Actualité Tutoriels Liens contact dotnet-tech.com - Home - Tutoriels techniques avec code source Dotnet-tech.com : Les site sur les technologies .Net

Emploi by Jobintree

Le MenuControl : contrôle riche

(Avec code source à télécharger)

Mis à jour le 15/09/2004
Par Boris Muller


Droit de diffusion:
L'ensemble ou partie de ce document ainsi que le code mis à disposition, ne peut être diffusé sur d'autres sites Web sans l'autorisation préalable de son créateur.

Introduction

            Comment faire un controle riche permettant d’afficher un menu à plusieurs niveaux, en utilisant les évènements, le databinding et les templates.

 

Connaître au préalable

Delegate et évènements

Le databinding

Les templated controles

 

Objectif

Définir le code source html pour chacun des niveaux de l’arborescence, dans le gabarit.

Définir autant de niveau différent que l’on souhaite sans modifier le contrôle.

Utiliser une source de donnée de type datatable ou icollection

Modifier les éléments crées à la volée.

 

Hypothèse

La source de donnée sera déjà triè. On supposera que chaque élément de la source de donné est un noeud et qu’il est fils du précédent si l’identifiant de son père (idparent) est égal à l’id de l’élément précédent.

 

Les différentes étapes   

  1. Définir et créer la classe MenuControl
  2. Créer la classe Item : MenuControlItem
  3. Ajouter le mécanisme de template .
  4. Définir et créer le mécanisme de liaison aux données
  5. Créer le gabarit et le code behind l’utilisant

Définir et créer la classe MenuControl

            Créer une nouvelle classe, la nommer MenuControl.

            Hériter de UserControl, pour bénéficier de l’ensemble des fonctionnalités implémentés pour cette classe.

            Ajouter l’interface INamingContainer, afin de permettre à cette classe de définir un namespace dans lequel chacun des contrôles crée aura un ClientID unique.

 

Voici le code source de ce contrôle.

/// <summary>

/// Summary description for MenuControl .

/// </summary>

public class MenuControl : UserControl, INamingContainer

{

}

 

Créer la classe Item : MenuControlItem

            Il nous faut pouvoir définir pour chaque niveau de l’arborescence:

-          le text pour afficher un libellé

-          l’image si image il faut afficher à la place du libellé

-          une url vers laquelle rediriger

-          un identifiant unique

-          l’identifiant parent pour établir la relation père-fils.

-          le rang du contrôle

 

On aura besoin aussi plus tard de pouvoir :

-          y retrouver le conteneur interne sur lequel ajouter les niveaux fils : Container

-          y ajouter l’objet provenant de la source de donnée auquel est bindé le contrôle : DataItem

 

Code source :

/// <summary>

/// Represents an item in the MenuControl control

/// </summary>

public class MenuControlItem : WebControl , INamingContainer

{

      #region Properties

      /// <summary>

      /// Gets or sets the text value for the control.

      /// </summary>

      public string Text

      {

            get

            {

                  return text;

            }

            set

            {

                  text = value ;

            }

      }

      private string text = "" ;

      /// <summary>

      /// Gets or sets the image value for the control.

      /// </summary>

      public string Image

      {

            get

            {

                  return image;

            }

            set

            {

                  image = value ;

            }

      }

      private string image = "" ;

      /// <summary>

      /// Gets or sets the rank value for the control[ value > 0].

      /// </ summary >

      public Int32 Rank

      {

            get

            {

                  return rank;

            }

            set

            {

                  if ( value < 0)

                        throw new IndexOutOfRangeException ( "Index of a MenuControlItem can not be less than 0." );

                  rank = value ;

            }

      }

      private Int32 rank = 0;

      /// < summary >

      /// Gets or sets the name of the inner container for the control.

      /// </ summary >

      public string ContainerName

      {

            get

            {

                  return containerName;

            }

            set

            {

                  containerName = value ;

            }

      }

      private string containerName = "" ;

      /// <summary>

      /// Gets the inner container for the control - if any.

      /// </ summary >

      public HtmlControl Container

      {

            get

            {

                  return ( HtmlControl ) this .FindControl ( this .ContainerName );

            }

      }

      /// < summary >

      /// Gets or sets the dataitem bound to the control.

      /// </summary>

      public object DataItem

      {

            get

            {

                  return dataItem ;

            }

            set

            {

                  dataItem = value ;

            }

      }

      private object dataItem = null ;

      /// <summary>

      /// Gets or sets the url value for the control

      /// </summary>

      public string Url

      {

            get

            {

                  return url ;

            }

            set

            {

                  url = value ;

            }

      }

      private string url = "" ;

      #endregion

}


Ajouter le mécanisme de template

On voudrait permettre à l’utilisateur :

1.       de définir le code html de chaque niveau de l’arborescence dans le gabarit

2.       de créer autant de modèle différent, qu’il aura de niveau d’arborescence.

 

Pour le premier point il faut :

-          ajouter la propriété ItemTemplate – de type Itemplate - à notre classe, pour recevoir les templates définis dans le gabarit

