分隔线拖放树节点c#WinForms

分隔线拖放树节点c#WinForms,c#,winforms,drag-and-drop,treeview,C#,Winforms,Drag And Drop,Treeview,我正在尝试复制以下拖放功能: 然而,我很难画出那条黑线。不知怎的,我最终得到了每个节点三行。我计算包含树节点的矩形的上、中、下部分,并根据计算结果绘制其中一条线。如果我使用treeView.Invalidate()的话,屏幕会闪烁太多,线条也看不见。我也尝试过使用graphics.clear(treeView.BackColor),但它也会清除我的树节点 演示: 代码树事件: public void ItemDrag(object sender, ItemDragEventArgs e)

我正在尝试复制以下拖放功能:

然而,我很难画出那条黑线。不知怎的,我最终得到了每个节点三行。我计算包含树节点的矩形的上、中、下部分,并根据计算结果绘制其中一条线。如果我使用treeView.Invalidate()的话,屏幕会闪烁太多,线条也看不见。我也尝试过使用graphics.clear(treeView.BackColor),但它也会清除我的树节点

演示:

代码树事件:

public void ItemDrag(object sender, ItemDragEventArgs e)
    {
        TreeNode selectedNode = (TreeNode)e.Item;
        if (e.Button == MouseButtons.Left && !selectedNode.Name.Contains("=") && !selectedNode.Name.Contains("#"))
            _treeView.DoDragDrop(selectedNode, DragDropEffects.Move);

    }

public void DragEnter(object sender, DragEventArgs e)
    {
        e.Effect = e.AllowedEffect;
    }

public void DragOver(object sender, DragEventArgs e)
    {
        try
        {
            if (!mousePoint.Equals(Cursor.Position))
            {
                mousePoint = Cursor.Position;
                bool droppable;
                TreeNode destinationNode = null;
                Point pointInTree = _treeView.PointToClient(new Point(e.X, e.Y));
                if (e.Data.GetDataPresent(typeof(TreeNode)))
                {
                    destinationNode = _treeView.GetNodeAt(pointInTree);
                    TreeNode souceNode = (TreeNode) e.Data.GetData(typeof(TreeNode));
                    droppable = true;
                }

                else droppable = false;

                e.Effect = droppable ? DragDropEffects.Move : DragDropEffects.None;

                Point pt = _treeView.PointToClient(new Point(e.X, e.Y));
                _treeView.SelectedNode = _treeView.GetNodeAt(pt);

                int dropLocation = CalculateNodeHooverArea(destinationNode, pointInTree);
                if(_dropLocation!=dropLocation)
                {
                    switch (dropLocation)
                {
                    case 0:
                        DrawLine(NodePosition.Above);
                        break;
                    case 2:
                        DrawLine(NodePosition.Below);
                        break;
                    case 1:
                        DrawLine(NodePosition.In);
                        break;
                }
                    _dropLocation = dropLocation;
                }

            }
        }
        catch (Exception exception)
        {
            Debug.WriteLine("TreeViewDragOverEvent: " + exception.Message);
        }
    }
public void DragDrop(object sender, DragEventArgs e)
    {
        try
        {
            Point targetPoint = _treeView.PointToClient(new Point(e.X, e.Y));
            TreeNode targetNode = _treeView.GetNodeAt(targetPoint);
            TreeNode draggedNode = (TreeNode)e.Data.GetData(typeof(TreeNode));
            if (!draggedNode.Equals(targetNode) && !draggedNode.Nodes.Find(targetNode.Name, true).Any() &&
                targetNode.Parent != null && !targetNode.Name.Contains("=") && !targetNode.Name.Contains("#"))
            {
                int nodeLocation = CalculateNodeHooverArea(targetNode, targetPoint);

                if (e.Effect == DragDropEffects.Move)
                    draggedNode.Remove();

                switch (nodeLocation)
                {
                    case 0:
                        if (targetNode.Parent != null)
                            targetNode.Parent.Nodes.Insert(targetNode.Index, draggedNode);
                        break;
                    case 1:
                        targetNode.Nodes.Add(draggedNode);
                        break;
                    case 2:
                        if (targetNode.Parent != null)
                            targetNode.Parent.Nodes.Insert(targetNode.Index + 1, draggedNode);
                        break;
                }
            }
            //SaveMemento();
            _treeView.Invalidate();
        }
        catch (Exception exception)
        {
            Debug.WriteLine(exception.Message);
        }
    }
