Click here to Skip to main content
Email Password   helpLost your password?

Introduction

Recently I built a web file manager server control. The control embeds two other server controls - microsoft.web.ui.webcontrols.treeview and DataGrid - and has to handle rendering, events and ViewState for those controls. It took me a lot of trial and error to figure out how to wire everything up correctly. This article simply shares the lessons I learned in the hope that it will save you some time Googling for the answers.

Figuring out ToolboxData

Let's start with something simple. You know that the {0} in:

[ToolboxData( "<{0}:ExiaWebFileManager runat= "server">" )]

gets replaced with something to form the HTML tag. But what does it get replaced with? This drove me crazy until I figured it out, and until you do, dragging and dropping the control from the toolbox will yield weird results, like replacing {0} with "cc1". If you're like me, you've been wondering for a long time how to get rid of the "cc1" and in your frustration, you might have written something like:

[ToolboxData( "<Exia:ExiaWebFileManager runat= "server">" )]

Well, it turns out that it gets filled in from an assembly level attribute. Put an attribute like this somewhere outside your namespaces, such as in AssemblyInfo.cs.

[assembly:TagPrefix( "Exia.Web.UI.WebControls", "Exia" )]

This tells Visual Studio that when you drop any control in the Exia.Web.UI.WebControls namespace onto a form, the tag prefix will be "Exia". Aside from the fact that your tags will be well formed, the <%register> directive will now get written out properly.

Another quickie... the Toolbox icon

What threw me off here was that it's not an icon, it's a bitmap. Create a 16x16 bitmap, include it in your project, set its Build Action to Embedded Resource and put an attribute like this above your server control class:

ToolboxBitmapAttribute( typeof(ExiaWebFileManager), 
                                       "FileManager.bmp")

Preserving ViewState of embedded server controls

This drove me crazy and consumed three days of my time. What threw me off was searching Google and finding some code like this...

private TreeView treeView
{
    get{ return (TreeView)ViewState["TreeView"]; }
    set{ ViewState["TreeView"] = value; }
}

...coupled with my own stupidity. The idea behind this code is that the custom control contains an embedded TreeView, and it makes sense then that the TreeView needs to be saved and restored on ViewState, right? That was my thinking anyway. Of course, you get an error saying the TreeView can't be serialized, and there are some more threads on Google about how it could be made serializable, that made me think I was on the right track.

Finally, just out of frustration, I tried the simplest thing possible, knowing it wouldn't work...

private TreeView treeView = new TreeView;

...and whaddayaknow... it worked!

That's when it dawned on me that (of course, you idiot) no control is ever saved, just its ViewState. The control is re-instantiated from scratch on each postback, and the saved ViewState is applied to the new control.

Well, it almost worked...

Of course, the control needs to be managed in the context of the control hierarchy and rendered before its ViewState can be restored. My next big challenge was to figure out how CreateChildControls and Render are related to each other, and what was supposed to do what, and when, and with what? After wading through some seemingly overly complex examples on the web, I managed to boil it down to the following simple rules. I'm sure there are other ways to do it, but here's how it works for our control:

Declare and instantiate controls as private instance variables

Say your control has a table that contains a row, the row contains a cell, and the cell contains a Microsoft.Web.UI.WebControls.TreeView. Declare the elements as private instance variables of your control, like:

class ... 
{
    private Table BorderTable = new Table();;
    private TableRow BorderTableRow = new TableRow();
    private TableCell BorderTableCell = new TableCell();
    private TreeView MyTreeView = new TreeView();
    etc.
...
...

Initialize the controls in the OnInit event

In the OnInit event, configure basic control properties, such as AutoGenerateColumns, ShowHeader etc., and wire up the events. (More on event wire-up later.)

protected override void OnInit(EventArgs e)
{
    InitializeControls();
    base.OnInit (e);
}

private void InitializeControls()
{
    // Set basic properties of controls

    FileListGrid.AutoGenerateColumns = false;
    FileListGrid.ShowHeader = true;
    etc.

    // Wire up events.

    FileListGrid.ItemCommand += 
       new DataGridCommandEventHandler( FileListGrid_ItemCommand );
    FileUploader.DialogClosed += 
       new DialogOpenerClosedEvent(FileUploader_DialogClosed);
    DeleteBtn.Click += new EventHandler( DeleteBtn_Click );
    AddBtn.Click += new EventHandler( AddBtn_Click );
    MoveBtn.Click += new EventHandler( MoveBtn_Click );
    etc.
}

Assemble the controls in CreateChildControls

In the CreateChildControls method, put the controls together into a hierarchy. Most importantly, add the root control or controls to the Controls collection of the server control. At the same time, set any attributes that need to be set, like CSS class and so on:

protected override void CreateChildControls()
{
    BuildControlHeirarchy();
    ...
    ...
}

private void BuildControlHeirarchy()
{
    // Add the root control to the 

    // Controls collection of

    // the server control. When this 

    // is done, the base.Render()

    // method will render the entire 

    // heirarchy for you and

    // manage the ViewState of any child 

    // controls in the heirarchy

    Controls.Add( BorderTable );
    BorderTable.CellSpacing = 0;
    
    // Build up the rest of the control heirarchy...

    BorderTable.Controls.Add( BorderTableRow );
    BorderTableRow.Controls.Add( BorderTableCell );
    BorderTableCell.CssClass = BorderCssClass;
    BorderTableCell.Controls.Add( MyTreeView );
    ...
    ...
    etc.
}

Do any custom rendering in the Render method

The minimum you have to do in the Render method is call Base.Render(). In fact, the minimum you have to do is not to override this method. The base Render method takes care of rendering the hierarchy of controls and child controls that you built up in CreateChildControls. You need to override Render to render special HTML that you can't easily build up using CreateChildControls. In our Render method, for instance, we write out a special style that fixes the grid header in a stationary position when the grid scrolls, like this:

output.Write(@"<style type='text/css'>
    .DataGridFixedHeader { POSITION: relative; ; 
          TOP: expression(this.offsetParent.scrollTop - 2) }
    </style>" );

Another reason to override Render is for speed. Rendering HTML directly is faster than building up the control hierarchy. (But of course, if you render the HTML directly, you don't have access on the server to the objects rendered, so if you need to set up things like the objects' event handlers and so on, then you'll either need to build them up in CreateChildControls or know quite a bit about wiring up HTML to events with GetPostBackEventReference(), and other complex stuff.)

If you do override, just don't forget to call base.Render(), otherwise the control hierarchy you had built up in CreateChildControls will never get rendered:

Here's what our complete Render method looks like. As you can see, it does very little other than call base.Render.

protected override void Render(HtmlTextWriter output)
{
    if( ! Visible )
        return;

    if( FileListGrid.Items.Count == 0 )
        FileListGrid.ShowHeader = false;
    
    // Write out the html to include the CSS file

    output.Write( "<link href='" + CssFile + "' 
             type='text/css' rel='stylesheet'>" );

    // Write out the CSS style for the 

    // fixed datagrid header. 

    output.Write( @"
            <style type='text/css'>
            .DataGridFixedHeader { POSITION: relative; ; 
               TOP: expression(this.offsetParent.scrollTop - 2) }
            </style>" );

    base.Render( output );            
}

Create grid columns in CreateChildControls

If your control embeds a DataGrid or other type of server control, when should you create the grid columns? Do they get saved with ViewState?

No, the columns are not saved with ViewState. They're child controls of the grid control in the same sense that table cells are child controls of table rows. So you create them at the same time you create other controls in the hierarchy, in CreateChildControls.

Bind the data in CreateChildControls

The data gets bound in CreateChildControls, but not on postback. Here's what our complete CreateChildControls method looks like:

protected override void CreateChildControls()
{
    ConfigureFileGridColumns();
    BuildControlHeirarchy();

    if( ! Page.IsPostBack )
        BindData();

    base.CreateChildControls();
}

Managing the ViewState

Now comes the great part. It turns out that if everything is set up correctly, there's almost nothing that you need to manually manage with ViewState. The embedded server controls will automatically manage their own ViewState. The only time you need to be concerned about ViewState is for managing control-specific information that needs to be round-tripped. For instance, in our file manager control, we call ViewState to keep track of the following things:

Here's how our code for keeping track of the currently selected file looks:

false)>  // Suppress display in the 

                    // Properties browser in 

                    // Visual Studio

public string SelectedFile
{
    get
    {
        if( ViewState["SelectedFile"] == null )
            ViewState["SelectedFile" ] = "";

        return (string)ViewState["SelectedFile"];
    }
    set
    {
        ViewState["SelectedFile"] = value; 
    }
}

All very straightforward stuff. So to summarize regarding ViewState, as long as the controls are assembled into the control hierarchy in CreateChildControls and they're rendered in the base render method, the managing of ViewState for embedded controls is all taken care of and you only have to do any custom ViewState management specific to your control.

Wiring up events

Our control emulates the Windows file explorer, with a TreeView displaying directories and a grid displaying the files. When a user clicks on a file, we need to perform some custom actions. In order to make this work, we need to trap the ItemCommand event of the embedded DataGrid that constitutes our file list.

You would think this would be pretty straightforward. When you initialize the DataGrid (see above) you just wire up the event like this:

FileListGrid.ItemCommand += 
  new DataGridCommandEventHandler(FileListGrid_ItemCommand);

There's just one problem. The event handler never gets called. So you Google a day or two away, and finally in desperation, or just out of luck (maybe you should have bought that book on server controls after all) you try implementing INamingContainer. And bam, the events work. That's when you realize that if the control doesn't have its own ID namespace in the control's hierarchy, the events can't wire up properly. So don't forget to implement INamingContainer if you want to trap events of embedded controls.

What about IPostbackEventHandler?

While we're on the subject of implementing, what about implementing IPostbackEventHandler. Do you have to implement that in a custom control? The answer is that you need to implement IPostbackEventHandler if you want your control to be event-aware. In other words, a control that implements IPostbackEventHandler will receive the event that is raised when the user interacts with the control. If the control doesn't implement IPostbackEventHandler, the control will be ignored when the event is fired, and the control's parent, or the next control up the hierarchy that implemented IPostbackEventHander, will receive the event.

If this is as clear as mud, here's an example. Our custom control contains embedded buttons. Because those buttons are ASP.NET buttons, they implement IPostbackEventHandler. That is to say, when a user clicks an ASP.NET button, the event is sent to the ASP.NET button, which has a chance to respond to it. In the case of the ASP.NET button, the control does indeed respond to it. The response is to raise the Click event, which a developer, like you and I, can trap in our code.

So, in our custom control, when our embedded buttons get rendered, the fact that they already implement IPostbackEventHandler means they will trap the user's click, and we therefore don't have to implement IPostbackEventHandler in our custom control. We simply need to take advantage of the fact that the button has trapped the event for us and raised the Click event. We do this by wiring the Click event to our desired action, as we saw above...

DeleteBtn.Click += new EventHandler(DeleteBtn_Click);

... and doing our custom action...

private void DeleteBtn_Click(object sender, 
                              System.EventArgs e)
{
    if( !IsRootSelected() )
    {    
        Delete();
    }
}

So when would you need to implement IPostbackEventHandler? You would need to implement IPostbackEventHandler if you want your control to respond to events, and the component or HTML that's generating the JavaScript to cause a postback does not itself implement IPostbackEventHandler. Let's say for instance that instead of adding an ASP.NET button control to the control hierarchy, you simply wrote the HTML directly to the response stream, like this:

protected override void Render(HtmlTextWriter output)
{ 
  output.Write(
    "<a href='" + GetPostbackEventReference() + "'>Click me</a>) 
}

In this case, when the href is clicked, the reference to your control will be made through GetPostbackEventReference and the fact that your control implements INamingContainer, but the event won't actually be sent to your control to handle unless it implements IPostbackEventHandler.

Conclusion

Developing custom controls that embed other custom controls can be tricky until you understand the mechanisms of control instantiation, rendering and event wire up. The available documentation in general doesn't seem to provide clear answers and easily followed guidelines. In developing our first major commercial custom control, we worked through a number of issues and in the end, were pleased to find that if things are organized, the .NET Framework not only makes sense, but does a lot of the plumbing for you, like managing ViewState and sending events along the correct path.

This article has not attempted to provide a comprehensive guide to control development, but rather a simple reflection on our own experience and some examples of how we got things working well. I hope it saves you some time in your own custom control development.

Additional help

I'm very interested in helping anyone who wants to build a custom control. If you require more examples or source code, although I can't supply the full source code for the commercial control, I can certainly supply large pieces of it. Just let me know where you're having problems and I'll be glad to try to help as best as I can!

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralNeed Code
Ratz Pilz
5:10 19 Mar '10  
Hey Nigel,

Great Work Sir, It is a great article.

It would be helpful if you can post of Mail me the Code

Thanks

Ratish
GeneralWeb File Manage Control Code needed
deldin
4:02 19 Mar '10  
Dear Nigel,

It was a nice article, I am building a control similar to yours, It will be grateful if you can send me the code.

THanks,
Eldin
Generalawesome article.
tzeis
17:23 19 Feb '10  
This is an awesome article. I’m an amateur programmer and most of what was said is over my head. I learn best by taking working code and stepping though it line by line to figure out what it’s doing. Would you please send me a working example of this. I am using Visual Web Developer 2008. My email is tszeis@moldmaker.com

Thank you.
GeneralCan i get some code on event wire up
jomet
2:39 12 Jan '10  
Hi Nigel Shaw,

Thanks for this excellent article.
I am working on a gridView customization. But i am still confused in managing events. please send me some code.

email: jomet4u@gmail.com
Thanks,
Jomet
QuestionExcellent, could you send me the code please?
Member 2277772
23:40 25 Nov '09  
An excellent article!

Please send the code to robert_yardley@next.co.uk
GeneralThanks for posting this article
rclabo
12:40 6 May '09  
Thanks for the article. This was just the info I was looking for today and it was a great help. Thumbs Up

Much appreciated,

-Ron
GeneralCode Please
Shaka913
11:33 28 Apr '09  
Nigel, looks great and helps answer one of my questions, but i am still not getting desired results. please post the code somewhere public or email me: shaka913@hotmail.com

thanks again
GeneralMy vote of 1
kckn4fun
7:54 19 Apr '09  
Source code not attached to article.
GeneralAwesome
drummr85
11:58 2 Apr '09  
You sir, are a god send. I have been banging my head against the wall for 2 days trying to figure out why the events weren't getting trapped by my control and trying all kinds of tricks. Why no one ever mentions simply having to implement INamingContainer is beyond me.
GeneralCode please
Tici
22:50 29 Mar '09  
Can you send me the code: blevai@decnet.ro
Thanks in advance!
GeneralCode Please
aureolin
14:22 13 Feb '09  
to: aureolin@hotmail.com

Thanks!
GeneralCode Please
btwestmo
10:22 6 Feb '09  
Thanks for the excellent article. I am learning alot and ask if you could please send me the code to better understand.

Thanks,
Ben
btwestmo@gmail.com
GeneralPls send me the code.
Member 2045042
11:17 7 Jan '09  
Thanks for writing the article! I started to write a similar control with different application and would be great to see your code. Please send me the source code to vik.dotnetprodigy@gmail.com


Thanks!
QuestionSource code
Dreyola
2:01 10 Dec '08  
Thank you for this wonderful article - it is exactly what I was looking for, but I would really appreciate some source code.
The example I'm seeking is the one where you wire the events.

Also I'm still a bit confised about viewstate not working with my
TextBox : System.Web.UI.WebControls.TextBox
If I just drop it on the page - ViewState works.
If I enbed it into my control that simply calls CreateChildControls() - ViewState is gone (I will read the article yet one more time Smile )

The email is dreyola@yandex.ru

Thank you.

/Andrey
GeneralThank you!
mikeg18
16:24 1 Dec '08  
I created a custom checkbox control, and was going notes because the new properties were not storing their values on postback. Your simple explanation on ViewState, helped me out.

Only problem I have is that I didn't figure it out before finding your article.

Thanks again,

Mike
Generalplease could i have the code too?
kindaska74
6:36 21 Oct '08  
kindaska@yahoo.com
thank in advance..
GeneralSource Code request
mwyrsch
8:26 13 Oct '08  
Could you please send me also the source code to marco@pocketpc.ch ?

Thank you very much for this great work!
GeneralPlease send me the source code
Hasan_Abbas
19:27 28 Sep '08  
Hey,

Please send me the code at hasanabbas19@hotmail.com

Thanks.

HASAN ABBAS

Generalcould you send me the source code?
mailofliujin
6:54 13 Aug '08  
It looks great, thank you.
GeneralVB Developers
cyber-will
3:38 30 May '08  
Just remember the reference to the resource is case sensitive. Assembly.Filename won't work if your file is name filename. Hope this will prevent somebody else from spending hours trying to figure it out. Smile
Generalwhere is the code?
dsmportal
10:23 23 Mar '08  
do you mind uploading the source code? so others can benefit?
thanks.
GeneralSource Code re: TreeView
RoyMcD
8:13 18 Mar '08  
Nigel,
Could you send me some source code showing how you manage your TreeView?
Reading your article, it sounds like I just need to re-instantiate the TreeView on each postback, add it to my WebControl ('this') in CreateChileControls, and the TreeView will be automatically restored to its previous state from viewstate. But this is not working for me. Some source code would be very helpful.
Thanks a lot.
Roy
GeneralRe: Source Code re: TreeView
Nigel Shaw
3:21 19 Mar '08  
No problem Roy. I sent the code along. If anyone else wants code, just ask.

Nigel

Nigel

GeneralRe: Source Code re: TreeView
Nigel Shaw
6:48 21 Oct '08  
Hi Roy, this is pretty embarassing but I've lost the original source code. Would you mind sending me back a copy of the one I sent you so I can forward to the others. Thanks.

Nigel

GeneralTHANK YOU
Stephan Grieger
17:52 12 Feb '08  
Man this was driving me insane but this simple article cleared it all up so I say again, THANK YOU!


Last Updated 29 Sep 2005 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010