spout

November 4, 2004 spout

It’s The Thoughts That Count…

On an internal mailing list the other day, there was a question asking for how to make a tray notification icon come back up on the try after explorer.exe died and came back to life. Good notification icons do this and the asker wanted to know how to do it for Windows Forms. I had had a dearth of technical work at the time, so decided to dig into the problem and see if I could answer it.

I started with a dim memory of a piece in MSJ by Paul Dilascia on the subject. Searching on msdn.com, I found Paul DiLascia’s 2/99 MSJ C++ Q&A (http://www.microsoft.com/msj/0299/c/c0299.aspx) (emphasis added by me):

“provided you have Windows 98 or the Microsoft Internet Explorer 4.0 desktop installed. Whenever Internet Explorer 4.0 starts the taskbar, it broadcasts a registered message TaskbarCreated to all top-level parent windows. This is your cue to recreate the icons. If you’re using MFC, all you have to do is define a global variable to hold the registered message and implement an ON_REGISTERED_MESSAGE handler for it.”

From there, I dug through the SDK docs on ON_REGISTERED_MESSAGE (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vcmfc98/html/_mfc_on_registered_message.asp) and found:

// example for ON_REGISTERED_MESSAGE
const UINT wm_Find = RegisterWindowMessage( FINDMSGSTRING )

BEGIN_MESSAGE_MAP( CMyWnd, CMyParentWndClass )
//{{AFX_MSG_MAP( CMyWnd )
  ON_REGISTERED_MESSAGE( wm_Find, OnFind )
// … Possibly more entries to handle additional messages
//}}AFX_MSG_MAP
END_MESSAGE_MAP( )

Figuring I’d have to be able to call RegisterWindowsMessage from managed code, I surfed to pinvoke.net and found RegisterWindowMessage (http://pinvoke.net/default.aspx/user32.RegisterWindowMessage):

[DllImport(“user32.dll”, SetLastError=true, CharSet=CharSet.Auto)]
static extern uint RegisterWindowMessage(string lpString);

Now that I knew how to figure out the Window message value for which to watch, I looked back into the MSDN documentation on System.Windows.Forms.Control, the base class of Form and all other HWND-based classes in Windows Forms (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemwindowsformscontrolclasswndproctopic.asp) and the WndProc method I would have to override to catch the message, finding:

protected virtual void WndProc(
ref Message m
);

and:

“All messages are sent to the WndProc method after getting filtered through the PreProcessMessage method.

“The WndProc method corresponds exactly to the Windows WindowProc function. For more information about processing Windows messages, see the WindowProc function documentation in the Windows Platform SDK reference located in the MSDN Library.

“Notes to Inheritors: Inheriting controls should call the base class’s WndProc method to process any messages that they do not handle.”

Putting this together, I figured you could add “re-awakening” to notification icons in the following way (some compiler errors left in to keep readers on their toes : ):

using System.Windows.Forms;
using System.Runtime.InteropServices;

class MyMainForm : Form {
  [DllImport(“user32.dll”, SetLastError=true, CharSet=CharSet.Auto)]
  static extern uint RegisterWindowMessage(string lpString);

  static uint taskbarCreatedMessage = RegisterWindowMessage(“TaskbarCreated”);

  protected override void WndProc(ref Message m) {
    if( (uint)m.Msg == taskbarCreatedMessage ) {
      // re-show your notify icon
    }
    base.WndProc(ref m);
  }
 
}

After posting this answer to the list, I couldn’t help but crank up a quick app to test it (after fixing the compiler errors of course : ). To my satisfaction, it worked immediately. I put up a notify icon in Windows Forms, killed explorer.exe and was pleased to see my icon show back up again when explorer.exe was restarted.

However, just to make sure, I commented out my code to see my icon not be restored. Those of you familiar with the internals of Windows Forms know what happened next, of course, because my notify icon was restored properly to the tray even without my code, as illustrated by Reflector against the .NET 1.1 Windows Forms implementation:

public sealed class NotifyIcon : Component {
  …
  static NotifyIcon() {
     NotifyIcon.WM_TASKBARCREATED =
      SafeNativeMethods.RegisterWindowMessage(“TaskbarCreated”);
  }

  private void WmTaskbarCreated(ref Message m) {
    this.added = false;
    this.UpdateIcon(this.visible);
  }

  private void WndProc(ref Message msg) {
    …
    if (msg.Msg == NotifyIcon.WM_TASKBARCREATED) {
      this.WmTaskbarCreated(ref msg);
    }
    …
  }
  …
  private static int WM_TASKBARCREATED;
  …
}

What? Why would someone post a question about how to make something work that already worked? I figured that the questioner was referring to .NET 1.0 and I was digging through .NET 1.1 code.

However, reading through the 1.0 code showed the same support, meaning that neither the questioner nor I had bothered to check for this support before running off to add it. So, the lesson? Write your tests first!

Still, I learned a bit along the way and that was fun. : )

October 25, 2004 spout

Here’s Some Marketing I Can Get Behind

I just got an email from Netflix letting me know that they’re lowering my monthly fee from $22 to $18. The only thing vaguely "marketing" about is that they used $21.99 and $17.99. There was no upsell. There was no limitation of service at that new lower price. There nothing additional I had to do to get the savings. When's the last time you got that kind of service from a vendor? I love Netflix.
October 14, 2004 spout

Consider Yourself An Artist

d.code has a lot of great stuff to say in his post, but hands down my favorite is this:

“How many software developers at Microsoft consider themselves artists first, and software developers second?”

This is the thing that separates today’s Windows software from tomorrow’s. IMO, this is the line between Windows Forms and Avalon. The former is hands down the best way for software engineers to build professional UIs that we’re familiar with under Windows today. The latter is for artists to build things we’ve never seen before. I know this is scary for software engineers who don’t know how to be artists (lord knows I don’t) and for companies that don’t have artists (better get some), but crossing this line is necessary to get from the best of today to the promise of tomorrow for Windows.

October 14, 2004 spout

Randy Jackson on Pleasing the Gods

Randy Jackson enjoyed my recent .NET Rocks appearance because of my discussion of how I like to learn things. I only remember talking about two strategies: “ignoring the docs” and “hyperventilating” but his take on it was so beautifully worded, I had to share it with you:

“I’m referring to flying in that strata called by some of my PhD’ed friends as the ‘Shirley McLean’ possession experience, where the actual act of jumping in to the unknown pleases the Gods in some twisted way and the outcome is usually beyond our expectations.”

I don’t get the Shirley McLean reference, but I love the bit about pleasing the Gods by jumping into the unknown.

And yes: today I’m going to read you my email because today, clever people insist on emailing me. : P

October 14, 2004 spout

A Quote To Live By

I watched the first few episodes of the award-winning HBO series “Angels in America” and hated it (but I still love NetFlix for making is so darn available to me).

However, while I didn’t identify with any of the characters (except, potentially, the hot nurse/angel), I did really resonate with one of the quotes, which went something like this (and feel free to correct me if you recognize the quote and I got it wrong):

“Don’t run your life by what you want. That changes with a whim. Run your life on what you believe.”

That really struck the Midwestern Eagle Scout idealist engineer in me.

October 11, 2004 spout

“How is anyone supposed to know that?”

The high priests that run the Internet and the IT industry have betrayed the trust of the secular community.

My wife called me over to her computer the other day when she was worried about an email stating a problem with her eBay account. By reflex, I said, “Oh, just ignore that.”

“What? Why? They’re going to shut off my eBay account,” she said, motioning me over more urgently this time.

Looking over her shoulder, I saw an email that looked very official, including the eBay logo.

“See?” she pointed at the From line. “It’s from ebay.com.”

“I know, sweetie, but hang the mouse over the link they want you to click. It’s a number; it’s not ebay.com”

Looking at me like I’d just broken the news about Santa Claus, “But how is anyone supposed to know that?”

I didn’t have an answer. There really should be one.

October 11, 2004 spout writing

All The Fun, Half the UK Price

Curt Johnson, my marketing guy at Addison-Wesley, has let me know this morning at the UK Amazon is having a 1/2 price sale on my Windows Forms book this month. Plus, the UK guys have their own reviews that are just as nice as the US reviews. Who knew? : )
October 7, 2004 spout

