AspAlliance.com LogoASPAlliance: Articles, reviews, and samples for .NET Developers
URL:
http://aspalliance.com/articleViewer.aspx?aId=1091&pId=-1
Mover List Control for ASP.NET 1.x/2.0
page
by Bilal Haidar
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 57923/ 98

Introduction

ASP.NET contains a large set of useful controls that each developer may require while developing web or windows applications. However, we as developers do not find all of the controls that we think of simply out-of-the-box. At the same time, since the .NET Framework is based on the object oriented concepts and since we have the server-side controls as objects, building new controls out of the existing controls is pretty simple and easy!

There are different kinds of custom control development, ranging from rendered controls to inherited controls, and finally the composite controls.

In this article we are going to develop a composite control that will make use of two xListBox, which is the control that we have developed few weeks ago and published on ASPAlliance.com where we fixed the issue of maintaining any changes in the ListBox items on the client side on the server. If you have not yet read that article, it is advisable to check it first before going on this article.

In addition to the two listboxes that we will use, two buttons are placed just in between the two listboxes. Those two controls will be used by end users to move items from one listbox to another and vice versa.

Continue reading down the sections to follow the steps required to build such a control in an organized and well explained way.

Requirements

Visual Studio .NET 2003

Note: Even though the project is created using Visual Studio .NET 2003, the control works with ASP.NET 2.0 applications.

xListBox History

As mentioned above, the composite control under development will use two instances of the control that we have developed in a previous article that can be reached on the following URL.

We will not go into the details of how that control was developed, however, it is worth mentioning that the xListBox implemented the IPostBackDataHandler interface where the LoadPostData default implementation was repeated with the addition of handling any added or removed items from the ListBox on the client side, since the Listbox that ships with ASP.NET lacks this feature. And because the user using the mover control will move items between the two listboxes all done on the client side, it is logical to make use of that control.

Hierarchy of the control

The new control we are developing extends the WebControl class. The WebControl class found in the .NET Framework inherits from the Control class. Using this class gives the control developer a richer environment especially in the field of styling the control he/she is developing.

The control under study also implements the INamingContainer interface. This interface ensures that the control you are developing will have a unique ClientID whenever it is placed on a web form, which is simply another control, thus allowing more than one control of the same type to be placed on the same web form.

The class header shows the main skeleton of the controls.

Listing 1

public class bhMover : WebControl, INamingContainer
{}

The mover control class consists of several sections; we are going to tackle each section separately throughout this article.

Constant Values Section

The IDs of the two listboxes and two buttons used in the composite control have been stored in constant variables so as not to havr any spelling mistakes when there is a need to write down the ID of any of the child controls. In addition, if we decided later on to change any of the names that will cost us just a simple change in those constant variables. The section is shown below.

Listing 2

private const string LEFT_LISTBOX_NAME = "leftlstbox";
private const string RIGHT_LISTBOX_NAME = "rightlstbox";
private const string RIGHT_TO_LEFT_BUTTON = "right2leftbtn";
private const string LEFT_TO_RIGHT_BUTTON = "left2rightbtn";
Child Controls

A composite control is known to hold several child controls. In this control we are developing, we will use two instances of the listbox control and two buttons that are going to be used to move items between the two lists. Therefore, a protected local variable is created for each of the controls used and instantiated.

Listing 3

// Left ListBox
protected xListBox leftListBox = new xListBox();
// Right ListBox
protected xListBox rightListBox = new xListBox();
// LeftToRight Button
protected Button leftToRight = new Button();
// RightToLeft Button
protected Button rightToLeft = new Button(); 
Member Variables

The mover control will expose a set of public properties that allows the page developer to configure as he/she wishes. Therefore, for each public property there will be a member variable that will hold the values configured by the page developer. This control will expose the left and right listboxes style property, a style property for both buttons, and finally the data source for each of the listboxes. The member variables are shown below.

Listing 4

// Styles for the ListBoxes
protected Style leftListBoxStyle = new Style();
protected Style rightListBoxStyle = new Style();
// Style for the buttons
protected Style buttonChooserStyle = new Style();
// Members to hold datasource for both listboxes
protected object leftListDataSource;
protected object rightListDataSource;
Public Properties

