Data binding is awesome. Sometimes we take it for granted that a list or grid control can, with only a few lines of code, both visualise and manipulate a collection. It’s a principle that supports the separation of model and presenter, not to mention making the task of developing a GUI a real breeze. More than that, though; when you understand the underlying principles behind data binding, it becomes so much cooler. In a previous article, I gave some background on how data binding accesses properties (which, as I explained, might not be actual property members; they could be logical, like columns in a DataTable). PropertyDescriptor is the magic class in all this, defining the ‘property’, naming it and providing a mechanism to get/set its value. The properties exposed by a data source (either by reflection or by ITypedList) can be used for the DisplayMember/ValueMember properties (of ListBox and ComboBox) and as columns in a DataGridView.

That’s all fine and dandy, but what if the property we want to bind to doesn’t belong directly to the list item? What if that property belongs to an object that the list item contains? For example, consider the following situation:

UML diagram for this example

A Person has a Name and a HairStyle. In UML, we’d say that Person “has a” HairStyle (or, HairStyle is aggregated by Person). A HairStyle, in turn, has a Colour and a Length. Say we’re binding a List<Person> to a DataGridView control. How can we bind one column to the Person‘s Name property and another to the Person‘s HairStyle‘s Colour property?

One column’s DataPropertyName will be set to “Name”. What about the other? Can we have “HairStyle.Colour”? The answer is that, no, with conventional data binding, we can’t. Only properties which belong directly to Person are available for binding. The good news is that, thanks to the aforementioned ITypedList interface (already used in, for example, DataTable) we can solve this problem.

Okay, before we go any further, you might be asking, “Why not transform the data into a flat form first?”. True, we could use LINQ to return a sequence of anonymous types which would contain bindable properties from both classes. There are other approaches too, however all of these destroy the potential two-way relationship that data binding offers. Such a data source would be immutable, hence it would be read-only in the DataGridView control. Without editing capabilities, the power of the grid control is significantly diminished. What if we want the user to be able to view and edit the person’s name and hair colour in the same place? It would be really cool if we could bind to properties on aggregated objects…

Yes, we can (with ITypedList)

The ITypedList interface exposes a method called GetItemProperties(), which returns a collection of PropertyDescriptor objects. By implementing this interface, we can create a collection class which exposes not only the properties on the list items, but also those on objects owned by the list items. In fact, we can use recursion to get the properties on the properties, and the properties on those properties, and so on! We’ll extend the existing List<T> generic class and implement ITypedList:

    public class AggregationBindingList<T> : List<T>, ITypedList {
        public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors) { /* ... */ }
        IEnumerable<PropertyDescriptor> GetPropertiesRecursive(Type t, PropertyDescriptor parent, Attribute[] attributes, int depth) { /* ... */ }
    }

We can get the properties on the list items (and the aggregated objects) using the TypeDescriptor class, as follows:

IEnumerable<PropertyDescriptor> GetPropertiesRecursive(Type t, PropertyDescriptor parent, Attribute[] attributes, int depth) {
    if (depth >= MAX_RECURSION) yield break;

    foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(t, attributes)) {
        yield return property;

        foreach (PropertyDescriptor aggregated in GetPropertiesRecursive(property.PropertyType, parent, attributes, depth+1)) {
            yield return property;
        }
    }
}

(I included a cap on the depth of recursion; if an class has a property of its own type, walking through all the aggregated properties would result in an infinite loop.)

See anything wrong with the code above? On the face of it, it looks sound, however one must recall how PropertyDescriptor is accessed by data binding; namely, it will pass an instance of the list item to the descriptor’s GetValue/SetValue method. A PropertyDescriptor for a property on an aggregated object will expect an instance of the aggregated object, not the list item! The other problem presented is that we have no way of uniquely identifying each property, making it impossible to relate the properties to the properties that own them.

A PropertyDescriptor for properties on aggregated objects

We can solve both of the aforementioned problems by creating our own class which derives from PropertyDescriptor. Essentially, we need to wrap the property descriptor supplied to us by TypeDescriptor.GetProperties() and hold a reference to the property which owns it; this property may, in turn, be owned by another property, and so on (due to the use of recursion). When data binding calls GetValue or SetValue, supplying an instance of the list item, we’ll call the corresponding method on the owning property first, in order to return an instance of the aggregated object. We can then call the wrapped property’s method to get or set the value on this object:

public override object GetValue(object component) {
    return AggregatedProperty.GetValue(OwningProperty.GetValue(component));
}

In the constructor for our AggregatedPropertyDescriptor class, we collect the inner and outer properties and set the name of the aggregated property appropriately:

Note: We cannot use the dot (.) symbol to delimit aggregation (e.g. “HairStyle.Colour”) because the ComboBox control will truncate the string when it encounters that character. Instead, i’ve opted to use the C++ pointer-to-member symbol (->).

