Tuesday, March 07, 2006

Getting Real Work Done in .NET Windows Forms

I have been working on a Windows Forms (WinForms) applications since Christmas 2005. The application essentially sends files to a web site for business logic processing and receives notifications, via RSS, that the user should be interested in. This is the first WinForms app I have developed entirely on my own. When I was working for Vertigo Software I was the Project Lead/Technical Lead on a number of WinForms apps that were pretty sophisticated. Using IE to do some pretty cool rendering tricks, BITS to download application updates, and good UI. One of those apps was the Windows Server 2003 Guided Tour But this is different because I am doing it all and its eye opening. To get your WinForms app to behave like a first class Windows app, you absolutely are going to step outside the .NET managed libraries and you need to understand how an actual Win32 application is constructed. I am sure this is not news to many, and I knew this is was the case going into this development effort, but it still bears repeating. What am I talking about? Take for instance the behavior of something like MSN Messenger with the close button. Everyone is familiar with this pattern now:
  • Application main window is running and visible to the user
  • Application has a button in the Taskbar and and icon in the tray (i.e. the stuff next to the clock)
  • User clicks the Close button (i.e. the red X). Normally this exits the application, but your app is special
  • Your app should hide its taskbar button but stay running and keep its icon in the tray
How are you going to do this in WinForms? Your first instinct is to register for the Form.Closing event, cancel the event and then hide your form. You also need to show an icon in the tray. Hooking Form.Closing is part of the solution, as is hiding your form (e.g. Form.Visible=False) but this leaves once pretty big whole in the functionality of your application. If your app is running and the user tries to Logoff or Shutdown/Restart the computer, you are implicitely cancelling that event. The user Logging off, Shutting down, or restarting is called ending their session according to Windows. The only way for the user to end their session if your app is cancelling Form.Closing is to kill it with Task Manager! This is obviosuly bad and this is all it takes (in C#):
private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
      e.Cancel=true;
}
How do you solve this problem? What you really want to do is cancel the Form.Closing event in cases where the user session isn't ending. You might think that Environment.HasShutdownStarted would be your salvation, and you would be wrong. You might then stumble onto the Microsoft.Win32.SystemEvents.SessionEnding event and hook it. Here is the description from MSDN:
Occurs when the user is trying to log off or shut down the system.
Great I will just hook that and, nada. That's right, There is no guarantee your Form will receive this event before Form.Closing is fired, thus making it useless for your purposes. So what do you do? You Google like mad and hope someone has figured this because you can hear Han Solo saying in your head: "I have a bad feeling about this." Lucky for you, they have and and its actually the poor MS employee writing the documentation for Microsoft.Win32.SystemEvents.SessionEnding because in the explanation of this event, there is sample code to do exactly what you want. How does it work? You have to override the actual message loop processing function, the same one that has existing in Win32 forever, and listen to the WM_QUERYENDSESSION message manually. Then you have to track whether you have received this message manuallly and respond to it in Form.Closing. Ah, now everything behaves right. Why the data about WHY your form is closing isn't in CancelEventArgs boggles the mind. This is far from the only example, but this post is long enough as is. What lesson have I learned from this development effort? Win32 knowledge is going to be relevant for a long time, even with Windows Presentation Foundation (WPF) on the horizon. MS never seems to completely replace the last application development with the new one. Charles Petzold has already confirmed that WPF won't have even all the controls that WinForms has. Maybe this is just me, but is MS ever going to finish one framework before replacing it with another? Win32->MFC->ATL->VB6->WinForms->WPF, when is it going to stabalize? I have been on this ride enough that I don't know if I want to get on the WPF roller coaster. Cocoa is looking better and better...