As mentioned in the previous section, the properties exposed are mainly those related to the styling of the child controls used. We will have a thorough discussion on how those public properties are written. We will take an example the RightListBoxStyle property whose code is shown below.

Listing 5

public Style RightListBoxStyle
{
  get
  {
    if (this.IsTrackingViewState)
      ((IStateManager)this.rightListBoxStyle).TrackViewState();
    this.ChildControlsCreated = false;
    return this.rightListBoxStyle;
  }
}

As you have noticed, we have just implemented the “get” section of this property. There is no “set” section and, hence, the page developer will not be able to do something as the following.

Listing 6

Style s1 = new Style();
 bhMover.RightListBoxStyle = s1;

The page developer will not be able to set the property. So how will he/she be able to configure this property? The answer is shown in the code snippet below.

Listing 7

bhMover.RightListboxStyle.CssClass = “lstBox”;

You access the inner fields of the Style using the above mentioned way and by doing so, you are first accessing the “get” section of the property, once you have the get section, you are configuring items in it. What is happening internally is that the local member leftLiatboxStyle is being configured automatically. Here comes the question, how will be able to preserve the configuration on the local member against postbacks, in a time there is no way to use the ViewState in the “get” section of the property to preserve the instance? This question will be answered later in this article when we will override the SaveViewState, LoadvioewState and TrackViewState methods to be able to preserve those instances.

As you have seen, we have explicitly checked to make sure the control is tracking its ViewState given that the container control is doing this properly.

The other properties are similar to this one explained above, therefore, we will not go into the details of the other properties; you can find them in the downloadable code that comes with this article.

Public Data Properties

This section in the mover control has been added upon the advice of my colleague Miguel Castro who is an MVP and a well known consultant in the field of .NET. He said that since this is a composite control, which means we are encapsulating controls within this control, it is not wise to simple expose the whole child controls to page developers! And he is absolutely true! What we can do is add some required properties and allow the page developers to configure to make the listboxes and buttons function properly. For instance, we have added style properties to let the page developer, if interested, to add some formatting properties. In addition, we want to allow the page developer to bind some data to the two listboxes. Therefore, it is wise to provide four main properties:

ListBoxDataSource

ListBoxItems

ListBoxDataTextField

ListBoxDataValueField

Those properties have been added for both listobxes. So, we will explain them in a generic way to show you why they have been added.

The ListboxDataSource will be used by page developers to bind any of the two listboxes to any custom collection, array list, dataset, etc … therefore, it is a “set” property only. One of those properties is shown below.

Listing 8

public object LeftListDataSource
{
  set
  {
    this.leftListDataSource = value;
  }
}

The ListBoxItems is a simple “get” property that returns the items present at any moment that control is being accessed. The code is show below:

Listing 9

public ListItemCollection LeftItems
{
  get
  {
    this.EnsureChildControls();
    return this.leftListBox.Items;
  }
}           

First of all, we make sure the controls on the composite control are created and this is done by calling the EnsureChildControls method. This internally checks whether the ChildControlsCreated property is false or true and accordingly calls the CreateChildControls method that we will discuss soon in this article and will mainly build the control tree of this control being developed.

The ListBoxDataTextField, as its name suggests, is used to specify the field that will be shown to the users when the items are added to any of the listboxes. It mainly corresponds to the DataTextField of the controls found in .NET Framework used to display a list of data.

Listing 10

public string LeftListDataTextField
{
  get
  {
    if ((object)ViewState["LeftListDataTextField"] == null)
      return "";
    return (string)ViewState["LeftListDataTextField"];
  }
  set
  {
    ViewState["LeftListDataTextField"] = value;
    this.ChildControlsCreated = false;
  }
}

As mentioned above, the idea behind those properties came to me from Miguel’s article on code-Magazine, Custom Web Controls Demystified, Part 2, and we would strongly recommend checking it out for the richness of information it gives developers in order to develop rich and well constructed controls.

Hint: Never expose the child controls contained in a composite control as public properties. Always do a selection on some properties required for those inner controls and expose them as public properties.

Overridden Methods

A set of methods that are part of the WebControl class are overridden in this control. The methods are the following.

CreateChildControls

The CreateChildControl is the core method used in composite control development. This method has mainly three functionalities:

Clear the entire child controls.

Build the control tree of the control itself.

Clear the view state of all the child controls.

The above mentioned functionalities are specified as follows:

Listing 11

protected override void CreateChildControls()
{
// Clear any present control
  Controls.Clear();
 
// Create the actual heirarchy
  CreateControlHierarchy();
 
// Clear view state of child controls
  this.ClearChildViewState();
}

The CreateControlHierarchy is implemented as follows:

Listing 12

protected virtual void CreateControlHierarchy()
{
  #region Initialize Controls
  InitializeControls();
  #endregion
 
// Build the hierarchy
  this.Controls.Add(new LiteralControl(
    "<table cellpadding='3' cellspacing='3' border='0'>"));
  this.Controls.Add(new LiteralControl("<tr>"));
 
// Cell for the Left ListBox
  this.Controls.Add(new LiteralControl("<td valign='middle' align='center'>"));
  this.Controls.Add(this.leftListBox);
  this.Controls.Add(new LiteralControl("</td>"));
 
// Cell for the Buttons
  this.Controls.Add(new LiteralControl("<td valign='middle' align='center'>"));
  this.Controls.Add(new LiteralControl(
    "<table cellpadding='0' cellspacing='0' border='0'>"));
  this.Controls.Add(new LiteralControl("<tr><td>"));
  this.Controls.Add(this.leftToRight);
  this.Controls.Add(new LiteralControl("</td></tr>"));
  this.Controls.Add(new LiteralControl("<tr><td>"));
  this.Controls.Add(this.rightToLeft);
  this.Controls.Add(new LiteralControl("</td></tr>"));
  this.Controls.Add(new LiteralControl("</table>"));
  this.Controls.Add(new LiteralControl("</td>"));
 
// Cell for the Right ListBox
  this.Controls.Add(new LiteralControl("<td valign='middle' align='center'>"));
  this.Controls.Add(this.rightListBox);
  this.Controls.Add(new LiteralControl("</td>"));
 
// Close the TR and Table
  this.Controls.Add(new LiteralControl("</tr>"));
  this.Controls.Add(new LiteralControl("</table>"));
}

First of all, the local method called InitializeControls is called. This method does nothing but initialize the local child controls used. Its implementation is show below.

Listing 13

private void InitializeControls()
{
  #region Initialize LeftListBox
  this.leftListBox.ID = LEFT_LISTBOX_NAME;
  this.leftListBox.Width = Unit.Pixel(200);
  this.leftListBox.Rows = 15;
  this.leftListBox.ApplyStyle(this.leftListBoxStyle);
// Make sure to have multiple selection mode
// this way, we will have only two buttons in between
// the list boxes so that, movement can be done
// per item or items.
  this.leftListBox.SelectionMode = ListSelectionMode.Multiple;
  #endregion
 
  #region LeftToRightButton
  this.leftToRight.ID = LEFT_TO_RIGHT_BUTTON;
  this.leftToRight.EnableViewState = false;
  this.leftToRight.Text = ">";
  this.leftToRight.ApplyStyle(this.buttonChooserStyle);
  #endregion
 
  #region RightToLeftButton
  this.rightToLeft.ID = RIGHT_TO_LEFT_BUTTON;
  this.rightToLeft.EnableViewState = false;
  this.rightToLeft.Text = "<";
  this.rightToLeft.ApplyStyle(this.buttonChooserStyle);
  #endregion
 
  #region Initialize RightListBox
  this.rightListBox.ID = RIGHT_LISTBOX_NAME;
  this.rightListBox.Width = Unit.Pixel(200);
  this.rightListBox.Rows = 15;
  this.rightListBox.ApplyStyle(this.rightListBoxStyle);
  this.rightListBox.SelectionMode = ListSelectionMode.Multiple;
  #endregion
}

As you can see, for each child control used a set of properties are configured and, hence, the control itself is being initialized.

Going back to the CreateControlHierarchy method, after initializing the child controls, there comes the time to add them to the composite controls’ property Controls. This property contains all the child controls that are going to be shown and used in this control.

