Introduction▲
À travers cet article, vous allez voir comment faire communiquer une ContentPage avec sa MasterPage. Puis vous verrez comment faire communiquer un UserControl avec sa page mère.
Le site web utilisé dans l'article est développé en C# / ASP.NET avec Visual Studio 2008, et se nomme MasterPageContentPageUserControl.
La structure de la MasterPage, nommée DvpMasterPage, est la suivante:
Le menu haut contiendra les liens permettant de naviguer entre les différentes pages du site.
Le menu bas contiendra un bouton et une liste déroulante.
Le pied de page contiendra un label.
Le code source est disponible à la fin de cet article.
1. Communication entre une MasterPage et une ContentPage▲
1-A. Communication de la ContentPage vers la MasterPage▲
Comme évoqué plus haut, le pied de page de la MasterPage contient un label nommé lblFooter, dont le texte par défaut est « Pied de page par défaut » :
<div id
=
"footer"
>
<asp:
Label ID
=
"lblFooter"
runat
=
"server"
>
Footer par défaut</asp
:
Label>
</div>
Le but ici est de pouvoir modifier le texte du label à partir de la ContentPage.
On va imaginer le fonctionnement suivant :
- une textbox et un bouton dans la ContentPage. Un clic sur le bouton entraîne une mise à jour du texte du pied de page avec le texte saisi dans la textbox.
Code aspx :
Modifier le texte du footer: <asp:
TextBox ID
=
"tbFooter"
runat
=
"server"
></asp
:
TextBox>
<asp:
Button id
=
"btnSubmit"
runat
=
"server"
Text
=
"Valider"
OnClick
=
"btnSubmit_Click"
/>
1-A-1. La méthode « pas propre »▲
La 1re idée qui vient à l'esprit est d'utiliser la méthode FindControl de la classe MasterPage, de caster l'objet Control récupéré en Label puis d'assigner une valeur à sa propriété Text.
Cela donnerait :
protected
void
btnSubmit_Click
(
object
sender,
EventArgs e)
{
// On récupère le label du pied de page
var
footer =
(
Label)this
.
Master.
FindControl
(
"lblFooter"
);
footer.
Text =
tbFooter.
Text;
}
Cela fonctionne, mais ce n'est pas vraiment idéal. Si vous vous trompez dans le nom du label passé à la fonction FindControl, ou si quelque temps plus tard vous modifiez le nom du label dans la MasterPage, vous aurez droit à une belle erreur lors de l'exécution du site web, alors que tout compile correctement.
1-A-2. La méthode « propre »▲
L'idée ici est de définir, au niveau de la MasterPage, une propriété qui mettra à jour le texte du label du pied de page.
Dans notre cas, une propriété SetOnly suffit. Ce qui donne :
public
string
FooterText
{
set
{
lblFooter.
Text =
value
;
}
}
L'étape suivante est d'accéder à cette propriété depuis la ContentPage.
Ce qui donnerait :
protected
void
btnSubmit_Click
(
object
sender,
EventArgs e)
{
this
.
Master.
FooterText =
tbFooter.
Text;
}
Le problème est que ce code ne compile pas. D'ailleurs l'IntelliSense ne propose pas la propriété FooterText.
La raison est simple: this.Master retourne un objet de type MasterPage, et non un objet de type DvpMasterPage.
Une solution serait de caster cet objet en DvpMasterPage. Ce qui donnerait:
protected
void
btnSubmit_Click
(
object
sender,
EventArgs e)
{
((
DvpMasterPage) this
.
Master).
FooterText =
tbFooter.
Text;
}
Cela fonctionne et, maintenant, l'IntelliSense nous propose la propriété FooterText.
Il existe cependant une solution plus élégante: la directive MasterType
Cette directive permet de fortement typer notre MasterPage. Au niveau du code aspx de notre page, cela donne:
<%@ MasterType VirtualPath
=
"~/DvpMasterPage.Master"
%>
À partir de maintenant, la propriété Master de la ContentPage est fortement typée comme vous pouvez le voir :
Et la propriété FooterText est directement accessible. Cela donne finalement :
protected
void
btnSubmit_Click
(
object
sender,
EventArgs e)
{
this
.
Master.
FooterText =
tbFooter.
Text;
}
Pour info, le fait d'insérer la directive MasterType surcharge la propriété Master dans le fichier designer.cs de la ContentPage, d'où le typage fort :
///
<
summary
>
/// Master property.
///
<
/summary
>
///
<
remarks
>
/// Auto-generated property.
///
<
/remarks
>
public
new
DVP.
MasterPageContentPageUserControl.
DvpMasterPage Master {
get
{
return
((
DVP.
MasterPageContentPageUserControl.
DvpMasterPage)(
base
.
Master));
}
}
1-B. Communication de la MasterPage vers la ContentPage▲
Le but ici est de récupérer dans la ContentPage, un clic sur le bouton ou une sélection dans la liste déroulante de la MasterPage.
Le principe est le suivant :
- du côté de la MasterPage: déclarer des événements et les lever lors du clic sur le bouton ou lors de la sélection d'un élément dans la liste déroulante ;
- du côté de la ContentPage: s'abonner aux événements de la MasterPage.
1-B-1. Côté MasterPage▲
On va déclarer deux événements : un pour le clic sur le bouton et un pour la sélection d'un élément dans la liste déroulante.
Cela donne:
public
event
EventHandler ColorChanged;
public
event
EventHandler BoutonClicked;
Il reste maintenant à lever ces événements au moment adéquat :
protected
void
btnMenu_Click
(
object
sender,
EventArgs e)
{
if
(
BoutonClicked !=
null
)
BoutonClicked
(
sender,
e);
}
protected
void
ddlColors_SelectedIndexChanged
(
object
sender,
EventArgs e)
{
if
(
ColorChanged !=
null
)
ColorChanged
(
sender,
e);
}
On va également avoir besoin d'une propriété qui permet de récupérer l'élément sélectionné dans la liste déroulante :
public
string
SelectedColor
{
get
{
return
ddlColors.
SelectedValue;
}
}
1-B-2. Côté ContentPage▲
Il faut commencer par créer les gestionnaires d'événements (« Event Handler »). Un gestionnaire d'événement est la méthode qui va s'exécuter en réponse à un événement.
private
void
Master_BoutonClicked
(
object
sender,
EventArgs e)
{
label.
Text =
string
.
Format
(
"Vous avez cliqué sur le bouton de la MasterPage à {0}"
,
DateTime.
Now.
ToLongTimeString
(
));
}
private
void
Master_ColorChanged
(
object
sender,
EventArgs e)
{
label.
Text =
"Vous avez sélectionné la couleur "
+
this
.
Master.
SelectedColor;
}
Il faut ensuite s'abonner à ces événements lors du chargement de la page. C'est là qu'interviennent les gestionnaires d'événements définis précédemment :
protected
void
Page_Load
(
object
sender,
EventArgs e)
{
this
.
Master.
ColorChanged +=
Master_ColorChanged;
this
.
Master.
BoutonClicked +=
Master_BoutonClicked;
}
On peut voir que l'IntelliSense propose les événements définis dans la MasterPage
Dorénavant, la ContentPage est automatiquement « avertie » de toute action sur le bouton ou la liste déroulante contenus dans la MasterPage.
2. Communication entre une Page et un UserControl▲
On va imaginer le scénario suivant :
- un UserControl qui contient un contrôle Calendar ;
- une page aspx qui contient ce UserControl ;
- à partir de la page, on souhaite pouvoir spécifier le mois à afficher dans le Calendar ;
- on souhaite récupérer au niveau de la page la date sélectionnée par l'utilisateur dans le Calendar.
2-A. Le UserControl▲
Côté design, le UserControl comporte uniquement un contrôle Calendar :
<%@ Control Language
=
"C#"
AutoEventWireup
=
"true"
CodeBehind
=
"UcCalendar.ascx.cs"
Inherits
=
"DVP.MasterPageContentPageUserControl.UcCalendar"
%>
<asp:
Calendar ID
=
"Calendar1"
runat
=
"server"
onselectionchanged
=
"Calendar1_SelectionChanged"
></asp
:
Calendar>
Il faut créer une propriété qui sera utilisée pour spécifier le mois à afficher. Lorsque cette propriété sera settée, on affichera également le Calendar qui est invisible par défaut :
public
Constants.
Months Month
{
set
{
Calendar1.
VisibleDate =
new
DateTime
(
DateTime.
Now.
Year,
(
int
)value
,
1
);
Calendar1.
Visible =
true
;
}
}
Le type Months est enum défini dans une classe static :
public
static
class
Constants
{
public
enum
Months
{
Janvier =
1
,
Fevrier =
2
,
Mars =
3
,
Avril =
4
,
Mai =
5
,
Juin =
6
,
Juillet =
7
,
Aout =
8
,
Septembre =
9
,
Octobre =
10
,
Novembre =
11
,
Decembre =
12
}
}
Il faut également créer une propriété qui retournera la date sélectionnée dans le Calendar :
public
DateTime SelectedDate
{
get
{
return
Calendar1.
SelectedDate;
}
}
La dernière étape consiste à mettre en place le mécanisme qui a pour but d'informer la page qui contient le UserControl lorsque l'utilisateur sélectionne une date dans le Calendar.
Une solution serait d'utiliser la méthode FindControl afin de récupérer le Label puis d'assigner une valeur à sa propriété Text. Cela donnerait :
protected
void
Calendar1_SelectionChanged
(
object
sender,
EventArgs e)
{
((
Label)this
.
Parent.
FindControl
(
"lblSelectedDate"
)).
Text =
string
.
Format
(
"La date sélectionnée est {0}"
,
Calendar1.
SelectedDate.
ToLongDateString
(
));
}
Encore une fois, cette solution est peu élégante. D'une part, on n'est pas à l'abri d'une faute de frappe sur le nom du Label ou d'un renommage de ce dernier ultérieurement. D'autre part, cela lie fortement le UserControl à la page, ce qui va à l'encontre du but d'un UserControl. En effet, un UserControl a pour but d'être réutilisé à différents endroits. Que se passera-t-il si vous utilisez ce UserControl dans une page qui ne contient pas de label intitulé lblSelectedDate? Vous aurez droit à une belle exception à l'exécution.
La bonne méthode est de créer un événement que l'on lèvera lors de la sélection d'une date dans le Calendar et qui sera intercepté par la page.
public
event
EventHandler SelectedDateChanged;
protected
void
Calendar1_SelectionChanged
(
object
sender,
EventArgs e)
{
if
(
SelectedDateChanged !=
null
)
SelectedDateChanged
(
sender,
e);
}
2-B. La Page▲
Dans notre exemple, la page qui contient le UserControl est très simple. Outre le UserControl, on y trouve également :
- une liste déroulante contenant la liste des mois ;
- un Label qui affichera la date sélectionnée dans le Calendar du UserControl.
La liste déroulante a sa propriété AutoPostBack à true. Ainsi, chaque sélection d'un élément entrainera automatiquement une mise à jour du Calendar.
<asp:
DropDownList
ID
=
"ddlMonths"
runat
=
"server"
AutoPostBack
=
"true"
onselectedindexchanged
=
"ddlMonths_SelectedIndexChanged"
>
</asp
:
DropDownList>
La liste déroulante est databindée au Page_Load à partir des valeurs de l'énumération Months précédemment définie.
protected
void
Page_Load
(
object
sender,
EventArgs e)
{
if
(!
IsPostBack)
{
ddlMonths.
DataSource =
Enum.
GetNames
(
typeof
(
Constants.
Months));
ddlMonths.
DataBind
(
);
}
}
Lors de la sélection d'un élément dans la liste déroulante, et afin de mettre à jour le Calendar, il suffit d'attribuer une valeur à sa propriété Month. Ce qui donne :
protected
void
ddlMonths_SelectedIndexChanged
(
object
sender,
EventArgs e)
{
var
selectedMonth =
(
Constants.
Months)Enum.
Parse
(
typeof
(
Constants.
Months),
ddlMonths.
SelectedValue);
UcCalendar1.
Month =
selectedMonth;
}
La dernière étape est de récupérer la date sélectionnée dans le Calendar et de l'afficher dans un Label. Pour cela, il suffit de s'abonner à l'événement levé par le UserControl lors de la sélection d'une date.
Avant de s'abonner à l'événement, il faut créer le gestionnaire d'événement, cette méthode qui sera appelée en réponse à un événement. Ce gestionnaire aura la simple tâche d'afficher la date sélectionnée dans le Calendar. Pour cela, il suffit d'utiliser la propriété du UserControl définie dans ce but. Cela donne :
protected
void
UcCalendar1_SelectedDateChanged
(
object
sender,
EventArgs e)
{
lblSelectedDate.
Text =
string
.
Format
(
"La date sélectionnée est {0}"
,
UcCalendar1.
SelectedDate.
ToLongDateString
(
));
}
Il y a deux façons de s'abonner à cet événement : de façon déclarative ou de façon programmatique.
De façon programmatique dans le Page_Load de la page :
protected
void
Page_Load
(
object
sender,
EventArgs e)
{
UcCalendar1.
SelectedDateChanged +=
new
EventHandler
(
UcCalendar1_SelectedDateChanged);
}
Ou alors de façon déclarative dans le code aspx :
<uc1:
UcCalendar ID
=
"UcCalendar1"
runat
=
"server"
OnSelectedDateChanged
=
"UcCalendar1_SelectedDateChanged"
/>
Notez qu'il faut ajouter le nom de l'événement avec le préfixe « On » lors de la déclaration de façon déclarative.
Conclusion▲
Cet article vous a montré comment faire communiquer une MasterPage avec une ContentPage, ainsi qu'un UserControl avec une Page.
Liens▲
Remerciements▲
Je remercie l'équipe Dotnet pour leurs relectures attentives du document.