-          placer l’attribut TemplateContainer sur cette propriété afin de caractèriser le type de template – ici : MenuControlItem.

-          ajouter à la classe MenuControl l’attribut ParseChildren( true ), afin que le lien entre les templates définit dans le gabarit et l’attribut ItemTemplate de la classe se fasse.

 

Pour le second point :

            - le set de la propriété ItemTemplate stockera non pas l’Itemplate obtenu dans une variable mais plutôt dans une collection.

 

Voici le code source de la classe MenuControl modifié

/// <summary>

/// Summary description for MenuControl .

/// </summary>

[ ParseChildren ( true )]

public class MenuControl : UserControl, INamingContainer

{

      #region Properties

      public event MenuControlEventHandler ItemDataBound ;

      /// <summary>

      /// Sets the template for each level of the control

      /// </summary>

      [ TemplateContainer ( typeof ( MenuControlItem ))]

      public ITemplate ItemTemplate

      {

            set

            {

                  if ( itemTemplates == null )

                        itemTemplates = new ArrayList ();

                  itemTemplates.Add ( value );

            }

      }

      private ArrayList itemTemplates = null ;

}


Définir et créer le mécanisme de liaison aux données

On voudrait permettre à l’utilisateur du contrôle de réagir à la création d’un élément :

-          définir une classe MenuControlEventArgs par laquel sera transmis le contrôle crée

-          définir le delegate MenuControlEventHandler

-          créer un évènement ItemDataBound – de type MenuControlEventHandler - auquel pourra s’abonner l’utilisateur du contrôle.

 

Voici le code ajouté en dehors du contrôle

/// <summary>

/// Represents the EventHandler used by the MenuControl control

/// </summary>

public delegate void MenuControlEventHandler ( object sender, MenuControlEventArgs e);

/// <summary>

/// Represents the EventArgs used by the MenuControl control

/// </summary>

public class MenuControlEventArgs : EventArgs

{

      /// <summary>

      /// Gets or sets the MenuControlItem refered to.

      /// </summary>

      public MenuControlItem Item

      {

            get

            {

                  return item;

            }

            set

            {

                  item = value ;

            }

      }

      private MenuControlItem item = null ;

}

 

De plus il faudra :

  1. avoir une source de donnée : propriété DataSource.

 

/// <summary>

/// Gets or sets the DataSource for the control[ ICollection or DataTable]

/// </summary>

public object DataSource

{

      get

      {

            return dataSource ;

      }

      set

      {

            dataSource = value ;

      }

}

private object dataSource = null ;

 

  1. permettre à l’utilisateur du contrôle de définir les champs de la source de donnée. Pour cela on créera la classe possédant l’ensemble des champs et on créera une propriété public sur la classe MenuControl.

 

public class MenuControlDataField

{

      /// <summary>

      /// Get or sets the text property in the datasource for each template

      /// </summary>

      public string Text

      {

            get

            {

                  return text;

            }

            set

            {

                  text = value ;

            }

      }

      private string text = "[Text]" ;

      /// <summary>

      /// Get or sets the image property in the datasource for each template

      /// </summary>

      public string Image

      {

            get

            {

                  return image;

            }

            set

            {

                  image = value ;

            }

      }

      private string image = "[Image]" ;

      /// <summary>

      /// Get or sets the id property in the datasource for each template

      /// </summary>

      public string Id

      {

            get

            {

                  return id;

            }

            set

            {

                  id = value ;

            }

      }

      private string id = "[Id]" ;

      /// <summary>

      /// Get or sets the ParentID property in the datasource for each template

      /// </summary>

      public string IdParent

      {

            get

            {

                  return idParent ;

            }

            set

            {

                  idParent = value ;

            }

      }

      private string idParent = "[ IdParent ]" ;

      /// <summary>

      /// Get or sets the ParentID property in the datasource for each template

      /// </summary>

      public string Url

      {

            get

            {

                  return url ;

            }

            set

            {

                  url = value ;

            }

      }

      private string url = "[ Url ]" ;

}

 

la propriété publique sur le contrôle MenuControl

 

public MenuControlDataField DataField

{

      get

      {

            return dataField ;

      }

      set

      {

            dataField = value ;

      }

}

private MenuControlDataField dataField = new MenuControlDataField ();

 

  1. réagir à l’appel de la méthode DataBind

Important : lors du databind evaluer le type de source de donné pour récuperer la bonne Collection.

 

/// <summary>

/// Create all items from the DataSource

/// </summary>

public override void DataBind ()

{

      base .DataBind ( );

      ICollection source = null ;

      if ( DataSource == null )

            return ;

      if ( DataSource is DataTable)

            source = ((DataTable)DataSource).Rows;

 

      object lastObject = null ;

      bool DoWatch = false ;

      foreach ( object o in source)

      {

            if ( lastObject == null || DoWatch )

                  lastObject = CreateItem (source, o, 0, this );

            DoWatch = lastObject == o;

      }

}

 

Lors de la création de chaque contrôle fils, il faut :

-           créer le contrôle

-           y placer les valeurs obtenus à partir de la source de donnée

