c# - How to disable text selection on a TreeView (WinForms .NET) - Stack Overflow

I am creating a custom treeview control for my winform application. In doing this, I noticed that there

I am creating a custom treeview control for my winform application. In doing this, I noticed that there is a really annoying feature of the treeview I am having trouble removing.

Upon selecting a node (Mouse Down), the text of that node is highlighted, I don't want this. However, I also don't to use drawing of any kind. I am looking for a way to do this without having to set the style to owner drawn and manually drawing the text of the nodes being added.

public class CustomTreeView : TreeView
{
    // Fields for the custom checkbox images
    private Image _checkedImage = Properties.Resources.CheckIcon;
    private Image _uncheckedImage = Properties.Resources.UnCheckIcon;
    private Image _intermediateImage = Properties.Resources.IntermediateCheckIcon;
    private ImageList _imageList;

    public CustomTreeView()
    {
        this.CheckBoxes = false;

        _imageList = new ImageList();
        _imageList.ImageSize = new Size(26, 26);
        _imageList.Images.Add("Unchecked", _uncheckedImage);
        _imageList.Images.Add("Checked", _checkedImage);
        _imageList.Images.Add("Intermediate", _intermediateImage);
        this.ImageList = _imageList;

        this.Font = new Font("Gadugi", 12f);
        this.ForeColor = Color.DodgerBlue;
        this.BackColor = SystemColors.Control;
        this.BorderStyle = BorderStyle.None;
        this.Scrollable = true;

        this.ShowLines = false;

        this.ShowRootLines = false;
        this.ShowNodeToolTips = true;

        this.ShowPlusMinus = false;

        this.HideSelection = false;
        this.ItemHeight = 30; 
    }

    private int GetEffectiveState(TreeNode node)
    {
        if (node.Nodes.Count == 0)
        {
            return node.Checked ? 1 : 0;
        }
        else
        {
            bool allChecked = true;
            bool anyChecked = false;
            foreach (TreeNode child in node.Nodes)
            {
                int childState = GetEffectiveState(child);
                if (childState == 0)
                    allChecked = false;
                else if (childState == 1)
                    anyChecked = true;
                else
                {
                    allChecked = false;
                    anyChecked = true;
                }
            }
            if (allChecked)
                return 1; 
            else if (anyChecked)
                return 2; 
            else
                return 0; 
        }
    }

    private void UpdateStateImageIndex(TreeNode node)
    {
        node.ImageIndex = GetEffectiveState(node); 
        node.SelectedImageIndex = node.ImageIndex;
    }

    private void SetNodeChecked(TreeNode node, bool checkedState)
    {
        node.Checked = checkedState;
        foreach (TreeNode child in node.Nodes)
        {
            SetNodeChecked(child, checkedState);
        }
    }

    private void UpdateStateIndices(TreeNode node)
    {
        while (node != null)
        {
            UpdateStateImageIndex(node);
            node = node.Parent;
        }
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        TreeViewHitTestInfo hitTestInfo = HitTest(e.Location);
        if (hitTestInfo.Location == TreeViewHitTestLocations.Image)
        {
            TreeNode node = hitTestInfo.Node;
            if (node != null)
            {
                int currentState = GetEffectiveState(node);
                if (currentState == 1)
                {
                    SetNodeChecked(node, false);
                }
                else
                {
                    SetNodeChecked(node, true);
                }
                UpdateStateIndices(node);
            }
        }
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
        base.OnMouseMove(e);
        TreeViewHitTestInfo hitTestInfo = HitTest(e.Location);
        if (hitTestInfo.Location == TreeViewHitTestLocations.Image)
        {
            this.Cursor = Cursors.Hand;
        }
        else
        {
            this.Cursor = Cursors.Default;
        }
    }

    protected override void OnAfterCheck(TreeViewEventArgs e)
    {
        base.OnAfterCheck(e);
        UpdateStateIndices(e.Node);
    }

