March 2003 - Posts

Pardon this obligatory War Blog entry...

I just heard an interesting comment on NPR:  more Americans have died in car wrecks, this week, than have died in Iraq.

I've long held the notion that if aliens came to visit this planet, they'd take one look at the way we line up to get into large steel cages and drive, head on, towards each other at breakneck speeds -- with only a thin painted line and a few inches of air between us and certain destruction -- then classify us one step below the Lemming, as the planet's most hopelessly self-destructive species.

-S

Rant of the Day:  What the heck is up with StreamReader, TextReader, and BinaryReader?

Am I the only person who thinks this little (but heavily used!) corner of the FCL got, like, zero peer review before .NET shipped?

1)  Why does StreamReader derive from TextReader?  Shouldn't it be the other way around?  And why does BinaryReader derive from neither?

2)  Speaking of inheritance, why are Stream/TextReader (and derived classes) MBRO's, while BinaryReader is not?

3)  Why are TextReader's Peek() and Read() methods typed to return 'int', rather than 'char'?  It's a TextReader, after all -- heck, ReadToEnd() returns a string!

3b)  BinaryReader has a PeekChar() which returns 'int', and a ReadChar() which returns... wait for it... 'char'.  WTF?

Ugh.  Either I'm missing something big, or MS needs to start drug-testing.  I hope it's the former, but fear it's the latter.

I've been tasked with writing a custom install-action, to add a few user controls to the VS.NET toolbox.  "No problem", I said.

Word? Schmurd. This VS.NET ToolBox can o' worms has turned out to be a new low in application-integration migraine severity...


There exists an innocent little API function, ToolBoxItems.Add(), in the EnvDTE assembly.  Of course, its documentation is a masterpiece of misinformation...

I Google'd long and hard, and all I could scrape up was anecdotal evidence from various folks that it was possible -- somehow -- I just had to work around a few quirks.  What those quirks were, exactly, and how to workaround them...?  Not much consistent info to go on.

All I knew for sure was that calling ToolBoxItems.Add, with every reasonable combination of typename and assemblyname for parameters, didn't work.

Some folks said I have to index the tabs by number (what?), some folks said I have to load a Solution file first (yikes!), and some folks said I have to display the Properties window first...  Unlikely as it sounds, that latter suggestion (plus or minus a few other non-intuitive stumbling blocks) turned out to be the case!

Think: EnvDTE.DTE.ExecuteCommand("View.PropertiesWindow");

There is clearly a very deep-running bug in VS.NET, regarding this Properties window.  I've noticed it flashing briefly into existance, quite often, even when I have it hidden -- I somehow suspect that this "workaround" is well-known within the halls of Building 40. Grrr...  Hopefully this is fixed in Everett, but of course I can't afford to care about that.

So, without further ado, here is the magic sequence to add InkPicture and InkEdit, from the Microsoft Tablet PC SDK, onto the VS.NET Toolbox.  This code can be run from any standalone app -- it does not need to be run from within an addin.  (In fact, all instances of VS should be closed.)  Hope this helps somebody...

        private static void AddToolBoxTab(EnvDTE.DTE env)
        {
            // First, ensure Tablet SDK is installed -- get location of Microsoft.Ink.dll
            string fullpathToMicrosoftInk = LookupMicrosoftInkAssemblyFilename();

            // Prepare to munge the toolbox -- get toolbox window, object, and tabs collection
            EnvDTE.Window toolboxWindow = env.Windows.Item(EnvDTE.Constants.vsWindowKindToolbox);
            EnvDTE.ToolBox toolbox = (EnvDTE.ToolBox)toolboxWindow.Object;
            EnvDTE.ToolBoxTabs toolboxTabs = toolbox.ToolBoxTabs;

            // Careful to check if our tab already exists
            foreach (EnvDTE.ToolBoxTab tab in toolboxTabs)
            {
                if (tab.Name == TabName)
                    return;
            }

            // No, so add it
            EnvDTE.ToolBoxTab newtab = toolboxTabs.Add(TabName);

            // WinBug: gotta show the Properties window, first (this cost me ~1 day of my life)
            env.ExecuteCommand("View.PropertiesWindow""");

            // WinBug2: gotta activate the tab and select the first item (grr...)
            newtab.Activate();
            newtab.ToolBoxItems.Item(1).Select();

            // Finally: add the Microsoft Ink controls
            newtab.ToolBoxItems.Add( 
                @"Unused?"
                fullpathToMicrosoftInk, 
                EnvDTE.vsToolBoxItemFormat.vsToolBoxItemFormatDotNETComponent
                );
        }

        private static string LookupMicrosoftInkAssemblyFilename()
        {
            Microsoft.Win32.RegistryKey rk = Microsoft.Win32.Registry.LocalMachine;
            rk = rk.OpenSubKey(KeyName, false);

            if (rk == null)
                throw new ArgumentException(String.Format("Could not find registry key:\n[HKLM\\{0}]",KeyName));

            string fullpathToAssembly = (string)rk.GetValue(null);
            fullpathToAssembly += @"\Microsoft.Ink.dll";

            if (!System.IO.File.Exists(fullpathToAssembly))
                throw new System.IO.FileNotFoundException("File not found", fullpathToAssembly);

            return fullpathToAssembly;
        }

 