public AggregatedPropertyDescriptor(PropertyDescriptor owner, PropertyDescriptor aggregated, Attribute[] attributes)
    : base(owner.Name + "->" + aggregated.Name, attributes) {
    OwningProperty = owner;
    AggregatedProperty = aggregated;
}

Completing the GetItemProperties method

Now that we have a mechanism to correctly handle properties on aggregated objects, we can finish the GetItemProperties() method on our AggregationBindingList<T> class:

IEnumerable<PropertyDescriptor> GetPropertiesRecursive(Type t, PropertyDescriptor parent, Attribute[] attributes, int depth) {
    if (depth >= MAX_RECURSION) yield break;

    foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(t, attributes)) {
        if (parent == null) {
            // property belongs to root type, return as-is
            yield return property;
        }
        else {
            // property is on an aggregated object, wrap and return
            yield return new AggregatedPropertyDescriptor(parent, property, attributes);
        }

        foreach (PropertyDescriptor aggregated in GetPropertiesRecursive(property.PropertyType, parent, attributes, depth+1)) {
            yield return new AggregatedPropertyDescriptor(property, aggregated, attributes);
        }
    }
}

And there you have it; when data binding enumerates the properties on the list items, it will see:

  • Name
  • HairStyle
  • HairStyle->Colour
  • HairStyle->Length

Furthermore, we will be able to get and set values on all of these properties.

Example usage

A more complex example using a self-referencing Person class:

public class Person {
    public string Name { get; set; }
    public int Age { get; set; }
    public Person Father { get; set; }
    public Person Mother { get; set; }
}

Assuming a Form with a DataGridView control on it:

dataGridView.AutoGenerateColumns = false;

dataGridView.Columns.Add("Name", "Person");
dataGridView.Columns["Name"].DataPropertyName = "Name";

dataGridView.Columns.Add("Age", "Age");
dataGridView.Columns["Age"].DataPropertyName = "Age";

dataGridView.Columns.Add("FatherName", "Father's name");
dataGridView.Columns["FatherName"].DataPropertyName = "Father->Name";

dataGridView.Columns.Add("MotherName", "Mother's name");
dataGridView.Columns["MotherName"].DataPropertyName = "Mother->Name";

dataGridView.Columns.Add("GrandfatherName", "Grandfather's name");
dataGridView.Columns["GrandfatherName"].DataPropertyName = "Father->Father->Name";

AggregationBindingList<Person> people = new AggregationBindingList<Person>();

Person harry = new Person { Name = "Harry", Age = 75, Father = null, Mother = null };
Person frank = new Person { Name = "Frank", Age = 65, Father = null, Mother = null };
Person angela = new Person { Name = "Angela", Age = 68, Father = null, Mother = null };
Person bob = new Person { Name = "Bob", Age = 35, Father = frank, Mother = angela };
Person fred = new Person { Name = "Fred", Age = 32, Father = harry, Mother = angela };
Person mary = new Person { Name = "Mary", Age = 36, Father = null, Mother = null };
Person jim = new Person { Name = "Jim", Age = 5, Father = bob, Mother = mary };

people.AddRange(new Person[] { bob, fred, mary, jim });
dataGridView.DataSource = people;

Download

AggregationBindingList.cs

ComboTreeBox controlThat’s right, exactly what it says on the box. But why do we need a control like this? Well, it’s a need that i’ve come across many times, in fact, and applies when:

  • You have hierarchical data
  • You want the user to make a selection
  • You don’t have enough room to use a TreeView control
  • You don’t expect the selection to be changed often…
  • …but when it is, a dialog box is too clunky and interrupting

In these situations, a regular ComboBox just won’t cut the mustard; it doesn’t show the structure of your data and its drop-down shows every item, whether you like it or not.

I have explored the challenges associated with writing a control like this in Windows Forms at great length, and i’d like to share the common pitfalls as well as the ultimate solution, which revolves around the ToolStripDropDown control.

What is a drop-down, really?

Most people, when considering this question, would immediately tell you that a drop-down is a Form. After all, it exhibits a lot of the behaviour of a form:

  • It can spill over outside the client area of its parent form
  • It can independently process mouse and keyboard input
  • It can appear top-most; other windows do not obscure it

However, this assessment is fundamentally incorrect. Drop-downs do some very un-Form-like things:

  • When they open, they do not take focus away from the parent form
  • When they capture mouse/keyboard input, they do not take focus away from the parent form
  • Interacting with their child controls/items does not switch focus

So, how does one achieve this behaviour? Those last few points seem to represent a drastic departure from the way forms and controls work!

How to get it wrong

With that in mind, here are some great ways to fail at implementing drop-downs in Windows Forms:

Use a Form (ignorantly)

