在使用拖放时,我可以让 Treeview 展开用户悬停的节点吗?

Posted

技术标签:

【中文标题】在使用拖放时,我可以让 Treeview 展开用户悬停的节点吗?【英文标题】:Whilst using drag and drop, can I cause a Treeview to Expand the node over which the user hovers? 【发布时间】:2010-12-15 03:21:11 【问题描述】:

简而言之:

.Net 2.0 中是否有任何内置功能可以在拖放操作正在进行时将鼠标悬停在 TreeNodes 上?

我在 Visual Studio 2005 中使用 C#。

更详细:

我已经使用多层次、多节点的树(想想组织结构图或文件/文件夹对话框)填充了 Treeview 控件,并且我想使用拖放来移动树中的节点。

拖放代码运行良好,我可以拖放到任何可见节点上,但是我希望我的控件在将文件拖动到文件夹窗格时表现得像 Windows 资源管理器一样。具体来说,如果将鼠标悬停在 1/2 秒左右,我希望打开每个文件夹。

我已经开始使用ThreadingSleep 方法开发解决方案,但我遇到了问题,想知道是否已经有什么东西已经到位,如果没有,我会认真学习如何使用线程(是时候了,但我希望尽快推出这个应用程序)

我是否需要编写自己的代码来处理在拖放模式下悬停在 TreeNode 上时的扩展?

【问题讨论】:

【参考方案1】:

您可以使用 DragOver 事件;拖动对象时它会反复触发 延迟后打开可以很容易地通过两个额外的变量来完成,这些变量记录鼠标下的最后一个对象和时间。不需要线程或其他技巧(在我的示例中为 lastDragDestination 和 lastDragDestinationTime)

来自我自己的代码:

TreeNode lastDragDestination = null;
DateTime lastDragDestinationTime;

private void tvManager_DragOver(object sender, DragEventArgs e)

    IconObject dragDropObject = null;
    TreeNode dragDropNode = null;

    //always disallow by default
    e.Effect = DragDropEffects.None;

    //make sure we have data to transfer
    if (e.Data.GetDataPresent(typeof(TreeNode)))
    
        dragDropNode = (TreeNode)e.Data.GetData(typeof(TreeNode));
        dragDropObject = (IconObject)dragDropNode.Tag;
    
    else if (e.Data.GetDataPresent(typeof(ListViewItem)))
    
        ListViewItem temp (ListViewItem)e.Data.GetData(typeof(ListViewItem));
        dragDropObject = (IconObject)temp.Tag;
    

    if (dragDropObject != null)
    
        TreeNode destinationNode = null;
        //get current location
        Point pt = new Point(e.X, e.Y);
        pt = tvManager.PointToClient(pt);
        destinationNode = tvManager.GetNodeAt(pt);
        if (destinationNode == null)
        
            return;
        

        //if we are on a new object, reset our timer
        //otherwise check to see if enough time has passed and expand the destination node
        if (destinationNode != lastDragDestination)
        
            lastDragDestination = destinationNode;
            lastDragDestinationTime = DateTime.Now;
        
        else
        
            TimeSpan hoverTime = DateTime.Now.Subtract(lastDragDestinationTime);
            if (hoverTime.TotalSeconds > 2)
            
                destinationNode.Expand();
            
        
    

【讨论】:

+1 用于使用 DragOver 的解决方案。很好“我还在同一个节点上吗?”逻辑也是如此。 非常好,非常感谢。我没有意识到你可以在一个方法之外实例化,所以我学到了更多:-) 还有一个问题,虽然你指的是什么 IconObject?我从您的代码中得出了一个解决方案,而没有使用它。另外,我忽略了 listview 代码,因为它与我的情况无关。 IconObject ListView 是否相关? 这是来自一个项目,它可以让人们管理和关联不同的对象;它有业务、人员、计算机、软件等。业务包含部门,其中包含人员、计算机、计算机包含软件等。一些对象分配给其他对象(显示在 TreeView 中),而其他对象未分配并留在通用池(例如未分配的计算机池),显示在 ListViews 中。所有对象都继承自 IconObject,这是我定义公共属性和逻辑的基类。 (子类定义了一个列表/树图像、一些额外的属性等等)【参考方案2】:

编辑

我有一个新的解决方案,有点牵强,但它确实有效......它使用DelayedAction 类来处理主线程上操作的延迟执行:

DelayedAction<T>

public class DelayedAction<T>

    private SynchronizationContext _syncContext;
    private Action<T> _action;
    private int _delay;

    private Thread _thread;

    public DelayedAction(Action<T> action)
        : this(action, 0)
    
    

    public DelayedAction(Action<T> action, int delay)
    
        _action = action;
        _delay = delay;
        _syncContext = SynchronizationContext.Current;
    

    public void RunAfterDelay()
    
        RunAfterDelay(_delay, default(T));
    

    public void RunAfterDelay(T param)
    
        RunAfterDelay(_delay, param);
    

    public void RunAfterDelay(int delay)
    
        RunAfterDelay(delay, default(T));
    

    public void RunAfterDelay(int delay, T param)
    
        Cancel();
        InitThread(delay, param);
        _thread.Start();
    

    public void Cancel()
    
        if (_thread != null && _thread.IsAlive)
        
            _thread.Abort();
        
        _thread = null;
    

    private void InitThread(int delay, T param)
    
        ThreadStart ts =
            () =>
            
                Thread.Sleep(delay);
                _syncContext.Send(
                    (state) =>
                    
                        _action((T)state);
                    ,
                    param);
            ;
        _thread = new Thread(ts);
    

AutoExpandTreeView

public class AutoExpandTreeView : TreeView

    DelayedAction<TreeNode> _expandNode;

    public AutoExpandTreeView()
    
        _expandNode = new DelayedAction<TreeNode>((node) => node.Expand(), 500);
    

    private TreeNode _prevNode;
    protected override void OnDragOver(DragEventArgs e)
    
        Point clientPos = PointToClient(new Point(e.X, e.Y)); 
        TreeViewHitTestInfo hti = HitTest(clientPos);
        if (hti.Node != null && hti.Node != _prevNode)
        
            _prevNode = hti.Node;
            _expandNode.RunAfterDelay(hti.Node);
        
        base.OnDragOver(e);
    

【讨论】:

行不通...我试过了。在拖放模式下不会触发 OnNodeMouseHover。

以上是关于在使用拖放时,我可以让 Treeview 展开用户悬停的节点吗?的主要内容,如果未能解决你的问题,请参考以下文章

拖放时滚动(WPF)

拖放时将桌面图标移动到Windows窗体上?

拖放时更改鼠标光标

在ANDROID中相互拖放时合并Recyclerview中的项目?

UICollectionView 快速拖放

Qt - 在QGraphicScene中拖放时如何从项目中获取文本?