Используя перетаскивание, могу ли я заставить Treeview развернуть узел, над которым находится пользователь? - PullRequest
12 голосов
/ 10 ноября 2009

Вкратце:

Есть ли в .Net 2.0 какая-либо встроенная функция для расширения TreeNode с при наведении курсора во время операции перетаскивания?

Я использую C # в Visual Studio 2005.

Более подробно:

Я заполнил Treeview элемент управления многоуровневым, многоузловым деревом (например, организационной диаграммой или диалоговым окном файла / папки), и я хочу использовать перетаскивание для перемещения узлов в дереве.

Код перетаскивания работает хорошо, и я могу перетаскивать его на любой видимый узел, однако мне бы хотелось, чтобы мой элемент управления вел себя так же, как Windows Explorer при перетаскивании файлов через панель папок. В частности, я бы хотел, чтобы каждая папка открывалась, если она зависала в течение 1/2 секунды или около того.

Я начал разработку решения с использованием Threading и метода Sleep, но у меня возникают проблемы, и я задаюсь вопросом, было ли уже что-то на месте, если нет, то я опущусь и научусь использовать многопоточность ( пора, но я надеялся быстро выпустить это приложение)

Нужно ли мне писать собственный код для обработки расширения TreeNode при наведении курсора в режиме перетаскивания?

Ответы [ 2 ]

17 голосов
/ 10 ноября 2009

Вы можете использовать событие 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();
            }
        }
    }
}
0 голосов
/ 10 ноября 2009

EDIT

У меня есть новое решение, немного надуманное, но оно работает ... Он использует класс 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);
    }
}
...