So, you decide to implement your drop-down as a Form. You make it borderless, suppress it from the taskbar, perhaps make it TopMost as well. You might even make it an owned form. Whatever the particulars, your users click the drop-down button and the form is shown. There’s a slight flicker/flash as focus switches to the drop-down’s form. The parent form’s title bar changes colour and its drop-shadow (under Aero) narrows. The user makes their selection via the controls on the drop-down form and it closes. Focus isn’t restored to the parent form. Keyboard input ceases to have any effect, so the user can’t tab to the next control. As soon as they click on the parent form again, focus changes, the title bar changes colour and the drop shadow expands. There might be an unsightly flicker too. And if you think it looks passable, try opening and closing the drop-down several times in quick succession. Overall result? Loads of superfluous clicks and a very poor user experience.

Use a Form and try to be clever

Those who have tried the above (or arrive at the problem possessing a better understanding of WinForms/Win32) will try to solve the initial problem first; that is, that the drop-down’s form takes focus away from the parent form. A well-kept secret about the Form class is that it has a protected property called ShowWithoutActivation, which (in most cases) will skip the activation of the form when it is shown:

protected override bool ShowWithoutActivation {
    get { return true; }
}

In those situations where ShowWithoutActivation has no effect, you can forcefully set the WS_EX_NOACTIVATE flag on control’s style by overriding the CreateParams property:

protected override CreateParams CreateParams {
    get {
        const int WS_EX_NOACTIVATE = 0x08000000;

        CreateParams p = base.CreateParams;
        p.ExStyle |= WS_EX_NOACTIVATE;
        return p;
    }
}

Mind you, these do nothing to prevent the form from gaining focus once it is visible; so merely clicking on your form will bring the whole problem to light again. So, having succeeded in preventing the form from taking focus when it is displayed, the next challenge is to prevent it from ever getting focus. The most commonly-used technique for this is to go all Win32 and start intercepting window messages. Specifically, the response to the WM_MOUSEACTIVATE message (which is sent to the form when it is clicked, but doesn’t have focus) is set to MA_NOACTIVATE (which processes the click without changing focus):

protected override void DefWndProc(ref Message m) {
    const int WM_MOUSEACTIVATE = 0x21;
    const int MA_NOACTIVATE = 0x0003;

    if (m.Msg == WM_MOUSEACTIVATE)
        m.Result = (IntPtr)MA_NOACTIVATE;
    else
        base.DefWndProc(ref m);
}

This actually works; clicking the drop-down’s form will not cause focus to change. The problem is, your drop-down isn’t just going to consist of the form itself. Clicking any control on the form will give focus to the drop-down’s form, undoing all your good work. You can replicate the message interception technique for every control on your form, but some controls (e.g. the TreeView) won’t play ball. Selecting a TreeNode, for example, will always set focus.

So, at the end of all this fuss, you’re left with a series of subclassed controls and low-level code. And what does it get you? A drop-down that will give you a nice user experience… as long as you don’t actually click on it. Ultimately useless.

Use a Control instead

Finally, you might think that using a Control and adding it to the parent form might be a clever way of avoiding issues with focus… and yes, it does completely mitigate that problem. Unfortunately, you’re now left with a drop-down that can’t extend beyond the parent form’s client area. You might think this is fine, so long as your drop-down control has plenty of space below it on the form… but doesn’t that completely defeat the purpose of having a drop-down in the first place? This ‘solution’ is also a dud.

Enter, ToolStripDropDown

In fact, when you look into the behaviours exhibited by drop-downs, it becomes apparent that they are more like menus than forms. Menus can independently process keyboard and mouse input without taking focus away from their parent windows. So, let’s look to the most obvious example in the framework; the ContextMenuStrip control. It’s a Control by inheritance only; in all other respects, it behaves like a Component. Importantly, though, it’s not a Form – and therefore avoids the issues with focus. In fact, menus represent a third class of focus when compared to other elements:

  • Forms take focus from their parent, but also have a separate notion of focus for their controls.
  • Controls take focus from their parent, but surrender it to their children.
  • Menus share focus with their parent and children.

This behaviour represents exactly what we want out of a drop-down, however the ContextMenuStrip has a very specific purpose. Looking into its inheritance chain, we can find ToolStrip, ToolStripDropDown and ToolStripDropDownMenu. The first doesn’t represent a menu, so that’s no use to us. The second is the base class for a drop-down, which makes a good start. It’s worthwhile to note that ToolStripDropDown doesn’t support scrolling (whereas its next descendant does), however with the method i’m going to use to implement my drop-down, automatic scrolling support won’t be necessary.

Any derivative of ToolStripItem (labels, buttons, etc) can be added to a drop-down, including ToolStripControlHost (although i’ve elected not to simply host a TreeView control inside the drop-down – there are still some quirky focus issues in doing this, not to mention the overheads involved). The drop-down itself must contain at least one item, even if it takes up no space. Being a descendant of Control, it can be painted by overriding the OnPaint method and can respond to mouse and keyboard events as well. Combined with the non-focusing behaviour, this gives us all the tools necessary to create a drop-down that behaves as expected.

Implementation

The full functionality of the TreeView is not required for this drop-down control, so a similar (but simplified) data model will form the basis of the implementation.

