September 30, 2006
04:13 AM

As promised in my last entry, here's the code.

[Download the Visual Studio Project]
[Download the Sample Executable (Windows Vista RC1 or higher)]

<d:Thumbnail Source="{Binding}" />

Trivial to use.

public sealed class Thumbnail : FrameworkElement, IDisposable

Thumbnail is a FrameworkElement and implements IDisposable. There is also a finaliser to ensure that the Thumbnail will be unregistered in the event that my check for visual tree membership fails. Unfortunately it looks like WPF doesn't take notice of IDisposable even for elements it creates and throws away.

public static DependencyProperty SourceProperty;
public static DependencyProperty ClientAreaOnlyProperty;
public static new DependencyProperty OpacityProperty;

Thumbnail has two DependencyProperties. Source is of type IntPtr and should be bound to the Window Handle for which a DWM Thumbnail is desired. ClientAreaOnly is of type bool and indicates whether the Window Chrome should be hidden. The Opacity Property from UIElement is overriden.

private HwndSource target;
private IntPtr thumb;

Next up, a couple of private fields. After the thumbnail is initialised, target is set to the HwndSource for the current window, thumb is the Thumbnail Handle or IntPtr.Zero if there is no Thumbnail registered.

private void Thumbnail_LayoutUpdated(object sender, EventArgs e)
{
    if (IntPtr.Zero == thumb)
    {
        InitialiseThumbnail(this.Source);
    }

    if (IntPtr.Zero != thumb)
    {
        if (!target.RootVisual.IsAncestorOf(this))
        {
            // we are no longer in the visual tree
            ReleaseThumbnail();
            return;
        }

        GeneralTransform transform = TransformToAncestor(target.RootVisual);
        Point a = transform.Transform(new Point(0, 0));
        Point b = transform.Transform(new Point(this.ActualWidth, this.ActualHeight));

        DWM.ThumbnailProperties props = new DWM.ThumbnailProperties();
        props.Visible = true;
        props.Destination = new DWM.Rect(
            (int)Math.Ceiling(a.X), (int)Math.Ceiling(a.Y),
            (int)Math.Ceiling(b.X), (int)Math.Ceiling(b.Y));
        props.Flags = DWM.ThumbnailFlags.Visible | DWM.ThumbnailFlags.RectDetination;
        DWM.DwmUpdateThumbnailProperties(thumb, ref props);
    }
}

Thumbnail_LayoutUpdated handles the LayoutUpdated event defined by UIElement. This event is fired both when the size of the element changes and when it's physical location changes. The bulk of the method is responsible for updating the Thumbnail's position. To do so, I obtain a transform to the Root Visual and use that to calculate the final location. The event also appears to fire when the Element is removed from the Visual Tree, I detect this using the IsAncestorOf method of Visual and if necessary unregister the Thumbnail.

There is more to this class: the Measure and Arrange overrides, Thumbnail registration, updates and release - but there's not much value discussing that here - it should be straightforward to follow.

I put together a slightly more interesting sample than last time. This one uses a ListBox together with a ContentControl to create a simple Master-Detail arrangement. The ListBox is populated with a list of Window Handles, which through DataTemplates are transformed into DWM Thumbnails. Selecting a Window in the list will clone a larger view on the right-hand side.

As you can see in the screenshot below, a problem is visible. The Thumbnails aren't clipped, the top thumbnail is above the "Refresh" button while it should be hidden. I imagine this would also happen when hosting Windows Forms, but I haven't confirmed it.

Where to from here? I've had some ideas, not sure if I'll implement them myself.

  • Minimise to Desktop?
  • Minimise to Sidebar Gadget
  • Running Application Slideshow Gadget

[Download the Visual Studio Project]

[Download the Sample Executable (Windows Vista RC1 or higher)]

© Douglas Stockwell 2007
Creative Commons License Unless otherwise specified all "source code" examples are available for use under the Creative Commons Attribution-Noncommercial 3.0 License. Please contact me if you would like more flexible licensing terms.
Messenger Presence