    private void UpdateStateIndicesRecursive(TreeNode node)
    {
        node.Expand();
        foreach (TreeNode child in node.Nodes)
        {
            UpdateStateIndicesRecursive(child);
        }
        UpdateStateImageIndex(node);
    }

    public void UpdateAllStateIndices()
    {
        foreach (TreeNode node in this.Nodes)
        {
            UpdateStateIndicesRecursive(node);
        }
    }

    protected override void OnBeforeCollapse(TreeViewCancelEventArgs e)
    {
        e.Cancel = true;
    }

    protected override void OnKeyDown(KeyEventArgs e)
    {
        if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down || e.KeyCode == Keys.Left || e.KeyCode == Keys.Right)
        {
            e.Handled = true;
        }
        base.OnKeyDown(e);
    }

    protected override void OnBeforeSelect(TreeViewCancelEventArgs e)
    {
        e.Cancel = true;
        base.OnBeforeSelect(e);
    }
}

I am creating a custom treeview control for my winform application. In doing this, I noticed that there is a really annoying feature of the treeview I am having trouble removing.

Upon selecting a node (Mouse Down), the text of that node is highlighted, I don't want this. However, I also don't to use drawing of any kind. I am looking for a way to do this without having to set the style to owner drawn and manually drawing the text of the nodes being added.

public class CustomTreeView : TreeView
{
    // Fields for the custom checkbox images
    private Image _checkedImage = Properties.Resources.CheckIcon;
    private Image _uncheckedImage = Properties.Resources.UnCheckIcon;
    private Image _intermediateImage = Properties.Resources.IntermediateCheckIcon;
    private ImageList _imageList;

    public CustomTreeView()
    {
        this.CheckBoxes = false;

        _imageList = new ImageList();
        _imageList.ImageSize = new Size(26, 26);
        _imageList.Images.Add("Unchecked", _uncheckedImage);
        _imageList.Images.Add("Checked", _checkedImage);
        _imageList.Images.Add("Intermediate", _intermediateImage);
        this.ImageList = _imageList;

        this.Font = new Font("Gadugi", 12f);
        this.ForeColor = Color.DodgerBlue;
        this.BackColor = SystemColors.Control;
        this.BorderStyle = BorderStyle.None;
        this.Scrollable = true;

        this.ShowLines = false;

        this.ShowRootLines = false;
        this.ShowNodeToolTips = true;

        this.ShowPlusMinus = false;

        this.HideSelection = false;
        this.ItemHeight = 30; 
    }

    private int GetEffectiveState(TreeNode node)
    {
        if (node.Nodes.Count == 0)
        {
            return node.Checked ? 1 : 0;
        }
        else
        {
            bool allChecked = true;
            bool anyChecked = false;
            foreach (TreeNode child in node.Nodes)
            {
                int childState = GetEffectiveState(child);
                if (childState == 0)
                    allChecked = false;
                else if (childState == 1)
                    anyChecked = true;
                else
                {
                    allChecked = false;
                    anyChecked = true;
                }
            }
            if (allChecked)
                return 1; 
            else if (anyChecked)
                return 2; 
            else
                return 0; 
        }
    }

    private void UpdateStateImageIndex(TreeNode node)
    {
        node.ImageIndex = GetEffectiveState(node); 
        node.SelectedImageIndex = node.ImageIndex;
    }

    private void SetNodeChecked(TreeNode node, bool checkedState)
    {
        node.Checked = checkedState;
        foreach (TreeNode child in node.Nodes)
        {
            SetNodeChecked(child, checkedState);
        }
    }