ComboTreeBox Class Diagram

DropDownBase contains the basic functionality for the editable portion of the control; painting, event handling and design-time support, as well as management of the DroppedDown state. The data model centers around ComboTreeNode, which is akin to TreeNode. The circular relationship between it and its collection type, ComboTreeNodeCollection, creates the tree structure. ComboTreeBox is the primary implementation of DropDownBase. It holds and controls the data model, and visualises the selected node. It also holds ComboTreeDropDown, the actual drop-down (descended from ToolStripDropDown).

In this implementation, the data model is completely separated from the view; nodes can be defined and manipulated independently of the control/drop-down.

Model – ComboTreeNode and ComboTreeNodeCollection

ComboTreeNode is a simple, atomic class used to hold the node’s name, text, state and model its relationship to the other nodes in the tree. ComboTreeNodeCollection represents a sub-tree, and is therefore associated with an owning node. The only exception to this rule is for sub-trees which have not yet been added to the control, and the root collection which belongs to the control. Neither class depends upon ComboTreeBox, however both take on additional properties and behaviours once added to the control.

ComboTreeNodeCollection implements IList<ComboTreeNode>, because the order of the nodes is important. An inner List<ComboTreeNode> is used as the backing store. The collection is responsible for assigning each node’s parent, as well as ensuring that CollectionChanged event (from INotifyCollectionChanged) is fired recursively up the tree:

public void Add(ComboTreeNode item) {
    innerList.Add(item);
    item.Parent = node;

    // changes in the subtree will fire the event on the parent
    item.Nodes.CollectionChanged += this.CollectionChanged;
    OnCollectionChanged(
         new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)
    );
}

It also implements the non-generic IList to provide compatibility with CollectionEditor, used by the Windows Forms Designer.

View – The drop-down itself

ComboTreeDropDown is responsible for displaying and providing interaction with the data model.Visually, the nodes can be represented as a series of rows, whose bounds are determined by their Depth in the tree, and whose visibility is determined by whether their Parent node is expanded. The visibility of each node is determined by recursively checking the Expanded property of the node’s parents; a node is visible only if the path to the root of the tree contains no collapsed nodes:

internal bool IsNodeVisible(ComboTreeNode node) {
    bool displayed = true;
    ComboTreeNode parent = node;
    while ((parent = parent.Parent) != null) {
        if (!parent.Expanded) {
            displayed = false;
            break;
        }
    }
    return displayed;
}

Rendering

A reasonably simple set of rules govern how the connectors between nodes in the tree are drawn – in fact, it’s possible to isolate the different permutations and cache the resulting bitmaps in order to save on processing and memory (these permutations are represented using the BitmapInfo structure). The superset of visible nodes changes whenever a node is expanded or collapsed, or if nodes are added/removed from the tree. Changes to the size and font of the owning control will also cause the set to be re-evaluated.

Scrolling

Scrolling is implemented manually, on a very simple principle: A contiguous subset of the visible nodes, the size of which is determined by the maximum height of the drop-down, is selected using an offset.

  • The range of the scroll bar is equal to the total number of visible items, minus the size of the largest subset that can be displayed in the drop-down.
  • The position of the scroll bar can be expressed as a percentage of the offset, relative to the range.
  • The size of the ‘thumb’ on the scroll bar is equal to the size of the scroll range as a proportion of the total number of visible items.

To actually render the drop-down, then, the visible nodes within the scroll range are positioned, relative to the top. The visual properties of each node are represented using NodeInfo. There are two painting operations per node; the (cached) bitmap which represents the indentation, node image, glyphs & connectors, and the text on the node. If the number of nodes in the scroll range is less than the total number of visible nodes, the scrollbar is painted too.

The bounds for drawing each node and part of the scroll bar (stored using ScrollBarInfo) are also used when responding to mouse events. If the mouse pointer falls within the bounds of an item, the associated ComboTreeNode can be determined. Clicking the plus/minus glyph beside a node causes it to collapse/expand, whereas clicking anywhere else on the node will change the selection and close the drop-down. (It’s worthwhile to note that some mouse features, such as dragging the scroll bar and holding down a button with auto-repeat behaviour, are somewhat complex and fall outside the scope of this overview. I will, however, mention the most noteworthy quirk – the MouseWheel event for the drop-down can only be handled on the owning control.)

Highlighted item vs. selected node

Each time the drop-down is displayed, it scrolls to (and highlights) the node corresponding to the SelectedNode property of the owning control. The highlighted item is a separate concept to the selected node, as is the case in an ordinary ComboBox‘s drop-down. These rules apply:

  • Moving the mouse over an item causes it to become highlighted, however the selected node does not change unless the item is clicked.
  • Navigating with the keyboard (up/down) changes both the highlighted item and the selected node.
  • Scrolling (with either the mouse wheel, keyboard (page up/page down) or using the scroll bar) changes neither the highlighted or selected items.

Controller – The owning control

