Editable TreeView

As I was updating the COM Shim Wizards (the new version should be published in a couple of weeks), I found I had a need for an editable TreeView. There is only limited support for editing in the standard TreeView. So, I did what all sensible people do – I LiveSearched for it to see if I could reuse something out there. To my surprise, there really wasn't anything (I double-checked with Google). However, I did find an article on building a better combobox, so I borrowed a couple of the techniques in that.

In the end, it turned out to be pretty simple. Obviously, you need to derive a class from TreeView, and override the WndProc so that you can hijack the messages of interest before (or instead of) the parent implementation. I wanted to trap the user double-clicking the left mouse button, and also pressing the delete key.

My requirements were pretty simple: if the user double-clicks an existing root node in the tree, I want to add a child to that node. If they double-click somewhere that's not on a root node, I want to add a new root node to the end of the tree. This scheme only supports 2 levels of node – which is exactly what I wanted for the wizard dialog – but, clearly, you could expand this to suit your own requirements. If they press Delete, I want to delete the selected node. That's it. Implementation was also pretty simple – the significant code is listed below:

public class TreeViewEx : TreeView

{

    protected override void WndProc(ref Message m)

    {

        switch (m.Msg)

        {

            case (int)Win32Messages.WM_LBUTTONDBLCLK :

                int x = Win32Macros.LoWord(m.LParam.ToInt32());

                int y = Win32Macros.HiWord(m.LParam.ToInt32());

                TreeViewHitTestInfo info = this.HitTest(x, y);

                if (info.Node != null)

                {

                    // They've clicked on a node.

                    TreeNode hitNode = info.Node;

                    // If this is a root node, add a child to it.

                    // Otherwise, do nothing.

                    if (hitNode.Level == 0)

                    {

                        hitNode = info.Node;

                        TreeNode newNode = hitNode.Nodes.Add("New Child Node");

                        this.SelectedNode = newNode;

                    }

                }

                else

                {

                    // They've clicked somewhere other than a node.

                    // Add a root node to the end of the tree.

                    this.Nodes.Add("New Root Node");

                }

                return;

            case (int)Win32Messages.WM_KEYDOWN :

                // If they press Delete, we'll delete the selected node.

                if (m.WParam.ToInt32() == Win32VirtualKeys.VK_DELETE)

                {

                    if (this.SelectedNode != null)

                    {

                        this.SelectedNode.Remove();

                    }

                }

                return;

        }

        base.WndProc(ref m);

    }

}

The above listing is my entire custom TreeView class – it doesn't get much simpler than this. You can see from this that I've also defined some helpers: Win32Messages, Win32VirtualKeys, and Win32Macros. They look like this:

public enum Win32Messages

{

    // etc

    WM_KEYDOWN = 0x100,

    WM_KEYFIRST = 0x100,

    WM_KEYLAST = 0x108,

    WM_KEYUP = 0x101,

    WM_LBUTTONDBLCLK = 0x203,

    WM_LBUTTONDOWN = 0x201,

    WM_LBUTTONUP = 0x202,

    // etc

}

class Win32VirtualKeys

{

    // etc

    public const uint VK_INSERT = 0x2D;

    public const uint VK_DELETE = 0x2E;

    public const uint VK_HELP = 0x2F;

    // etc

}

Of course, I got most of these from www.pinvoke.net. The macros were a bit trickier – I hacked these together from the C++ sources in windef.h, winuser.h:

public class Win32Macros

{

    public static int MakeLong(int LoWord, int HiWord)

    {

        return (HiWord << 16) | (LoWord & 0xffff);

    }

    public static IntPtr MakeLParam(int LoWord, int HiWord)

    {

        return (IntPtr)((HiWord << 16) | (LoWord & 0xffff));

    }

   

    public static int HiWord(int Number)

    {

        return (Number >> 16) & 0xffff;

    }

   

    public static int LoWord(int Number)

    {

        return Number & 0xffff;

    }

}