views:

499

answers:

2

In brief:

Is there any built-in function in .Net 2.0 to Expand TreeNodes when hovered over whilst a Drag and drop operation is in progress?

I'm using C# in Visual Studio 2005.

In more detail:

I've populated a Treeview control with a multi levelled, multinoded tree (think organisational chart or file/folder dialog) and I want to use drag and drop to move nodes within the tree.

The drag drop code works well, and I can drop onto any visible node, however I would like my control to behave like Windows Explorer does when dragging files over the folder pane. Specifically, I'd like each folder to open if hovered over for 1/2 second or so.

I've begun developing a solution using Threading and a Sleep method but I'm running into problems and wondered if there was something in place already, if not I will knuckle down and learn how to use threading (it's about time, but I was hoping to get this app out quickly)

Do I need to write my own code to handle Expanding a TreeNode when hovered over in drag-drop mode?

A: 

EDIT

I have a new solution, a bit far-fetched, but it works... It uses a DelayedAction class to handle delayed execution of an action on the main thread :

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);
    }
}
Thomas Levesque
Won't work... I tried this. OnNodeMouseHover isn't fired while in drag drop mode.
Philip Wallace
See my updated answer
Thomas Levesque
+5  A: 

You can use the DragOver event; it fires repeatedly while you are dragging an object Opening after a delay can be done very easily with two extra variables that note the last object under the mouse and the time. No threading or other tricks required (lastDragDestination and lastDragDestinationTime in my example)

From my own code:

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();
      }
     }
    }
}
Zack
+1 for solution using DragOver. Nice "Am I still over the same node?" logic as well.
JeffH
Excellent, thanks very much. I didn't realised you could instantiate like that outside of a method bady so I've learned even more :-) One futher question though what is IconObject that you refer to? I derived a solution from your code without using that. Also, I have ignored the listview code as it isn't relevant to my situation. Is IconObject ListView related?
G-
This is from a project that lets one manage and associate different objects; it has businesses, people, computers, software, etc. Businesses contain departments, which contain people, computers, computers contain software, etc. Some objects are assigned to other objects (show in a TreeView), while others are unassigned and left in a general pool (such as a pool of unassigned computers), show in ListViews. All objects inherit from IconObject, my base class that defines common properties and logic. (The child classes define a list/tree image, a few extra properties, and little else)
Zack