    private void UpdateStateIndices(TreeNode node)
    {
        while (node != null)
        {
            UpdateStateImageIndex(node);
            node = node.Parent;
        }
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        TreeViewHitTestInfo hitTestInfo = HitTest(e.Location);
        if (hitTestInfo.Location == TreeViewHitTestLocations.Image)
        {
            TreeNode node = hitTestInfo.Node;
            if (node != null)
            {
                int currentState = GetEffectiveState(node);
                if (currentState == 1)
                {
                    SetNodeChecked(node, false);
                }
                else
                {
                    SetNodeChecked(node, true);
                }
                UpdateStateIndices(node);
            }
        }
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
        base.OnMouseMove(e);
        TreeViewHitTestInfo hitTestInfo = HitTest(e.Location);
        if (hitTestInfo.Location == TreeViewHitTestLocations.Image)
        {
            this.Cursor = Cursors.Hand;
        }
        else
        {
            this.Cursor = Cursors.Default;
        }
    }

    protected override void OnAfterCheck(TreeViewEventArgs e)
    {
        base.OnAfterCheck(e);
        UpdateStateIndices(e.Node);
    }

    private void UpdateStateIndicesRecursive(TreeNode node)
    {
        node.Expand();
        foreach (TreeNode child in node.Nodes)
        {
            UpdateStateIndicesRecursive(child);
        }
        UpdateStateImageIndex(node);
    }

    public void UpdateAllStateIndices()
    {
        foreach (TreeNode node in this.Nodes)
        {
            UpdateStateIndicesRecursive(node);
        }
    }

    protected override void OnBeforeCollapse(TreeViewCancelEventArgs e)
    {
        e.Cancel = true;
    }

    protected override void OnKeyDown(KeyEventArgs e)
    {
        if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down || e.KeyCode == Keys.Left || e.KeyCode == Keys.Right)
        {
            e.Handled = true;
        }
        base.OnKeyDown(e);
    }

    protected override void OnBeforeSelect(TreeViewCancelEventArgs e)
    {
        e.Cancel = true;
        base.OnBeforeSelect(e);
    }
}
Share Improve this question asked Mar 22 at 1:40 VoidSharpVoidSharp 455 bronze badges 2
  • I think i understand what you mean, I made a mockup of this and it worked by checking hitlocation of the label, and right of the label and returning before the highlight can be displayed. However, my treeview as seen in the code contains its own image checkboxes. When selecting those (mousedown), the highlight is also shown. If I handle TreeViewHitTestLocations.Image, it prevents it but the functionality of the checkbox is lost (requires 2 clicks or more to get a toggle) Sidenote: This is not a commercial app in the sense of being sold, however it will be distributed among my team at work. – VoidSharp Commented Mar 22 at 19:43
  • @Jimi If you'd like to provide a better solution than the one I made, feel free and I'll accept it to give you credit. Otherwise, tomorrow I will accept my own. Thank you again! – VoidSharp Commented Mar 22 at 20:09
Add a comment  | 

2 Answers 2

Reset to default 1

The effect you see is caused by the attempt to solve an issue with the Win32 Control: when you show a ContextMenuStrip clicking on a TreeNode, the TreeView enters an undetermined state, in relation to that Node.

The .NET code forces the redraw of the Node, which would otherwise remain permanently selected in this scenario, sending a TVM_SELECTITEM message, specifying TVGN_DROPHILITE (the state of a Node when a Drag&Drop operation is performed).
You can override WndProc and trap WM_LBUTTONDOWN to change this behavior. When the left Mouse button is down on an TreeNode, you test whether the pointer is over an Icon a State Icon or the Item itself (other location can be tested as well). In the first two cases, you send a TVM_SELECTITEM message, specifying TVGN_CARET, which sets the current selection (so the parent Control receives selection changing and selection changed events), but no highlight is performed because you have e.Cancel = true in OnBeforeSelect.

Otherwise, you call OnMouseDown() with new MouseEventArgs values.

The rest can remain as it is, I assume, because nothing else appears to interfere with this setup.
See whether this can solve the issue at hand.


As noted, depending on the use-case, the WM_LBUTTONDOWN override may require a reimplementation of some of the safety features, which handle some edge cases, that are left out when the override is in place. This cannot be done directly because internal methods / properties are used in the source code