ComboTreeBox is the controller in this implementation. In addition to holding the data model and providing access to the view, it exposes operations for manipulating both – bringing the whole concept together into a reusable Windows Forms control. As such, it combines aspects of behaviour from both the ComboBox and TreeView controls:

  • The ability to assign a Name to each node, which can be used to access the node in its collection.
  • Use of ImageIndex/ImageKey to specify an image for each node.
  • Persistence of the Expanded state for each node.
  • SelectedNode property to get/set the user’s selection in the tree.
  • PathSeparator and Path properties to express the selected node as a path string.
  • BeginUpdate()/EndUpdate() methods for bulk adding.
  • ExpandAll()/CollapseAll() methods to manage the tree.
  • Ability to sort the tree (performs a recursive sort, using either a default or custom comparer).

The following additional features are provided:

  • Recursive enumerator (AllNodes) to iterate over the entire tree.
  • Ability to choose whether paths are constructed from node names or text.
  • Ability to set the selected node using the Path property.
  • ExpandedImageIndex/ExpandedImageKey to allow a different image to be displayed for expanded nodes.

The following features from the TreeView control are NOT implemented for ComboTreeBox:

  • Horizontal scrolling (visually unappealing on a drop-down).
  • Checkboxes beside nodes (this would imply multiple selected values).
  • The ability to customise the visual elements or owner-draw the control using events.
  • SelectedImageIndex/SelectedImageKey properties (which, personally, I find useless).
  • Tooltips on nodes, drag-and-drop and other functionality outside the scope of a drop-down.
  • A Sorted property to create an automatically-sorted tree; instead, they can be sorted on-demand.

Final words

The ComboTreeBox is an example of both the need for custom drop-downs, as well as the solution to the problem of creating one in Windows Forms. The ToolStripDropDown component makes it all possible, even though (in this case) it places the burden of implementing the content of the drop-down manually. This was also a very useful learning exercise in writing a custom control which adhered to established conventions for appearance, behaviour and the handling of input. I am particularly pleased with the elegance of the scrolling mechanism and the use of bitmap caching; with these, I was successfully able to populate a drop-down with several hundred thousand nodes without suffering a significant performance hit (even though such volume would be thoroughly unsuitable for a drop-down!).

I hope it proves useful in its own right, as well as in its capacity for demonstrating how to implement a custom drop-down.

Download

Please visit the project page here: Drop-Down Controls

 

In an earlier post, I explained how to host Preview Handlers in Windows Forms. You may recall that Preview Handlers are implementations of the COM interface IPreviewHandler and are initialised either by a file or a stream (depending on whether they implement IInitializeWithStream or IInitializeWithFile).

I observed recently that the MAPI Mail Preview Handler (the handler registered for Outlook .MSG files) did not implement either of the aforementioned interfaces. After some further research, I have discovered that there is a third initialisation technique – which some preview handler implementations support to the exclusion of all others.

The COM interface IInitializeWithItem is used to initialise a preview handler (or thumbnail provider, etc) with an implementation of IShellItem. The latter interface is used by the Windows Shell API and represents a file, folder or virtual item in Windows Explorer. So why insist on a shell item instead of initialising the preview handler with a file or stream? Well, you have to remember that the primary host for preview handlers is, of course, Windows Explorer – which works primarily with shell items, rather than raw files or streams. They also remove the need for preview handlers to know the location of the item on disk (if indeed it is even on disk), allowing a wider variety of content to be loaded into them.

Declaration

The declaration for the interface is as follows:

internal const string GUID_ISHELLITEM = "43826d1e-e718-42ee-bc55-a1e261c37bfe";

(Don’t worry, i’ll explain that later…)

[
    ComImport,
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
    Guid("7F73BE3F-FB79-493C-A6C7-7EE14E245841")
]
interface IInitializeWithItem {
    void Initialize(IShellItem psi, uint grfMode);
}

[
    ComImport,
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
    Guid(PreviewHandlerHost.GUID_ISHELLITEM)
]
interface IShellItem {
    void BindToHandler(
        IntPtr pbc,
        [MarshalAs(UnmanagedType.LPStruct)]Guid bhid,
        [MarshalAs(UnmanagedType.LPStruct)]Guid riid,
        out IntPtr ppv
    );
    void GetParent(out IShellItem ppsi);
    void GetDisplayName(uint sigdnName, out IntPtr ppszName);
    void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
    void Compare(IShellItem psi, uint hint, out int piOrder);
};

Rather unsurprisingly, there is no managed API for obtaining instances of IShellItem, and they can’t be instantiated via Activator.CreateInstance(). Instead, the Shell API function SHCreateItemFromParsingName() is needed:

[DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
    static extern void SHCreateItemFromParsingName(
    [In][MarshalAs(UnmanagedType.LPWStr)] string pszPath,
    [In] IntPtr pbc, [In][MarshalAs(UnmanagedType.LPStruct)] Guid riid,
    [Out][MarshalAs(UnmanagedType.Interface, IidParameterIndex = 2)] out IShellItem ppv
);

(There are other functions that return IShellItem instances, but this one is the most appropriate for obtaining them for file system objects.)

Usage

Recalling the implementation of PreviewHandlerHost (my Windows Forms control), we can add a third condition to the section which initialises the preview handler:

if (mCurrentPreviewHandler is IInitializeWithFile) {
    ((IInitializeWithFile)mCurrentPreviewHandler).Initialize(filename, 0);
}
else if (mCurrentPreviewHandler is IInitializeWithStream) {
    mCurrentPreviewHandlerStream = File.Open(filename, FileMode.Open);
    StreamWrapper stream = new StreamWrapper(mCurrentPreviewHandlerStream);
    ((IInitializeWithStream)mCurrentPreviewHandler).Initialize(stream, 0);
}
else if (mCurrentPreviewHandler is IInitializeWithItem) {
    IShellItem shellItem;
    SHCreateItemFromParsingName(
        filename,
        IntPtr.Zero,
        new Guid(GUID_ISHELLITEM),
        out shellItem
    );
    ((IInitializeWithItem)mCurrentPreviewHandler).Initialize(shellItem, 0);
}

The reason for defining a constant for the GUID of the IShellItem interface is that it must be passed to the SHCreateItemFromParsingName() function. (There are two versions of the interface, one for compatibility with Windows XP SP1 and the other for Vista and onwards, the parameter determines which one to create.)

Final words

So, what does this enable us to do? Well, to date i’ve only come across one preview handler that insists on being initialised in this manner; the MAPI Mail Preview Handler {435fdba0-964c-43a7-8aff-cc94e21b2249}. However, in the business software I write, being able to preview Outlook messages is a significant requirement unto itself. Since Windows 7 ships with several more preview handlers than Vista did, it follows that the likelihood of encountering more which are initialised from shell items will increase.

Download

If you find my code useful, please consider making a donation.

PreviewHandlers3.zip – Visual Studio 2012 solution (includes test app) – .NET Framework 4.0 (updated 2013/10/14)

Regarding MSG Files

Several people have reported problems with the preview handler for Outlook mail messages (.msg files). To ensure that e-mail previews are displayed correctly, you will need to:

  • Ensure that the solution platform matches the architecture of the preview handler; i.e. x64 on 64-bit OS with 64-bit Office, or x86 on 32-bit OS with 32-bit Office. If you get this wrong, you will get an E_FAIL or CO_E_SERVER_EXEC_FAILURE error.
  • Use the included app.manifest file when building the project. This manifest instructs the runtime to load version 6 of the Windows Common Controls library (instead of the default version 5).
  • Run the project without attaching the debugger. In my build environment, this was the only way I could get it to work.

Most seasoned C# programmers will be familiar with P/Invoke and the use of the [DllImport] attribute to call unmanaged functions from DLLs. There are plenty of cases (especially in Windows Forms) where the managed functionality is too limiting to get the desired outcome, so it is excellent to have a way of calling functions directly from the Windows API. (Of course, it would be even better if there were managed equivalents of that functionality, but we won’t get into that argument.)

Perhaps less common is the use of DllImport to call functions from custom/purpose-built unmanaged DLLs. The aim of this article is to demonstrate how to author a DLL in C/C++ whose functions can be called from managed code. Now, i’m certainly no stranger to C++, but thusfar in my career i’ve never had cause to write my own Dynamic Library. So, here’s what you need to know:

Project/Build Settings

When adding a new Win32 Project to a solution in Visual Studio, you’re presented with a wizard. Here, you need to specify the application type as DLL.

Architecture

It’s important to note that you can only call functions from a 32-bit DLL if your managed project targets the x86 platform, or if you are running a 32-bit OS and your managed project targets the Any CPU platform. If the latter is true, and you are running a 64-bit OS, you will get a BadImageFormatException when you try to call the unmanaged function. You have two options when selecting the architecture for your solution:

  • Set the platform for the unmanaged project to Win32, and the managed project to x86. Your code will run on both platforms.
  • Build both Win32 and x64 DLLs, import the function(s) from both and call the appropriate version according to the current platform/process. (Note: .NET 4 adds Environment.Is64BitProcess to aid in this, but earlier versions of the framework make this a lot harder to do.)

Debug Info

There’s no point generating a PDB file for the unmanaged DLL – ultimately you’re going to want to use the Release build to import the functions from.

Character Set

Strings in .NET are Unicode, and indeed the default character set for C/C++ projects now reflects this as well. When you write your import declaration, you will need to specify CharSet=CharSet.Unicode in the DllImport attribute. If you don’t do this, strings might be marshalled as ANSI instead.

Implementation

These sorts of DLLs (i.e. unmanaged, non-COM) lend themselves better to the functional programming paradigm than OO. You’re probably going to want to implement your DLL as a set of independent utility functions. Use standard Win32 data types wherever possible, bearing in mind that the marshaller in .NET supports the most common subset of types only. If you elect to write functions that take or return complex structures, be prepared to re-declare these in your managed project. The same applies to enumerations.

Module Definition (.def) File

When you’ve written the code for your unmanaged DLL, the next step is to declare which functions you want to export (i.e. make available to the managed project). To do this, you need to add a Module Definition file to your unmanaged project. Visual Studio will automatically hook this up to the linker configuration for you. The format of the file looks something like this:

MODULE "NameOfUnmanagedDLL"
EXPORTS
    Function1Name   @1
    Function2Name   @2

…and so on. Declaring the exported functions in this manner ensures that the DllImport attribute will be able to locate them. The alternative technique involves decorating the function prototypes with the __declspec(dllexport) keyword – but this will obfuscate the name of the function, so I wouldn’t advise it. Use a DEF file whenever you can.

Adding Product/Company Information

Since you will likely be distributing the unmanaged DLL with your application, it’s a good idea to add product/company/version information. This adds credibility and consistency to your binaries. To add this information, add a new Resource (.rc) file to the project, then add a Version resource. As with the Module Definition file, Visual Studio will automatically hook this up to the linker. Within the Version resource, you can specify descriptive information about the DLL.

Calling From Managed Code

Writing the Import Declaration

The first thing you want to do is re-write the signature of the function as declared in C/C++, adding the modifiers static extern in order to mark them as being located in an external DLL. Then, you need to replace the C/C++ data types (on the parameters and return value) with appropriate .NET types. In doing so, consider the following basic principles:

  • HANDLE, HWND and most other pointer types (excluding arrays and strings) resolve to IntPtr
  • Fixed-length arrays resolve to .NET arrays of the appropriate type
  • wchar_t *, char *, BSTR and LPCWSTR (as well as many other string types) resolve to String or, if the result is mutable, StringBuilder
  • DWORD resolves to UInt32 (but can be marshalled as Int32 if required by the calling code)
  • Any complex structure must be re-declared in the managed project. The marshaller will map the unmanaged structure onto the managed one.

A basic function such as:

DWORD CreateProcessMediumIL(WCHAR* path, WCHAR* cmdLine);

Could be imported as:

[DllImport("MediumElevation.dll", CharSet=CharSet.Unicode)]
public static extern int CreateProcessMediumIL(string path, string cmdLine);

Note that, in this example, the function returns a Win32 error code – by marshalling it as Int32 instead of UInt32, we can elegantly pass it to the constructor of Win32Exception.

Catching Possible Exceptions

When calling the method (formerly function), you should be prepared to handle DllNotFoundException and BadImageFormatException. If the function returns a Win32 error code or an HRESULT, you can use Win32Exception/Marshal.ThrowExceptionForHR() to throw an exception for the error.

The complete calling code for the aforementioned function might look like this:

try {
    int result = CreateProcessMediumIL(@"C:\Windows\System32\notepad.exe", null);
    if (result != 0) throw new Win32Exception(result);
}
catch (DllNotFoundException) {
    // the DLL was not in the current directory or any of the paths probed by LoadLibrary
}
catch (BadImageFormatException) {
    // the DLL is probably for a different platform (e.g. 32-bit DLL called from 64-bit)
}
catch (Win32Exception) {
    // something failed within the function itself
}

Final Words

There are a number of legitimate cases for calling functions from unmanaged DLLs, and there is also a subset of legitimate cases for developing purpose-built unmanaged DLLs to provide advanced functionality within your managed projects. Provided you follow these basic guidelines, you should be able to avoid most of the common pitfalls in achieving this.

Of course, there are some alternatives which may be more appropriate for your specific needs:

  • Create hybrid native/managed assemblies in C++ and reference these in your managed projects
  • Create COM components which encapsulate the required functionality, then use the RCW features to treat these like managed objects in code

However, if you only need a small number of independent functions, this technique will suit your needs most sufficiently.

The static method ExtractAssociatedIcon in the System.Drawing.Icon class is typically used in .NET applications to display an icon for a file that is consistent with what the user would see in Windows Explorer. Unfortunately, in spite of its ease-of-use, this built-in method has some serious shortcomings:

  • It always returns a 32×32 icon in preference to a 16×16 icon.
  • It returns a copy of the original, so you cannot use the overloaded constructor for Icon to get a different-sized icon.
  • In some cases, it will return a thumbnail representation of the file instead of the icon for the file type. While this behaviour can be desired, it is not always wanted.
  • The method is unhelpful in situations where the file does not exist, the path is virtual or a generalisation for the file type is desired.

Unfortunately, there is no managed API for achieving the additional functionality, but the process of invoking Win32 functions is quite simple. This article details a utility class for extracting icons, regardless of whether the file exists, and with the ability to select an icon size.

SHGetFileInfo Function

The SHGetFileInfo function is defined in Shell32.dll and can be used to extract an icon from any file system object:

[DllImport("shell32.dll")]
private static extern IntPtr SHGetFileInfo(
    string pszPath,
    uint dwFileAttributes,
    ref SHFILEINFO psfi,
    uint cbSizeFileInfo,
    uint uFlags
);

The SHFILEINFO structure holds various properties of the file, but we are only interested in hIcon, which can be passed to the Icon.FromHandle method. uFlags is the mechanism we use to determine the size of icon we want:

const uint SHGFI_ICON = 0x100;
const uint SHGFI_LARGEICON = 0x0;    // 32x32 pixels
const uint SHGFI_SMALLICON = 0x1;    // 16x16 pixels

These values must be OR’d together, together representing the type of information we want the function to return and the modifier for that behaviour. To abstract this from the user, we can define an enumeration to represent the two practical values of 0x100 and 0x101.

The completed method looks like so:

public static Icon GetIconForFile(string filename, ShellIconSize size) {
    SHFILEINFO shinfo = new SHFILEINFO();
    SHGetFileInfo(filename, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), (uint)size);
    return Icon.FromHandle(shinfo.hIcon);
}

