When Templates Suck

My ExpandingPanel control is a templated control. It isn't a repeating databound control like DataGrid or anything, but it's templated because it has two "views", and the easiest way to do that was, it seemed, to make each view a template.
 
This has caused me quite a few emails over the months it has been around, because it is very confusing for those who are trying to create the control from codebehind, and not declare it in an aspx. The reason for this is because asp.net does a lot of behind-the-scenes magic when templated controls are parsed and compiled. Every template you declare on the page becomes its own class that implements ITemplate, and that class's InstantiateIn method is called when the containing control needs to create the template for rendering and such.
 
Most developers have no idea that this is going on, and send me emails asking why ExpandingPanel1.Controls.Add(myControl) doesn't work. I generally try to explain to them that they have to create a new class that implements ITemplate, implement InstantiateIn to add the controls they want, and then instantiate a new instance of that class for the templated property of the control. One for each template in the control. At that point, the developer either goes, "OK, got it" and does their thing, or goes "Wa huh?", stops using ExpandingPanel, and handcodes their own implementation of the control so they understand whats going on.
 
And then I get the email from the "OK I got it" guy when he tries to figure out how to do databinding. OOOOF. This is not fun. Most people just use that Container.DataItem property without even a small understanding of where that hell that "Container" object comes from, or what "DataItem" refers to, other than, "that seems to magicly hook up with my data".
 
Now he has to understand that "Container" is actually a code-generated property that refers to the individual row/item in the outside control. So, for DataGrid, it's a DataGridItem. for Repeater, it's a RepeaterItem. etc. But ExpandingPanel has no ExpandingPanelItem, it's just the ExpandingPanel control itself. And here's the kicker, Container only exists for autogenerated templates, and you have to implement it yourself in the ITemplate class. And there's nothing in the vanilla sdk that tells you how to do this afaik. BAH.
 
Sure, I know how to do it, it's generally the NamingContainer of the control you get sent to instantiate into. But why the hell does the page developer have to know this arcane stuff?
 
I think templates are a great idea, and they are great for the visual designer -> asp.net runtime story. They suck for the codebehind guy. why does he need to make an entire class and implement some non-documented stuff, just to add controls to a container?
 
And then, as usual, I came up with a great idea. :) I figured... I can't really do anything about the way templating/ITemplate works. I need to have an object instance that implements ITemplate. However, what I noticed is that almost all custom implementations of ITemplate do the same thing. How about a RuntimeTemplate class that can be used genericly for these ITemplate Control properties? RuntimeTemplate has an Instantiate event. When the containing Control calls instantiate on its ITemplate property, RuntimeTemplate simply raises its event.
 
This is obviously not a complex class. In fact, it's about 15 lines of code. but it changes the "create a new class, implement ITemplate.." story to handling an event like so:
 
    void Form_Init( Object sender, EventArgs e ) {
  Repeater1 = new Repeater();
  Form1.Controls.Add( Repeater1 );
  
  RuntimeTemplate template = new RuntimeTemplate();
  template.Instantiate += new InstantiateEventHandler( template_Instantiate );
  Repeater1.ItemTemplate = template;
  
  
        if ( !IsPostBack ) {
            Repeater1.DataSource = new String[] { "one", "two", "three" };
            Repeater1.DataBind();
        }
    }
 
    void template_Instantiate( Object sender, InstantiateEventArgs e ) {
  CheckBox InnerCheckBox = new CheckBox();
  InnerCheckBox.ID = "InnerCheckBox";
  InnerCheckBox.DataBinding += new EventHandler( InnerCheckBox_DataBinding );
  
  e.Container.Controls.Add( InnerCheckBox );
    }
   
    void InnerCheckBox_DataBinding( Object sender, EventArgs e ) {
  CheckBox InnerCheckBox = sender as CheckBox;
  RepeaterItem container = InnerCheckBox.NamingContainer as RepeaterItem;
  InnerCheckBox.Text = container.DataItem.ToString();
    }
 
"But Wait", you might say, "you didn't really fix the databinding/container thing!" Ya, sorry, I haven't figured out a good way around that yet. I'll let yall know when I do.
 
 
Anyway, look for this class on metabuilders.com sometime this week.

2 Comments

  • Sweet. I've been thinking writing this exact class myself. I'm writing a templated control that I want to support a "default template" - that is, a control that supports templates but can render a sensible default look and feel without forcing the user to write a template declaration. This'll make it a piece of cake.





    I don't know why the framework didn't include this class in the first place. Think of all the newsgroup postings that could have been avoided ("How do I dynamically create template column in the datagrid") if they had. :)

  • I couldn't agree with you more. Microsoft even recommends programatically creating template columns (I just wrestled with a simple checkbox control in a datagrid). I wrote a custom class to handle it, but thought about writing a generic one too, for future reference. I thought I'd google it first, and came across this page.



    I appears to me that if three developers (including Kevin Dente's post) thought the exact same thing, that MS should have provided this functionality some time ago. Maybe they will in future releases.

Comments have been disabled for this content.