We add the controls and contain them in a constructed table as shown above in the code. A better approach to construct this table is to use the Render method of each control to render the table required and each control inside it. However, for the sake of simplicity we have constructed the table using LiteralControls in the CreateChildControls to hold the table, tr, td tags, and other controls required to be rendered; however, as a rule of thumb, it is better to use the Render method to do so.

Hint: When constructing the tree of controls, it is recommended to use the Render method by using AddAttribute, AddStyleAttribute, and other useful methods used inside the Render method.

Render

The render method of the composite control has been overridden, mainly to attach some JavaScript method calls on the two buttons added in the control. Those JavaScript methods will be used to move items between the two controls easily and neatly.

Listing 14

protected override void Render(HtmlTextWriter writer)
{
// Prepare Scripts
  string scriptLeftToRight = "bhMover_MoveItem('" + this.leftListBox.ClientID +
    "','" + this.rightListBox.ClientID + "');";
  string scriptRightToLeft = "bhMover_MoveItem('" + this.rightListBox.ClientID
    + "','" + this.leftListBox.ClientID + "');";
 
// Ensure controls are created
  this.EnsureChildControls();
 
// Add client side events
  this.leftToRight.Attributes.Add("onclick", scriptLeftToRight +
    " return false;");
  this.rightToLeft.Attributes.Add("onclick", scriptRightToLeft +
    " return false;");
 
  base.Render(writer);
}

First of all, we constructed the JavaScript methods calls. Notice that we have used the ChildID of the two listboxes. With this implementation you make sure that if you have placed your mover control in a MasterPage for instance, the ID of the listboxes will be calculated as it should be, hence, taking care of all the successive containers where the listbox will be displayed.

After that we make sure the control tree is constructed by simple calling EnsureChildControls method discussed above.

Then we have attached the “onclick” client event for each server side button to the above created JavaScript method calls. Notice that we have added “return false;” to the end of the “onclick” event. What does this do? This ensures that the server side button used in this composite control will not cause a postback to the server and, therefore, only the JavaScript method calls will be executed and no postback happens and all the movement of items between the two listboxes will be done on the client side.  This clearly explains why we have used the xListBox control instead of the normal ListBox!

Hint: Add “return false;” to any client side event attached to any server side control to prevent the server side control from posting back to the server.

OnPreRender

The OnPreRender method is used to add any JavaScript methods required by the control. As you have seen above, the two buttons used to move items between the listboxes calls a JavaScript method named “bhMover_MoveItem”. This method is shown below.

Listing 15

function bhMover_MoveItem(ctrlSource, ctrlTarget)
{
  var Source = document.getElementById(ctrlSource);
  var Target = document.getElementById(ctrlTarget);
 
// Make sure both Listboxs are present
  if ((Source != null) && (Target != null))
  {
// Loop through all the items selected
// since the ListBoxes are configured to have
// multiple selection mode
    while (Source.options.selectedIndex >= 0)
    {
// Create a new instance of ListItem
      var newOption = new Option();
      newOption.text = Source.options[Source.options.selectedIndex].text;
      newOption.value = Source.options[Source.options.selectedIndex].value;
 
//Append the item in Target
      Target.options[Target.length] = newOption;
 
// Add the new text|value to the Hidden Field _ADDED of the Target Listbox
      AddListItem(Target, newOption.text, newOption.value);
 
//Remove the item from Source
      Source.remove(Source.options.selectedIndex);
 
// Add the index of the item to be removed into the
// Hidden Field _REMOVED
      RemoveListItem(Source, newOption.value);
 
    }
  }
}

This method is usually called by passing to it two parameters which are the source and target listboxes. This means that in case we are moving items from the left listbox to the right listbox, we will send as a source the left listbox and as a target the right listbox. It will simply loop through all selected items in the source listbox and moves them to the target listbox control. In addition, it calls two methods that come from the xListbox, mainly the AddListItem and RemoveListItem. Those two methods have been thoroughly explained the xListBox article mentioned above. Those two items add/remove any item to some hidden fields, on the page holding the control, that are used later on the server to recreate the items of each listbox taking into consideration any changes done on the client side.

The OnPreRender method adds the above JavaScript in the form of an included JavaScript file that should be present in the same directory of the page that is using this composite control.

Listing 16