ExtractIconEx Function and the Windows Registry

The above method is already an improvement over ExtractAssociatedIcon, but it still does not cater for a scenario where the file does not exist, is unknown or exists in some virtual location. If the only piece of information we have is the file extension (.pdf, .jpg, etc) then we need another mechanism. Information about the default icons for file types is stored in the Windows Registry, and can be accessed readily using the managed methods in the Microsoft.Win32.Registry class.

Every registered file extension has its own key in HKEY_CLASSES_ROOT. The default value of this key is the class name for the file type (one file type may have many extensions). We can usually get information about the default icon for the file type by examining the value of the key at HKEY_CLASSES_ROOT\[class name]\DefaultIcon. In some cases, there will not be a DefaultIcon node in the key for the class name, however. Often in these situations, there is a value defined at HKEY_CLASSES_ROOT\[class name]\CLSID. If this is the case, the information we want is located at HKEY_CLASSES_ROOT\CLSID\[clsid value]\DefaultIcon.

The format of this information can be given in two ways: The simplest case is a path to an ICO file. In most cases, however, the form is [path],[index] where ‘index’ is either an ordinal position or a resource identifier for the icon to use. These are intended to be passed to a Win32 function called ExtractIconEx:

[DllImport("Shell32.dll")]
public extern static int ExtractIconEx(
    string libName,
    int iconIndex,
    IntPtr[] largeIcon,
    IntPtr[] smallIcon,
    uint nIcons
);