Here, I'm using the TVM_HITTEST message to test the Mouse location.
You can also use the Control's HitTest() method.

public class CustomTreeView : TreeView {
    // [...]

    protected override void OnBeforeSelect(TreeViewCancelEventArgs e) {
        e.Cancel = true;
        base.OnBeforeSelect(e);
    }

    protected override void WndProc(ref Message m) {
        switch (m.Msg) {
            case WM_LBUTTONDOWN:
                var pos = new Point((short)m.LParam.ToInt32(), 
                                   (m.LParam.ToInt32() >> 16) & 0xFFFF);
                var hitInfo = new TVHITTESTINFO(pos);
                SendMessage(Handle, TVM_HITTEST, 0, hitInfo);

                if ((hitInfo.flags & TVHT_ONITEMICON) != 0 &&
                    (hitInfo.flags & TVHT_ONITEMSTATEICON) != 0) {
                    SendMessage(Handle, TVM_SELECTITEM, TVGN_CARET, IntPtr.Zero);
                }
                else {
                    OnMouseDown(new MouseEventArgs(MouseButtons.Left, 1, pos.X, pos.Y, 0));
                }
                m.Result = IntPtr.Zero;
                return;
        }
        base.WndProc(ref m);
    }

    // Win32 declarations
    const int WM_LBUTTONDOWN = 0x0201;
    const int TVHT_ONITEMICON = 0x0002;
    const int TVHT_ONITEMSTATEICON = 0x0040;
    const int TVGN_CARET = 0x0009;
    const uint TVM_HITTEST = 0x1111;
    const uint TVM_SELECTITEM = 0x110B;

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    public class TVHITTESTINFO {
        public int pt_x;
        public int pt_y;
        public int flags = 0;
        public IntPtr hItem = IntPtr.Zero;
        public TVHITTESTINFO(Point p) { pt_x = p.X; pt_y = p.Y; }
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern int SendMessage(IntPtr hWnd, uint uMsg, int wParam, TVHITTESTINFO lParam);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern int SendMessage(IntPtr hWnd, uint uMsg, int wParam, IntPtr lParam);

    // [...]
}

After some insight from Jimi in the comments of the post. I have come to a solution using WndProc.

Below was my solution to resolve this issue but as a side note, as mentioned by Jimi

this requires strict testing, and, in the end, also might require (depending on the use-case) re-implementing some of the safety features (that handle some edge cases) that are left out when the override is in place

private const int WM_LBUTTONDOWN = 0x0201; // Left mouse button down

protected override void WndProc(ref Message m)
{
    //Checks if the Left mouse button down is being sent as the message
    if (m.Msg == WM_LBUTTONDOWN)
    {
        //Gathers click location for HitTest
        int x = (int)(m.LParam.ToInt64() & 0xFFFF);
        int y = (int)((m.LParam.ToInt64() >> 16) & 0xFFFF);
        Point clickPoint = new Point(x, y);

        TreeViewHitTestInfo hitTestInfo = HitTest(clickPoint);
        if (hitTestInfo.Node != null)
        {
            //For my case, I am using a Image as a checkbox icon, this section handles the selection of this.
            if (hitTestInfo.Location == TreeViewHitTestLocations.Image)
            {
                int currentState = GetEffectiveState(hitTestInfo.Node);

                // Toggle check state
                SetNodeChecked(hitTestInfo.Node, currentState != 1);
                UpdateStateIndices(hitTestInfo.Node);

                // Suppress selection
                m.Msg = 0; // Block the message
                return;
            } //This section is used for handling the click of the label, or just right of the label (preventing highlight)
            else if (hitTestInfo.Location == TreeViewHitTestLocations.Label || 
                hitTestInfo.Location == TreeViewHitTestLocations.RightOfLabel)
            {
                return; // Do nothing, preventing selection highlight
            }
        }
    }

    base.WndProc(ref m);
}

I did my best to provide context of what is happening on each line. Thanks again Jimi

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744327437a4568719.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信