One day you’ll find that you’ve outgrown the native web controls in .NET, or find yourself using a set of controls in a lot of your projects, and feel the urge to implement something….custom. We’ve discussed extending a single, preexisting control to add extra functionality (such as a DropDownList pre-binded with gender data for a form), but what if you want to combine multiple controls, something similar to a user control, and want it to be easily deployable – such as dropping an assembly into your bin directory?
The answer is creating a composite control. A composite control is exactly what it sounds like – it’s a set of controls, added using the Controls.Add(…) method as child controls to the object. You lose out on design-time aspects you would otherwise get by using a user control (using a .ASCX file and inheriting from UserControl), but you gain the ability to compile one or more controls into a single assembly for easy redistribution. You may even add these to your Visual Studio toolbar.
As mentioned earlier, the Controls.Add(…) method is used to add child controls to the control “tree” inside your control. This method is available at any point in the state of your control but you should try to construct your control tree inside the CreateChildControls() method for one major reason – ViewState. If you build your controls inside of Render(), your controls will still load, but during a PostBack, you will lose your ViewState.
This behavior is related to the Page lifecycle and the order in which events fire. During a PostBack, the CreateChildControls() method is fired BEFORE the page’s PreLoad event, which is the event that loads up the ViewState from your last PostBack. The reason this is important is because, when you invoke Controls.Add(…) inside of that method, a special method called TrackViewState() is invoked on your composite control. Once this is called, any binded data will be added to the ViewState. Once it is added to the ViewState, the Page’s PreLoad method can load up the ViewState on a PostBack. This order is very important in maintaining the state of your composite control during PostBack.
Lets play with some code. The following code builds a very, very simple (pretty much useless) composite control. The point is to really show the basic structure and to demonstrate how it relates to maintaining the ViewState for TextBox and DropDownList controls. This control is inheriting from the CompositeControl class, which implements the INamingContainer interface. This marker interface gives the control’s child elements a unique ID (in reference to the parent control’s ID). You could also inherit from WebControl and implement the INamingContainer (System.Web.UI) interface yourself.
using System;
using System.Web;
using System.Web.UI.WebControls;
using System.ComponentModel;
namespace RyanControls
{
public class MyCompositeControl : CompositeControl
{
public MyCompositeControl() { }
protected override void CreateChildControls()
{
TextBox txtMyData = new TextBox();
DropDownList drpMyList = new DropDownList();
Button btnUpdate = new Button() { Text = "Update!" };
Controls.Add(drpMyList);
Controls.Add(txtMyData);
Controls.Add(btnUpdate);
if (!Page.IsPostBack)
{
drpMyList.Items.Add(new ListItem("item 1"));
drpMyList.Items.Add(new ListItem("item 2"));
drpMyList.Items.Add(new ListItem("item 3"));
}
}
}
}
Notice that I’m adding ListItem elements AFTER I added the DropDownList to the Controls list. This is an important step. If I had not done this, while I would be able to add items for this first instance of the control, they would be added BEFORE the TrackViewState method was invoked by performing Controls.Add(…). Because of this, they would not added to the ViewState and not be available during PostBack. If I had removed the IF-statement for checking IsPostBack and added items before the Controls.Add(…) method, it would still essentially work during a PostBack – the items would still appear and the selected index would still be correct (the ViewState is maintained for the controls themselves, but not their child controls in this case). However, this is adding the items for each and every time it’s called. If you were grabbing this data from, say, a database, you’re not going to want to perform this overhead each time. This is why we checked for a PostBack (which is what you should be doing anyway when binding unchanging data) so that we will only have to bind once and not each time. With that said, adding the items, or performing data binding, after the controls has been added will add it to the ViewState.
So, that’s pretty much it. If you were to run this code by loading it onto your page, you’ll see the three controls laid out side-by-side. If you change the DropDownList selection, type some text into the TextBox, and click “Update!”, you’ll notice that the ViewState is maintained, and no subsequent trips to the database or use of other binding methods are required.
Next time I’ll add onto this control by making use of delegates for adding custom event handlers (even with a custom event argument class)!