This function extracts icons from resource libraries (i.e. DLL or EXE files containing icons). In our special, limited case, we will request a single icon of a specific size from a known path and index. The complete method looks like so:

public static Icon GetIconForExtension(string extension, ShellIconSize size) {
    RegistryKey keyForExt = Registry.ClassesRoot.OpenSubKey(extension);
    if (keyForExt == null) return null;

    string className = Convert.ToString(keyForExt.GetValue(null));
    RegistryKey keyForClass = Registry.ClassesRoot.OpenSubKey(className);
    if (keyForClass == null) return null;

    RegistryKey keyForIcon = keyForClass.OpenSubKey("DefaultIcon");
    if (keyForIcon == null) {
        RegistryKey keyForCLSID = keyForClass.OpenSubKey("CLSID");
        if (keyForCLSID == null) return null;

        string clsid = "CLSID\\"
            + Convert.ToString(keyForCLSID.GetValue(null))
            + "\\DefaultIcon";
        keyForIcon = Registry.ClassesRoot.OpenSubKey(clsid);
        if (keyForIcon == null) return null;
    }        

    string[] defaultIcon = Convert.ToString(keyForIcon.GetValue(null)).Split(',');
    int index = (defaultIcon.Length > 1) ? Int32.Parse(defaultIcon[1]) : 0;

    IntPtr[] handles = new IntPtr[1];
    if (ExtractIconEx(defaultIcon[0], index,
        (size == ShellIconSize.LargeIcon) ? handles : null,
        (size == ShellIconSize.SmallIcon) ? handles : null, 1) > 0)
        return Icon.FromHandle(handles[0]);
    else
        return null;
}

Callers must watch out for null values, because while every file has an iconic representation under Windows (i.e. the first mechanism never fails), not every file type has a known icon. Depending on the usage, different failsafe/fallback icons may be used in these circumstances.

Example Usage

Icon smallIcon = IconTools.GetIconForFile(
    @"C:\Windows\System32\notepad.exe",
    ShellIconSize.SmallIcon
);

Icon largeIcon = IconTools.GetIconForExtension(".html", ShellIconSize.LargeIcon);

Final Words

The built-in ExtractAssociatedIcon method has its applications, but a more flexible set of mechanisms are required in order to provide users with a complete and consistent visual representation for files and file types. By invoking some Win32 functions, we are able to close this important gap.

Download

There is a newer version of this code available here: IconTools Revisited