Settings, Collections and VS05b1 (oh my)

I sat down to write a new Windows Forms application in .NET 1.1 the other day, but the Visual Studio 2005 beta 1 called to me with its “menu strips” and its “user settings” and most especially its “generics,” and I just couldn’t resist.

To start with, it was such a pleasure to add a menu strip, add in the standard menu items (including graphics), then just strip it down to just the menu items I wanted. Then, as I added new menu items, the menu item object names were set for me based on the menu item text, e.g. startRetrievalToolStripMenuItem instead of menuItem1, which was fabulous. Not everything was wonderful, e.g. I couldn’t drag and drop menu items or use Alt+arrows to rearrange them , but overall the new menu control was a pleasant experience.

Even more pleasant was the app/user settings model. To add a setting to my application, all I had to do was bring up the properties of my project, add a named setting of whatever type I wanted (more on this later) and choose whether it should be an Application setting or a User setting. Both kinds of settings are loaded automatically when the app starts and all I had to do to save them was call Properties.Settings.Value.Save() when my main form shut down. Then, with the settings in place, e.g. MyUserSetting, I could get to it after the app started from anyone in my app with a type-safe access, e.g. Properties.Settings.Value.MyUserSetting.

And this didn’t just work for built-in simple strings and ints and such like. Oh, no. I was allowed to add custom and collection types like System.Collections.Generic.List<MyNamespace.MyType> as well. Plus, using generics, the underlying XML serialization mechanism worked great, because all of the types are known at compile-time (settings are stored in standard XML .config files @ c:\documents and settings\<user name>\Local Settings\Application Data\<company name>\<product name_hash>\<product version>\user.config). Being able to declaratively set app and user settings of any type and then just use them in a type-safe manner, saving them with one line of code, loading them with zero lines of code and not having to flatten my collections into comma-separated lists made things so pleasant until I hit the ugly realities of beta software, especially as new features interact with existing features and each other.