-           dans notre cas ajouter l’objet dataItem pour que l’utilisateur puisse s’en servir dans son codebehind

-           récuperer le template correspondant au rang et instantier le template dans le contrôle

-           l’ajouter au contrôle courant pour que son clientID soit correcte

-           lever l’évènement ItemDataBound pour permettre à l’utilisateur de le modifier

-           éventuellement supprimer le contrôle si l’utilisateur l’a décidé.

-           et parcourir le reste de la source de donnée pour voir si ce contrôle n’a pas de contrôle fils.

 

Voici le code source :

 

protected object CreateItem ( ICollection source, object dataItem , Int32 rank, Control parentControl )

{

      object lastObject = dataItem ;

 

      //define the nested control

      MenuControlItem mci = new MenuControlItem ( );

      mci.Text = DataBinder.Eval ( dataItem , this .DataField.Text ). ToString ();

      mci.Image = DataBinder.Eval ( dataItem , this .DataField.Image ). ToString ();

      mci.Url = DataBinder.Eval ( dataItem , this .DataField.Url ). ToString ();

      mci.DataItem = dataItem ;

      mci.Rank = rank;

      mci.ContainerName = this .ContainerName + rank.ToString ( );

 

      //find the right template for the control

      if ( rank > itemTemplates.Count - 1)

            throw new IndexOutOfRangeException ( string .Format ( "Template not defined for the object MenuControlItem [rank = {0}]." , rank));

      ITemplate it = ( ITemplate ) itemTemplates [rank];

      it.InstantiateIn(mci);

      mci.DataBind();

 

      //adding the control

      parentControl.Controls.Add ( mci );

 

      //Give user a view on the control before adding it to the collections off controls

      MenuControlEventArgs e = new MenuControlEventArgs ( );

      e.Item = mci ;

      if ( this .ItemDataBound != null )

            this .ItemDataBound ( this , e);

 

      //User can disable the control and sons of this control

      //by setting the Visible property of the MenuControlItem to false

      if ( ! mci.Visible )

            parentControl.Controls.Remove ( mci );

 

      //find if there is any children control in the datastream

      bool DoWatch = false ;

      string currentId = DataBinder.Eval ( dataItem , this .DataField.Id ). ToString ();

      Control innerControl = null ;

      foreach ( object o in source)

      {

            if ( DoWatch )

            {

                  //if current dataItem is parent for current element o

                  string idparent = DataBinder.Eval (o, this .DataField.IdParent ). ToString ();

                  string text = DataBinder.Eval (o, this .DataField.Text ). ToString ();

                  HttpContext.Current.Trace.Warn ( text + ":" + idparent + ":" + currentId );

                  if ( currentId.Equals ( idparent ))

                  {

                        if ( innerControl == null )

                        {

                             //finding the inner container

                             innerControl = mci.FindControl ( mci.ContainerName );

                             if ( innerControl == null )

                                   throw new NullReferenceException ( string .Format ( "Container control not defined for rank {0}" , rank));

                        }

                        //create element as son

                        lastObject = CreateItem (source, o, rank + 1, innerControl );

                        DoWatch = false ;

                  }

                  else

                        break ;

            }

            if ( dataItem == o || o == lastObject )

                  DoWatch = true ;

      }

      return lastObject;

}

 


Créer le gabarit

Il faut ici importer dans le gabarit le contrôle a l’aide de l’instruction de pré-parsing :

 

<%@ Register tagprefix ="control" Namespace=" Website.Templated " assembly="General"%>

 

Puis définir le contrôle :

 

 

Utiliser le databinding pour le remplir dans le code behind

/// <summary>

/// Summary description for Menu.

/// </summary>

public class Menu : System.Web.UI.Page

{

      #region Properties

      protected MenuControl MenuMain ;

      # endregion

 

      private void Page_Load ( object sender, System.EventArgs e)

      {

            DataTable dt = GetDataSource ( );

            MenuMain.ItemDataBound += new MenuControlEventHandler ( this .MenuMain_ItemDataBound );

            MenuMain.DataSource = dt;

            MenuMain.DataBind( );

      }

      protected void MenuMain_ItemDataBound ( object sender, MenuControlEventArgs e)

      {

            if ( e.Item.Rank == 0)

            {

                  e.Item.Attributes [ " onclick " ] = string .Format ( " javascript:Hide ('{0}')" , e.Item.Container.ClientID );

            }

      }

 

      public DataTable GetDataSource (){}

}


Conclusion

            Nous avons vu à travers cet exemple la puissance des templated controls. Ici la séparation gabarit - présentation - et codebehind – logique – prend tout son sens, et il devient dès lors facile de modifier le rendu sans toucher au code de logique.

            Grace au DataBinding et aux évènements nous avons même permis à l’utilisateur du contrôle de modifier le rendu au cours du DataBinding dans le codebehind pour une plus grande souplesse.

            Vous êtes maintenant prêt à créer vous même vos propre templated control.





Accueil - Tutoriels & Articles - Liens - A Propos de l'auteur dotnet-tech.com : le site des technologies .net

www.dotnet-tech.com - 2003-2007