In the previous post, we have only scratched the surface of how to provide a custom thumbnail and custom preview for an application’s window. However, we focused on top-level windows in that post – these are the windows that DWM (Desktop Window Manager) recognizes, loves and converses with. But what about child windows in an MDI application? What about individual tab windows in a TDI application, like Internet Explorer?
This is a completely different story, with a significantly steeper barrier of entry. The reason is that the DWM won’t talk to child windows no matter what we do. This means that for each tab window, we have to create a top-level invisible (or off-screen) proxy window that we can register with our application’s taskbar button. This proxy window will delegate the DWM messages for providing a thumbnail and a live preview to the real child window, making this whole act transparent to the user.
Implementing something like this in native code is not trivial, and mainly revolves around handling all the different window messages in your proxy window’s window procedure. Specifically, you would need to handle the following messages:
Fortunately, this work is handled for you by the CustomWindowsManager class that we’ve seen in the previous episode (part of the Windows7.DesktopIntegration project in the managed wrapper). The wrapper automatically creates a proxy window on your behalf and hooks up all the window messages so that your application behaves seamlessly. All you need to do is provide the thumbnail and live preview (or rely on the partial support for automatically rendering a screenshot of the window*).
The sample Windows7.DesktopIntegration.WebBrowser application demonstrates how a tabbed web browser can mimic IE8’s taskbar thumbnail behavior.
This simple web browser has support for multiple tabs, each of which is registered as an individual taskbar thumbnail, with full live preview support. (See * below for an explanation of why there are sometimes glitches in the rendered image – mainly because there’s no way to grab the screen data from a tab that is not currently visible.)
How is this done? Well, for each new tab, we create a CustomWindowsManager instance and pass to it the tab’s window handle as well as the parent form’s window handle. This automatically causes the CustomWindowsManager to create a proxy window for our tab, and hook up all the events required. All we need to do now is provide the actual thumbnail bitmap and live preview bitmap, which are stored when the tab is rendered. (Note that in this scenario, there’s no need to call DispatchMessage from your form’s window procedure because the CustomWindowsManager does it automatically in the proxy window’s window procedure.)
Here’s the code that executes when a new tab is added to the simple web browser:
CustomWindowsManager cwm = CustomWindowsManager.CreateWindowsManager(newTab.Handle, Handle);
cwm.PeekRequested += (o, e) =>
{
e.Bitmap = new Bitmap(_tabBitmaps[copy], e.Width, e.Height);
e.DoNotMirrorBitmap = true;
};
cwm.ThumbnailRequested += (o, e) =>
{
e.Bitmap = new Bitmap(_tabBitmaps[copy], e.Width, e.Height);
e.DoNotMirrorBitmap = true;
};
_windowManagers.Add(cwm);
Not much at all here other than the actual logic that provides the thumbnail and live preview – which is of course the application’s task. There are quite a few other interesting things around the CustomWindowsManager class which I encourage you to investigate on your own.
* I’m saying “partial support” here because it’s fairly difficult to automatically obtain a screenshot of a child window accurately. Moreover, it might be the case that the child window is not even drawn until it is selected – in which case there is no screenshot that could possibly be obtained! In these cases, it is up to your application to use its internal rendering logic in both scenarios – when a thumbnail/live preview is needed, and of course when the window is actually shown.
Even without getting involved with the APIs, it’s clear that one of the most visually stunning features of the Windows 7 taskbar is the live multiple thumbnails shown for each window. The new taskbar thumbnails are much smarter than their Vista counterparts, enabling you to preview a live rendering of the underlying window (a.k.a. Aero Peek) and to close the window without even switching to it. (And of course there’s no need to mention the awesome thumbnail toolbars that we’ve seen in the previous post.)
However, the Windows 7 taskbar thumbnails are not just smarter – they are also significantly more extensible. With a modest degree of effort, an application can customize the specific bounds of the area appearing in the thumbnail (thumbnail clip) and even render an entirely different thumbnail and live preview than the default DWM representation, enabling very interesting scenarios which improve the user’s ability to see past the thumbnail without switching to the application’s window.
For example, consider the sample Windows7.DesktopIntegration.DocumentReader application which is bundled with the managed taskbar wrappers. It’s a really simple app, capable of rendering an RTF file in its main edit control, but here’s what its thumbnail looks like by default:
Can you read anything on that thumbnail? I doubt that. However, the user is clearly not interested in the entire visual rendering of this window. He might be interested only in the few lines around the current cursor position, instead. So why don’t we pull the managed wrapper and clip the thumbnail so that it only shows a few lines around the cursor position?
The “Toggle Clip” button does just that, and here’s a glimpse at its implementation:
private void SetClip()
{
int index = rtbText.GetFirstCharIndexOfCurrentLine();
Point point = rtbText.GetPositionFromCharIndex(index);
Windows7Taskbar.SetThumbnailClip(Handle, new Rectangle(point, new Size(200, 119)));
}
In real code, you would probably not want to hardcode the 200 and 119 constants for the image width and height, but work with me here. Look at the result:
Now THAT’S readable, isn’t it? Just as big as the original window, only showing a clipping of its contents that is currently relevant (around the cursor position).
If this wasn’t enough, we can entirely customize what the thumbnail displays. For example, there’s hardly any need for the window bounds and the buttons in the above image – we could squeeze more text in if we explicitly created a bitmap with some amount of text around the cursor position and showed that to the user as a thumbnail of this window.
This is somewhat more difficult that what we just did, and requires the use of the CustomWindowsManager class (from the Windows7.DesktopIntegration assembly). By registering to the ThumbnailRequested event, the application can customize the thumbnail that is displayed by the DWM; by registering to the PeekRequested event, the application can customize the live preview that is rendered when the user hovers over the taskbar thumbnail.
The following code renders a thumbnail by explicitly drawing a few lines of text (around the cursor position) onto the drawing surface:
_windowsManager = CustomWindowsManager.CreateWindowsManager(Handle, IntPtr.Zero);
_windowsManager.PeekRequested += (o, e) =>
{
e.UseWindowScreenshot = true;
};
_windowsManager.ThumbnailRequested += (o, e) =>
{
Bitmap bmp = new Bitmap(e.Width, e.Height);
using (Graphics g = Graphics.FromImage(bmp))
{
int index = rtbText.GetFirstCharIndexOfCurrentLine();
g.DrawString(rtbText.Text.Substring(index, Math.Min(150, rtbText.Text.Length - index)),
new Font("Tahoma", 9),
new SolidBrush(Color.Black),
new RectangleF(0, 0, e.Width, e.Height));
}
e.Bitmap = bmp;
};
Note how the PeekRequested event handler simply instructs the framework to use the window screenshot as the live peek, which is precisely what you would want if you don’t want to customize this preview aspect.
This is not all however – the application still has to override its window’s window procedure and call the DispatchMessage method of the CustomWindowsManager class to make sure the DWM notifications are dispatched appropriately:
if (_windowsManager != null)
{
_windowsManager.DispatchMessage(ref m);
_windowsManager.InvalidatePreviews();
}
base.WndProc(ref m);
There’s a particularly interesting phenomenon here – grabbing a screenshot of the thumbnail preview on my system actually FAILS to include the thumbnail in the screenshot (probably because it’s custom, but I haven’t looked into this enough yet). However, you can easily reproduce this on your Windows 7 machine by clicking the “Toggle Preview” button in the document reader sample application.
If at any time you want to disable the custom preview provided by the app, just call the DisablePreview method of the CustomWindowsManager and everything goes back to normal.
To conclude, even though there are still some glitches in the current implementation of the managed wrapper (for example, it’s unable to automatically grab a screenshot of the window if the window is minimized), it still provides a fairly easy way to customize the way your application’s thumbnail and live preview is rendered, providing exactly the look you need to achieve the best user experience.