I can't remember the last time I've been handed source code for a Form- or UserControl-derived class that didn't include a handler for its own Load event.  Why is this?  Sadly, I know the reason:  the VS.NET forms designer makes it all too easy to do -- almost by accident -- by double-clicking on the form in design-view.  This is very unfortunate.

// BAD!
private void Form1_Load(object sender, System.EventArgs e)
{
    this.label1.Text = "This code is lame!";
}

Please, everybody:  avoid subscribing to your own events.  The event-dispatching mechanism in .NET is not expensive, but it's not free.  It's much more efficient to do whatever it is you need to do upstream of the code that fires the event -- in that way, if there are no other subscribers, then there is no more work for the runtime to do.

In the case of the UserControl/Form.Load event, this means overriding the base class's OnLoad virtual method:

// Better...
protected override void OnLoad(EventArgs ea)
{
    // Remember to call base implementation
    base.OnLoad(ea);

    // Now do our business
    this.label1.Text = "This code is less lame.";
}

Remember, when overriding virtual methods in winforms, it's almost always a good idea to call the base class.  (Somewhat ironically, it's the base class's implementation of OnLoad that actually fires the Load event.  So if you forget this line, any extant parties interested in your Load event won't get notified.)

I realize the OnLoad override may seem like a lot more typing -- but trust me, taking time to care about such subtle points is far better than sitting up late the night before a deadline, wondering why one's app is behaving so strangely...  what am I talking about?

In the case of the Load event, the issue not just performance, but one of functionality.  You see, OnLoad is not called until the form/control becomes visible.  That's a subtle point that can make your code path become very complicated, very quickly.

Consider that, typically, the form becomes visible immediately after the form's constructor -- so the code you put in your Load event handler seems to have the same effect as code placed at the end of your construct (eg, below the //TODO line):

public Form1()
{
    //
    // Required for Windows Form Designer support
    //
    InitializeComponent();

    //
    // TODO: Add any constructor code after InitializeComponent call
    //
}

Now, consider my last WinForms Tip-of-the-day (ok, it was almost a month ago), regarding initially-invisible forms.  In that scenario, your OnLoad code may not be executed until some significant time has elapsed, after the form class's construction.  Maybe that's what you want.  Maybe not.

Alternatively, what if you've set the form's Visible property to 'true', in the designer?  Your form will become visible (and thus, the OnLoad method will be called) within the scope of the constructor.  Danger! Danger!  This completely changes the semantics of the method.  At the time of the call to this.Visible=true, the designer-generated code likely hasn't even hooked up your event handler yet -- so your event handler code won't get run at all!

All this nonsense makes it very difficult for folks reading your code to determine what the intended semantics of your OnLoad method are -- or even if it's getting called at all.

So please, never subscribe to your own Load event, and only override OnLoad if you have load-time code that truly doesn't belong in the constructor.  (This case is rare, but not unheard of -- examples include rigging timers for splash screens, and that sort of thing.)

Then adopt this rule into your .NET programming lifestyle, and avoid subscribing to your own events, in the general case.

More Posts