protected override void OnPreRender(EventArgs e)
{
  base.OnPreRender(e);
 
// Add the bhMover javascript file
  StringBuilder script = new StringBuilder();
  string EOL = Environment.NewLine;
  script.Append("<script language='javascript' src='bhMover_js.js'></script>" +
    EOL);
 
// Register the JavaScript file that includes utility method
  if (!Page.IsClientScriptBlockRegistered("bhMoverScript"))
    Page.RegisterClientScriptBlock("bhMoverScript", script.ToString());
}

OnDataBinding

This method has been overridden to bind the two listboxes to their data sources and set their DataTextField and DataValueField respectively as shown in the code below.

Listing 17

protected override void OnDataBinding(EventArgs e)
{
  if (this.leftListDataSource != null)
    this.leftListBox.DataSource = this.leftListDataSource;
  if (this.rightListDataSource != null)
    this.rightListBox.DataSource = this.rightListDataSource;
 
  this.leftListBox.DataValueField = this.LeftListDataValueField;
  this.leftListBox.DataTextField = this.LeftListDataTextField;
 
  this.rightListBox.DataValueField = this.RightListDataValueField;
  this.rightListBox.DataTextField = this.RightListDataTextField;
 
  this.leftListBox.DataBind();
  this.rightListBox.DataBind();
 
  this.ChildControlsCreated = false;
}           

Special ViewState consideration

In some of the public properties we have used in this control mainly the simple data type properties:

LeftListDataValueField

RightListDataValueField

LeftListDataTextField

RightListDataTextField

We have used the nice technique of preserving their member variables in the ViewState in the “set” section of the properties.

In other public properties like the LeftListBoxStyle for example, since we had only a “get” section, there was no way to use the ViewState technique as used by other properties. Therefore we had to implement three methods that we have already mentioned above:

SaveViewState

Listing 18

protected override object SaveViewState()
{
// State of the whole composite control
  object[]state = new object[4];
 
  state[0] = base.SaveViewState();
  state[1] = ((IStateManager)this.leftListBoxStyle).SaveViewState();
  state[2] = ((IStateManager)this.rightListBoxStyle).SaveViewState();
  state[3] = ((IStateManager)this.buttonChooserStyle).SaveViewState();
 
  return state;
}

First of all, we preserver the ViewState of the entire composite control, after that we save the state for each of the Style member variables that we have used in the “get” sections of the public properties. Notice that the Style class implements the IStateManager as protected methods, and hence, we should first of all cast the style variable to IStateManager, and then access its SaveViewState method. We repeat the same for the other style member variables.

LoadViewState

We should also implement the LoadViewState method that will be used to retrieve the style local members when needed.

Listing 19

protected override void LoadViewState(object savedState)
{
  object[]state = null;
  if (savedState != null)
  {
    state = (object[])savedState;
    base.LoadViewState(state[0]);
    ((IStateManager)this.leftListBoxStyle).LoadViewState(state[1]);
    ((IStateManager)this.rightListBoxStyle).LoadViewState(state[2]);
    ((IStateManager)this.buttonChooserStyle).LoadViewState(state[3]);
  }
}

The reverse of what we have done in the SaveViewState is done in this method. We load back the state of each style member variable by calling each LoadViewState method.

TrackViewState

Finally, the TrackViewState method should be implemented to make sure each style member variable is tracking its own ViewState.

Listing 20

protected override void TrackViewState()
{
  base.TrackViewState();
  if (this.leftListBoxStyle != null)
    ((IStateManager)this.leftListBoxStyle).TrackViewState();
 
  if (this.rightListBoxStyle != null)
    ((IStateManager)this.rightListBoxStyle).TrackViewState();
 
  if (this.buttonChooserStyle != null)
    ((IStateManager)this.buttonChooserStyle).TrackViewState();
}

For each style member variable we check, if the variable is not null then call its TrackViewState.

What made the life easy here is that Style class itself implements IStateManager, so the role is nothing but calling those methods on each member variable.

To sum up this section, we had to handle the ViewState of the member variables used in the public properties that were part of their “get” section and had no other way to preserve their state in the ViewState like we have done to all other simple data type properties that included both “get” and “set” sections.

Some other properties have not been preserved in the ViewState as you might ask yourself. You are absolutely right, but I did not mention anything about those properties to make you think more of why I have done this!

