Allow multi-select in a .NET TreeView

65,853

Solution 1

We did this in a WTL project once, but the basic work needed is the same for .NET. To achieve a multiple selection tree control, you will need to draw the tree items yourself and override the keyboard and mouse handling. You will also need to maintain your own list of items that are selected.

Don't forget to consider selection rules (are parents and children allowed, for example), and don't forget to implement the keyboard shortcuts including selection using Ctrl, Shift, and Ctrl+Shift, as well as the Spacebar for selecting/deselecting.

Solution 2

TreeView.CheckBoxes

Solution 3

Are check-boxes an option? or do you want the select like you get in a list box?

  • checkboxes are built in
  • select like you get in a list box requires a custom tree control

There is a multi-select tree control available on CodeProject:Multi-Select Tree View

Solution 4

The simplest solution would be to extend the existing TreeView control shipped with the framework and override the OnBeforeSelect and OnAfterSelect methods with logic to capture multiple selections.

An example can be found here: http://www.arstdesign.com/articles/treeviewms.html

Solution 5

The below code will allow you to adjust the background colour you use, to ensure that all selected nodes are highlighted.

protected override void WndProc(ref Message m)
{
    switch (m.Msg) {
        // WM_REFLECT is added because WM_NOTIFY is normally sent just
        // to the parent window, but Windows.Form will reflect it back
        // to us, MFC-style.
        case Win32.WM_REFLECT + Win32.WM_NOTIFY: {
            Win32.NMHDR nmhdr = (Win32.NMHDR)m.GetLParam(typeof(Win32.NMHDR));
            switch((int)nmhdr.code) {
                case Win32.NM_CUSTOMDRAW:
                    base.WndProc(ref m);
                    Win32.NMTVCUSTOMDRAW nmTvDraw = (Win32.NMTVCUSTOMDRAW)m.GetLParam(typeof(Win32.NMTVCUSTOMDRAW));
                    switch (nmTvDraw.nmcd.dwDrawStage) {
                        case Win32.CDDS_ITEMPREPAINT:
                            // Find the node being painted.
                            TreeNode n = TreeNode.FromHandle(this, nmTvDraw.nmcd.lItemlParam);
                            if (allSelected.Contains(n))
                                // Override its background colour.
                                nmTvDraw.clrTextBk = ColorTranslator.ToWin32(SystemColors.Highlight);
                            m.Result = (IntPtr)Win32.CDRF_DODEFAULT;  // Continue rest of painting as normal
                            break;
                    }
                    Marshal.StructureToPtr(nmTvDraw, m.LParam, false);  // copy changes back
                    return;
            }
            break;
        }
    }
    base.WndProc(ref m);
}

// WM_NOTIFY notification message header.
[System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential)]
public class NMHDR
{
    private IntPtr hwndFrom;
    public IntPtr idFrom;
    public uint code;
}

[StructLayout(LayoutKind.Sequential)]
public struct NMCUSTOMDRAW
{
    public NMHDR hdr;
    public int dwDrawStage;
    public IntPtr hdc;
    public RECT rc;
    public IntPtr dwItemSpec;
    public int uItemState;
    public IntPtr lItemlParam;
}

[StructLayout(LayoutKind.Sequential)]
public struct NMTVCUSTOMDRAW
{
    public NMCUSTOMDRAW nmcd;
    public int clrText;
    public int clrTextBk;
    public int iLevel;
}

public const int CDIS_SELECTED = 0x0001;
public const int CDIS_FOCUS = 0x0010;
public const int CDDS_PREPAINT = 0x00000001;
public const int CDDS_POSTPAINT = 0x00000002;
public const int CDDS_PREERASE = 0x00000003;
public const int CDDS_POSTERASE = 0x00000004;
public const int CDDS_ITEM = 0x00010000;  // item specific 
public const int CDDS_ITEMPREPAINT = (CDDS_ITEM | CDDS_PREPAINT);
public const int CDDS_ITEMPOSTPAINT = (CDDS_ITEM | CDDS_POSTPAINT);
public const int CDDS_ITEMPREERASE = (CDDS_ITEM | CDDS_PREERASE);
public const int CDDS_ITEMPOSTERASE = (CDDS_ITEM | CDDS_POSTERASE);
public const int CDDS_SUBITEM = 0x00020000;
public const int CDRF_DODEFAULT = 0x00000000;
public const int CDRF_NOTIFYITEMDRAW = 0x00000020;
public const int CDRF_NOTIFYSUBITEMDRAW = 0x00000020;  // flags are the same, we can distinguish by context

public const int WM_USER = 0x0400;
public const int WM_NOTIFY = 0x4E;
public const int WM_REFLECT = WM_USER + 0x1C00;
Share:
65,853

Related videos on Youtube

Hello all
Author by

Hello all

A developer for a software company in Waterloo, ON. My current project is building development tools (notably an IDE built on Eclipse) for a proprietary language you've never heard of. It makes things interesting. I've done quite a bit of Java and C# development and a smattering of C/C++. At home I get to mess with stuff like Python, Ubuntu and Android development. I'm also part of the local Agile/Lean software community, LoCo contact for Ubuntu Canada and I'm involved in running Kwartzlab, the local hackerspace.

Updated on August 19, 2020

Comments

  • Hello all
    Hello all over 3 years

    I'm stuck in .NET 2.0 Windows Forms.

    It doesn't look like the ability to select multiple nodes exists in the standard TreeView control.

    I'm trying to do this for a context menu selection. So check boxes aren't an acceptable UI paradigm here.

    What's the best way to provide that very necessary functionality?

  • Hello all
    Hello all over 15 years
    That's not really what I'm looking for. Every other tree view in Windows allows you to ctrl-click select multiple entries. And driving a context menu off of checky boxes doesn't make any sense.
  • John Rudy
    John Rudy over 15 years
    I can't find an example of a ctrl-click tree view anywhere in the "standard" Windows OS environment. Can you provide an example? I agree that a context menu from checks isn't very sensible, but I don't think expecting users to ctrl+click is either. (A lot of users have trouble with that, in my exp.)
  • Admin
    Admin over 15 years
    For the standard Windows Forms treeview, that's how you select more than one node in the tree.
  • Hello all
    Hello all over 15 years
    Okay, "standard" may be the wrong word. Take, for example, the Solution Explorer in Visual Studio.
  • Admin
    Admin over 15 years
    The problem is that the "standard" controls you get to play with and use for free (the ones that ship with VS) aren't necessarily the ones you see actually being used in Windows. You have three options 1) checkboxes 2) write your own 3) buy one. Sucks, I know. Its easy to do in WPF!
  • synhershko
    synhershko over 14 years
    -1 : took me much less than 5 minutes to find a simple free one and implement it in my solution. Here are 2 samples: codeproject.com/KB/tree/MWTreeViewv2010.aspx, arstdesign.com/articles/treeviewms.html. Not perfect, but works and could be improved if you know your way around it.
  • Oliver Bock
    Oliver Bock about 12 years
    You can get around doing the drawing yourself by intercepting WM_NOTIFY and adjusting the background colour for selected nodes. I have added an answer so I can include the code.
  • Jeff Yates
    Jeff Yates about 12 years
    @OliverBock: That's certainly the simplest way if all you need is a background color change.
  • Victor Zakharov
    Victor Zakharov over 10 years
    @synhershko: Articles are dated 2004 and 2002 respectively, I am sure there are better ways to implement same thing today.
  • Larry
    Larry over 10 years
    Despite it is old (2004), the first implementation is reliable, and well coded. It fits in one class that inherits TreeView and is very simple to adapt.