August 2003 - Posts
Doug Reilly recently blogged about his problem of running into the v1.1 __doPostBack bug, mentioning that my version of MasterPages isn't affected by it. I really debated about including my own little bugfix into the distribution, but after some consideration, I decided to include it. Microsoft has a fix for the bug, but it's a bit of a hassle to get it, calling phone numbers and such. So a few 3rd-parties have released their own fixes to this bug, usually regex or something to alter the output of pages to change the __doPostBack script itself. I looked at the problem from the other way, changing the behavior of the HtmlForm control so it doesn't expose the bug. Basicly, because the name of the form has a colon in it from being in a naming container, it's no longer a valid jscript identifier. I figured, hey, if I just make the name attribute not have a colon, that fixes the problem too... no need to futz with generated script. This way, there is no problem with using my masterpages with either patched or unpatched systems. I wrapped the idea in a NoBugForm control, which I include in my version of masterpages. It derives from HtmlForm, so everything in the page framework functions just like it should. Simply replace the form tags with NoBugForm tags.
I started writing a simple little post with tips on effective use of clientscript with custom controls, but it turned into an essay on the subject, so I made it an article. In it, I go over what I consider to be best practices in the area. And before you read that, look at the code I have on metabuilders, and say “AH HA! You Don't Follow Your Own Advice Half The Time!”... well ya, I wrote a lot of code and produced a lot of controls to get where I am today. The older code works, so I don't futz with it. Anyway, I hope you enjoy the article.
Lets say you wanted to make a textbox that was really ugly. So, hey, lets make the BackColor green. That's plenty ugly. So you do this:
public class UglyTextBox : TextBox {
public override Color BackColor {
get { return Color.Green; }
set { }
}
}
Then you realize, that hey, somebody using my control might think of an even UGLIER backcolor, so you just want to make it the default BackColor, so you quickly change it to this:
public class UglyTextBox : TextBox {
public UglyTextBox() {
BackColor = Color.Green;
}
}
Little do you realize, you've actually commited a c# sin. Thou Shalt Not Call Virtual Members From The Constructor. The reason for this is explained fairly well in this develop.com thread. Basicly, things go bad if a subclass overrides the virtual method you call in the constructor, and uses variables created in its constructor, because your constructor is called before its constructor. So, you might think you can do this:
public class UglyTextBox : TextBox {
protected override void OnInit(EventArgs e) {
BackColor = Color.Green;
base.OnInit(e);
}
}
And while this will work at runtime, it won't work at design time. The Init event doesn't happen in the designer. None of the events do. The controls are simply constructed, properties from the tag are set, and then hit RenderControl. So for full design time support of a change in default property value, this is the best solution I've come up with so far:
public class UglyTextBox : TextBox {
[DefaultValue(typeof(Color),"Green")]
public override Color BackColor {
get {
if ( BackColorSet ) {
return base.BackColor;
}
return Color.Green;
}
set {
BackColorSet = true;
base.BackColor = value;
}
}
private Boolean BackColorSet = false;
}
Meh. Does that seem overly complicated to anybody else?
While in the proccess of making a small data-massaging winforms app, I got to thinking about persistence in winforms. Almost all “real” applications have the ability to remember certain things between sessions. Most commonly, form size and location, as well as remembering some textbox values and such. These things aren't what you'd generally call “application options”, they are just those things that people think, “it should still be in the same state as when I left it”. Well, people wouldn't actually think those exact words... more like, “why the hell does that window keep popping up in random places.” or “do i really need to fill out this textbox every single time?”
And then I got to thinking about why I seperate these kinds of properties from more the properties that I consider application options. It seems to me that it comes down to the idea that I generaly consider options to be nerd knobs that change how the application works, while form state just changes the way it looks. I'm not sure why these things are so seperate in my mind. It all comes down to some object having a property that is persisted to an external format so that the values aren't lost when the application is shut down. That's the theoretical point of view.
In the practical world tho, options are different to deal with than UI stuff. I already have an options storage pattern I use when I need to do that stuff. I use a global options class that I store in IsolatedStorage via xml serialization. For changing options, I use that property editor box ( whatevertheheck that thing is called ) to edit the global instance. You see, that form just exposes the “real” options, and my code just talks to the options class to get the important stuff.
That pattern is kind of awkward when dealing with options that are more UI centered, such as form size and textbox values. If were handling a button click that dealt with a persisted textbox value, it seems more natural to talk to the TextBox itself, not the options class, to get/set the value. So now it's the textbox that is the real authority, and the fact that it is persisted is secondary. You see how this is opposite of my traditional view of options? I don't query the options form to get the values of the property box... I just talk directly to the class facade over the storage mechanism.
So how do I resolve this?
Obviously, I'd like the best of both worlds. Keep my current natural coding mechanisms, but use the same persistance framework for everything. I think the solution lies in making some kind of two-way databinding shim between controls and the persistance framework. I was thinking that it should be some component I can drag onto the form that exposes whatever databinding interface is required so that I can edit the databindings of my controls and just point whatever properties I want to store at my component. Now, I don't know a whole lot about how the winforms databinding stuff works, but from my minimal experience there, it seems like it should work.
If anybody knows of a project already out there that does this, I'd be interested in hearing about it.
More Posts