If you look at the LeftListBoxDataSource property, it uses internally in the “set” section a member variable to hold the DataSource of a specific listbox. The data source will be eventually assigned to the data source of the listbox, and since the listbox preserves its items in the ViewState internally there was no need to preserve those member variables in the ViewState and do the double work!

The same applies on the LeftItems and RightItems properties which represent nothing but the items of each listbox which are also preserved internally into the ViewState.

Serializing complex properties at Design-time

If you look at the downloadable code, you will notice that we have added an attribute to the Style properties and the ListItemCollection properties:

DesignerSerializationVisibility(DesignerSerializationVisibility.Content)

The DesignerSerializationVisiblity attribute should be added to public properties to specify whether those properties should be serialized in the composite control at design time or not.

You still cannot see the picture here? We will show a code snippet taken from a web form where the mover control has been added and the BackColor of the left listbox has been set.

Listing 21

<cc1:bhMover id="BhMover1" runat="server">
  <LeftListBoxStyle BackColor="Red"></LeftListBoxStyle>
</cc1:bhMover>

If you go back to the public property named LeftListBoxStyle, you would see the above mentioned attribute specified. That attribute takes as input an enumeration of type DesignerSerializationVisibility which has three different values.

Visible: It forces the composite control to serialize the designated property at design time and is mainly used for simple data type properties.

Content: It forces the composite control to serialize the designated property at design time and is mainly used for complex data types.

Hidden: It forces the composite control not to serialize the designated property at design time.

Since the Style class is a complex data type, we have used the Content value and the above code showed how the LeftListBoxStyle property is serialized and rendered at design time once you place the composite control on a web form.

More on the DesignerSerializationVisibility attribute can be found in this article: Serialize properties correctly with DesignerSerializationVisibilityAttribute.

You have also noticed the presence of another attribute, PersistenceMode attribute. This attribute specifies how the property should be persisted by the designer, i.e. whether the property will be shown as an attribute or as a nested element with the property fields nested inside. This attribute takes as input an enumeration of type PersistenceMode which has the following values:

Attribute: The property will be persisted as an attribute on the main composite control tag. This value is mainly used for simple data type properties.

InnerProperty: The property will be persisted as a nested element within the composite control tag. This value is mainly used for complex data type properties.

More on this topic can be found at this article: Persist properties correctly with PersistenceModeAttribute.

Hint: Once again you are advised to check the article mentioned above by Miguel Castro, where we have came up with that idea of serialization and other important ideas.

Fix Control View in Design time

In .NET 1.1 a trick should be added to the composite control to be able to view the composite control child-controls at design time correctly.

The trick is by adding this new property:

Listing 22

public new void EnsureChildControls()
{
  base.EnsureChildControls();
}

Since the Control class has the EnsureChildControls implemented as protected, we need to add this method and, as you have noticed, it is added with the keyword “new” which means we are not overriding the EnsureChildControl of the parent class but rather giving a new implementation of that which is nothing but calling the base EnsureChildControls method.

Then after that we add a Designer class as follows:

Listing 23

public class bhMoverDesigner: ControlDesigner
{
  public override String GetDesignTimeHtml()
  {
// Ensure the control is built
    bhMover mover = this.Component as bhMover;
    mover.EnsureChildControls();
 
    return base.GetDesignTimeHtml();
  }
}

In this designer class we have overridden the GetDesignTimeHtml method which is used to show the control in design time in the Visual Studio IDE. We simple get the instance of the control and then call its EnsureChildControls method that we have just added above. This trick will take care of showing the composite control together with its child controls at design time.

Downloads

[Download Sample]

Note: The MoverControlApplication folder contains the project with the code. MoverWeb is the testing project which implements the control.

Conclusion

In this article we have seen how easy it is to build a composite control in general and a mover list control specifically. The xListBox has been used a replacement to the ListBox that ships with ASP.NET.  Several hints and articles have been mentioned through out the article and it is very important that you check them out and learn from them. Every single line of code in the composite control has been explained in details to give you a clear idea on what is going on through out the entire sections of the control.


Product Spotlight
Product Spotlight 

©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-03-28 2:07:07 PM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search