Search code examples
c#winformstreeview

Select portion of text in TreeView node


Visual Studio has this very nice "solution explorer", which I assume is some form of TreeView. When you rename files, it selects the file name only and not the extension.
enter image description here

However, treeviews don't seem to expose methods for selecting only parts of the text. When you treeview.BeginEdit(), it selects all of the text.
enter image description here

Is there a way to select just parts of the text in the node when it begins edit?


Solution

  • Create a custom TreeView and intercept the notification messages that the parent window of the control sends them back to it through the WM_REFLECT + WM_NOTIFY messages. The LParam property holds a handle to NMHDR structure that contains information about the notification message. We are interested here in TVN_BEGINLABELEDITW notifications to get a handle to the edit control by sending TVM_GETEDITCONTROL message and using the handle returned to lastly send EM_SETSEL message to set the selection range.

    [DesignerCategory("Code")]
    public class TreeViewEx : TreeView
    {
        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);
    
            if (m.Msg == WM_REFLECT_NOTIFY)
            {
                var st = Marshal.PtrToStructure<NMHDR>(m.LParam);
    
                if (st.code == TVN_BEGINLABELEDITW)
                {
                    IntPtr editPtr = SendMessage(Handle, TVM_GETEDITCONTROL, IntPtr.Zero, IntPtr.Zero);
    
                    if (editPtr != IntPtr.Zero)
                    {                        
                        int selStart = 0;
                        int selEnd = Path.GetFileNameWithoutExtension(SelectedNode.Text).Length;
                        SendMessage(editPtr, EM_SETSEL, (IntPtr)selStart, (IntPtr)selEnd);
                    }
                }
            }
        }
    
        const int WM_REFLECT_NOTIFY = 0x204E;
        const int TVM_GETEDITCONTROL = 0x0000110F;
        const int TVN_BEGINLABELEDITW = 0 - 400 - 59;
        const int EM_SETSEL = 0xB1;
    
        [StructLayout(LayoutKind.Sequential)]
        struct NMHDR
        {
            public IntPtr hwndFrom;
            public IntPtr idFrom;
            public int code;
        }
    
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
    }