Thursday, March 10, 2011

Updating the MDI parent menu icon for maximized child form

While working on a C# .NET program recently, I ran up against an infamous (and another apparently long-standing) bug in Microsoft's Multiple Document Interface (MDI) architecture. When a child form has been maximized, and its window icon is updated, the icon that is merged into the MDI parent's menu strip is not updated to reflect the change.

I was using the child window icon to identify the document type being displayed and to mark when it had been modified. Everything worked fine when the window was open in Normal mode, but the icon stopped being updated when the child was maximized to fill its parent's client area.

After literally days of searching the web to see if anybody else solved the problem, I found answers ranging from "impossible" to "keep track of your own windows" (a.k.a. the old roll-your-own interface standby). Digging through the layers of classes with the debugger, I figured out a hack that seems to work.

Here's the basis of my solution: when a child window is maximized, the controls on its title bar are merged with its parent window. The parent menu inserts an image as its first element that is a copy of the child window's icon. The image (a Bitmap image) is copied only at the time the child is maximized, and is never updated afterward, regardless of calling Refresh(), Update(), Invalidate(), etc. So, to get around this missing functionality, my solution was to check the parent menu when I changed the icon of the child window; if it had an image in its first element, I converted the Icon into a Bitmap and replaced the image. It may sound hokey, but it worked.

These are the steps I used for updating the icon. I raised an event to update the parent form.

// Change the child icon according to the document state.
// Called from a method in the child form.
int newState = this.GetChildState();
this.Icon = (newState == 0) ?
myProject.Properties.Resources.savedicon :

// ...raise event to notify the parent. sender is the child form.

// Change the parent menu icon when child is maximized.
// Called from an event handler in the parent form.
if (MainMenuStrip.Items[0].Image != null)
// Copy the new state icon from the child window.
MainMenuStrip.Items[0].Image = ((Form)sender).Icon.ToBitmap();

The MDI architecture has its quirks. This is just a way to get around one of them. I think this is an inelegant solution to an inelegant problem, but sometimes you just have to be practical.