It turns out that creating a custom server control that uses dynamic
controls (controls added at runtime) is a huge pain in the ass. Add to
that INamingContainer, which is virtually a necessity when making a
composite control (it lets you have more than one copy of the server
control on the page by guaranteeing that the child controls have unique
id's) completely screws around with control's life-cycle, and you have
one frustrated developer..
It's taken me entirely too long to create my data-paging server
control, which simply creates the buttons you need to page through data
(First, Prev, 1, 2, 3, Next, Last - etc) and provides an event for when
the user clicks a button. It doesn't do the actual data-paging, just
the controls so it should have been a simple control to write. Nope, I
had to try just about every trick and lame technique in the book to get
it to work the way I want. Along the way I tried such things as
serializing out the settings to a hidden field (essentially creating my
own custom viewstate), so that I could re-instantiate my controls
before the events are supposed to fire. Turns out that way doesn't work
so hot if you need to hide the control for whatever reason (hidden
panel, etc), since the hidden field won't get included in the page. I
also tried to tap into virtually every part of the control's
life-cycle, which ultimately useless, turned out to be good for learning
how things worked, inserting hidden fields, or creating and re-creating
controls at different points.
Finally I have something that works, though it's not without it's share
of shortcuts. I ultimately borrowed some idea and code from Dennis
Bauer's DynamicControlsPlaceHolder, to re-create my dynamic controls
out of the viewstate on a postback. The downside to this technique is
that it doesn't re-wire the events properly, and even worse, you loose
command arguments (which is what I used to determine which page button
the user clicked on). So I had to work around that by storing some
extra info into the persistenceString (a string that is passed along
that tracks which controls to restore).
So once the control is loaded, I override CreateChildControls to create
the final controls the user sees. I do this by calling
this.Controls.Clear() to wipe out the controls that were created by
from the viewstate (which could be different depending on which page
the user clicked on). It's not great, but it's much easier and probably
faster than comparing the new controls that will be generated to the
existing ones and adding or removing ones that don't match up.
It would be nice if there was some way to detect which button was
clicked so I could only restore that control to capture the event, but
I don't know if that's possible. So it now works with only
instantiating the controls a maximum of twice
per page load, which is an improvement over earlier versions.
All of the leaves my final workaround for INamingContainer. As
mentioned above using INamingContainer causes the control life-cycle
events to be called in a different order. This meant that my
CreateChildControls was being called after my event was firing, the
event which set the current page that the CreateChildControls relied
upon. That was a problem. I could have forced a call to re-create the
child controls after my event fired, but that would be adding a lot of
overhead to an already inefficient system by having to re-create the
controls 3 times instead of two.
So I ended up manually setting all of the control id's so that they
used the parent control's ClientID. This was essentially what
INamingContainer did, sans the odd life-cycle changes. It wasn't as big
a deal as it could have been since I had to explicitly set the control
id's for the DynamicControlsPlaceHolder code to work anyway.
It's not pretty, but it works. I can put as many pagingcontrols on a
page as I want (say at the top and bottom of a repeater), even hide
them and it all works. I can't imagine having to do anything more
complex with dynamic controls in a server since this one was such a
pain. On the upside I learned many of the finer points of creating
server controls and the next one I write should go a little more
smoothly.