处理线条绘制的方法:

    private void DrawLine(NodePosition position)
    {
        Graphics g = _treeView.CreateGraphics();
        Pen customPen = new Pen(Color.DimGray, 1) { DashStyle = DashStyle.Dash };
        if (position == NodePosition.Above)
            g.DrawLine(customPen, new Point(0, _treeView.SelectedNode.Bounds.Top),
                new Point(_treeView.Width - 4, _treeView.SelectedNode.Bounds.Top));

        else if (position == NodePosition.Below)
            g.DrawLine(customPen, new Point(0, _treeView.SelectedNode.Bounds.Bottom),
                new Point(_treeView.Width - 4, _treeView.SelectedNode.Bounds.Bottom));

        else
        {
            g.DrawLine(customPen, new Point(_treeView.SelectedNode.Bounds.X + _treeView.SelectedNode.Bounds.Width,
                _treeView.SelectedNode.Bounds.Y +_treeView.SelectedNode.Bounds.Height / 2),
                new Point(_treeView.Width - 4, _treeView.SelectedNode.Bounds.Y + _treeView.SelectedNode.Bounds.Height / 2));
        }

        customPen.Dispose();
        g.Dispose();
    }
这个问题可以解决吗?或者我应该研究另一种显示此类信息的方式吗?(例如,工具提示)

Invalidate()
是在不执行布局算法的情况下重新绘制控件时调用的正确方法

问题似乎是您通过调用
\u treeView.CreateGraphics()
使用了一个新的图形对象

您可以尝试(在绘制旧分隔线的位置)或使用双缓冲方法(我更愿意用于完全自定义绘制的控件),但这可能值得一试:更新,如果不完全覆盖控件的绘制,这将不起作用


当我想到这个问题时。。。为什么不存储最后一行的坐标,一旦画出另一行,就用相同的笔大小和形状,但背景颜色,再次“擦除”(覆盖)它。我知道,这读起来有点脏,但最终都是关于不引人注意和性能良好的黑客(比如考虑游戏引擎中的渲染技巧)。一旦用户滚动或做了其他任何事情,由于坐标确实发生了变化而导致过度绘制失败,您也不需要过度绘制,因为操作系统会重新绘制整个控件。

您真的不必每次靠近新树节点时都画线。在我看来,每次拖放事件时都要使用图形对象来绘制和擦除某些内容,这不是一个干净的解决方案。相反,这里有一种我称之为更“干净”的方法来实现同样的目标-

  • 在Windows窗体上,绘制一条线作为静态控件,并最初将其可见性设置为false。现在你怎么画一条线?添加标签控件、添加实体或三维边框、清除文本并设置固定高度(可以是2像素,也可以根据需要设置宽度)。将此标签放置在表单左下角的某个位置,在该位置它不会以UI上其他控件的方式出现

  • 与其使用
    DrawLine
    方法,不如称之为
    ShowLine
    或其他方法。在该方法中,根据TreeView节点的位置,动态地将该新标签(实际上是一条线)的X和Y位置设置为新位置,并使其可见。因此,每次在DragOver上,它都会在不同的X和Y位置上可见,并为您提供所需的相同体验

  • 在节点内放置项目后(即,拖放操作完成),将此线条标签的可见性设置为false,并将其X和Y位置设置回原始位置(本例中为左下角)


如果我要尝试您的第一个解决方案,我必须有一个对以前访问的区域的引用,计算它(或在此之前执行此操作),然后使其无效。对我来说,这似乎是一个很大的工作,相当肮脏。我也尝试过使用treeView组件的OnPaint()事件,但它没有公开是的,你这样说是有道理的。我会试试看,让你知道进展如何。非常感谢。这确实是一个不那么消耗的选择。我会试一试的。是的,这确实很容易,我仍然不知道我怎么从来没有想过。非常感谢。现在干净多了。可怜的问题。源代码缺少依赖的方法和变量(CalculateNodeHooverArea、mousepoint等)@stigzler无关注释。这个问题已经2岁了,已经被标记为已回答。@stigzler不知道你是如何解决的,这个问题很好而且很全面,它确实帮助了我。