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.