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.