For example, because the default AssemblyVersion attribute is set to “1.0.*” in AssemblyInfo.cs (which has moved to below the Properties folder of your VS05 project), every time I compiled, all of my settings were lost. That seems very counterintuitive to me. Why should a user lose all of their settings with the new version of the application? To work around this, I changed my AssemblyVersion attribute to a hard-coded string that I now have to remember to change manually, blowing the benefit of having a version number that changes automatically with each build.

As another example, like C++ “const” of old, generics infect your code; use them in one place and you find yourself using them all over. That was fine with me (the generic Predicate<T> for finding things in a List<T> was so handy!) except that as of b1, the Windows Forms Designer gets all unhappy when you use generics. I have every hope that this will be fixed by b2, but as of right now, if I wanted to use the Designer (and it’s so sweet, how could I not want to use it?!?), I had to strip out any methods or properties in my visual code that exposed generics (although method and property implementations with generics works just fine).

Stripping out List<T> brought me to the use of ArrayList instead. That worked just fine (System.Collections.ArrayList is even available via the Browse button when setting up app/user settings) until the XML serializer couldn’t serialize instances of the custom type that I was using to populate my ArrayList. The error looked like this:

Could not use Xml serialization for setting: SelectedExchanges. --->
System.InvalidOperationException: There was an error generating the XML document. --->
System.InvalidOperationException: The type MyNamespace.MyType was not expected.
Use the XmlInclude or SoapInclude attribute to specify types that are not known statically.

When I start up the debugger to see the line of code where the error is happening, I ended up in Main because the code is part of the Application start-up for which there is no source code, making this even more difficult to debug. The problem was that the underlying serialization engine didn’t know what to do with this custom type. The clue was the mention of the XmlInclude attribute, which you can use to tell the serializer what types may be in the ArrayList, but you have to have somewhere to hang the attributes. In this case, that lead to a custom ArrayList type for the express purpose of informing the serializer (making me really miss the use of generics, where all of the types were specified at compile-type for me automatically):

[System.Xml.Serialization.XmlInclude(typeof(MyNamespace.MyType))]
public class MyList : System.Collections.ArrayList {
  public override object Clone() {
    MyList newList = new MyList();
    newList.AddRange(this);
    return newList;
  }
}

Notice also the Clone method. I added this later because the base ArrayList Clone method creates an instance of ArrayList, not my custom MyList type. Of course, since this was all run-time type errors, I couldn’t let the compiler tell me about these problems; I had to run my app and find them. Very frustrating, especially when generics makes these problems all go away.

Still, I’m very much enjoying the new productivity features in VS05 and Windows Forms 2.0 and when they work better together, I’ll be even more happy.


← Newer Entries Older Entries →