/// <summary> /// Gets an enumerable of all dragged items. /// </summary> /// <param name="dataObject">The <see cref="IDataObject"/> which defines the dragged items.</param> /// <param name="control">The control from which items will be derived.</param> /// <returns>Returns an enumerable of all dragged items which will be empty if no items were found.</returns> internal static IEnumerable <ToolboxTreeNodeModel> GetDraggedModels(IDataObject dataObject, TreeListBox control) { string format = TreeListBox.ItemDataFormat; if (dataObject.GetDataPresent(format)) { string data = dataObject.GetData(format) as string; if (!string.IsNullOrWhiteSpace(data)) { foreach (var fullPath in SplitItemFullPaths(data)) { if (control.GetItemByFullPath(fullPath) is ToolboxTreeNodeModel itemModel) { yield return(itemModel); } } } } }
/// <summary> /// Notifies that a drag event occurred over a target item, requests that appropriate updates are made to the supplied <c>DragEventArgs</c>, /// and requests that the allowed drop area is returned for visual target feedback. /// </summary> /// <param name="e">The <see cref="DragEventArgs"/> whose effects should be updated.</param> /// <param name="targetControl">The target control, over which the event occurred</param> /// <param name="targetItem">The target item, which could be <see langword="null"/> if dragging below the last tree item.</param> /// <param name="dropArea">A <see cref="TreeItemDropArea"/> indicating the drop area over the target item.</param> /// <returns> /// A <see cref="TreeItemDropArea"/> indicating the allowed drop area, which will be used for visual feedback to the end user. /// Return <c>TreeItemDropArea.None</c> for no visual feedback. /// </returns> /// <remarks> /// The default implementation of this method sets the <c>e.Effects</c> to <c>DragDropEffects.None</c> and returns that no drop area is allowed. /// Override this method if you wish to support dropping and add logic to properly handle the dragged data. /// </remarks> public override TreeItemDropArea OnDragOver(DragEventArgs e, TreeListBox targetControl, object targetItem, TreeItemDropArea dropArea) { // If the drag is over an item and there is item data present... if ((targetItem != null) && (dropArea != TreeItemDropArea.None) && (e.Data.GetDataPresent(TreeListBox.ItemDataFormat))) { var fullPaths = e.Data.GetData(TreeListBox.ItemDataFormat) as string; if (!string.IsNullOrEmpty(fullPaths)) { // Locate the first item based on full path object firstItem = null; foreach (var fullPath in fullPaths.Split(new char[] { '\r', '\n' })) { if (!string.IsNullOrEmpty(fullPath)) { var item = targetControl.GetItemByFullPath(fullPath); if (item != null) { firstItem = item; break; } } } if (firstItem != null) { // Ensure that the first item is already in the target control (nav will be null if not)... if allowing drag/drop onto external // controls, you cannot use the item navigator and must rely on your own item hierarchy logic var firstItemNav = targetControl.GetItemNavigator(firstItem); if (firstItemNav != null) { // Only support a single effect (you could add support for other effects like Copy if the Ctrl key is down here) if ((e.AllowedEffects & DragDropEffects.Move) == DragDropEffects.Move) { e.Effects = DragDropEffects.Move; e.Handled = true; } switch (e.Effects) { case DragDropEffects.Move: // Coerce the resulting drop-area so that if dragging 'after' an item that has a next sibling, the drop area // becomes 'on' the item instead... can still get between the items by dragging 'before' the next sibling in this scenario if (dropArea == TreeItemDropArea.After) { var targetItemNav = targetControl.GetItemNavigator(targetItem); if ((targetItemNav != null) && (targetItemNav.GoToNextSibling())) { dropArea = TreeItemDropArea.On; } } return(dropArea); } } } } } e.Effects = DragDropEffects.None; return(TreeItemDropArea.None); }
/// <summary> /// Notifies that a drop event occurred over a target item and requests that appropriate updates are made to the supplied <c>DragEventArgs</c>. /// </summary> /// <param name="e">The <see cref="DragEventArgs"/> whose effects should be updated.</param> /// <param name="targetControl">The target control, over which the event occurred</param> /// <param name="targetItem">The target item, which could be <see langword="null"/> if dragging below the last tree item.</param> /// <param name="dropArea">A <see cref="TreeItemDropArea"/> indicating the drop area over the target item.</param> /// <remarks> /// The default implementation of this method sets the <c>e.Effects</c> to <c>DragDropEffects.None</c> and takes no further action. /// Override this method if you wish to support dropping and add logic to properly handle the dragged data. /// </remarks> public override void OnDrop(DragEventArgs e, TreeListBox targetControl, object targetItem, TreeItemDropArea dropArea) { var originalEffects = e.Effects; e.Effects = DragDropEffects.None; // If the drag is over an item and there is item data present... var targetModel = targetItem as TreeNodeModel; if ((targetModel != null) && (dropArea != TreeItemDropArea.None) && (e.Data.GetDataPresent(TreeListBox.ItemDataFormat))) { // Resolve the real target item (in case the drop area is above or below the target item) var targetDropIndex = targetModel.Children.Count; switch (dropArea) { case TreeItemDropArea.Before: case TreeItemDropArea.After: var nav = targetControl.GetItemNavigator(targetItem); if (nav != null) { var targetChildModel = targetModel; var targetChildItem = targetItem; if (!nav.GoToParent()) { return; } targetItem = nav.CurrentItem; targetModel = targetItem as TreeNodeModel; if (targetModel == null) { return; } var index = targetModel.Children.IndexOf(targetChildModel); if (index != -1) { targetDropIndex = index + (dropArea == TreeItemDropArea.After ? 1 : 0); } } break; } // Get the items var fullPaths = e.Data.GetData(TreeListBox.ItemDataFormat) as string; if (!string.IsNullOrEmpty(fullPaths)) { // Locate items based on full path var items = new List <object>(); foreach (var fullPath in fullPaths.Split(new char[] { '\r', '\n' })) { if (!string.IsNullOrEmpty(fullPath)) { var item = targetControl.GetItemByFullPath(fullPath); if (item != null) { items.Add(item); } } } if (items.Count > 0) { // Check each item and validate that various drop operations are allowed before actually executing the drop foreach (var item in items) { if (item == targetItem) { MessageBox.Show("Cannot drop an item on itself.", "Drag and Drop", MessageBoxButton.OK); return; } else { var nav = targetControl.GetItemNavigator(item); if (nav == null) { MessageBox.Show("Cannot drop from a different control.", "Drag and Drop", MessageBoxButton.OK); return; } else { if (nav.GoToCommonAncestor(targetItem)) { if (nav.CurrentItem == item) { MessageBox.Show("Cannot drop onto a descendant item.", "Drag and Drop", MessageBoxButton.OK); return; } } } } } // Only support a single effect (you could add support for other effects like Copy if the Ctrl key is down here) if ((originalEffects & DragDropEffects.Move) == DragDropEffects.Move) { e.Effects = DragDropEffects.Move; e.Handled = true; } // Move items foreach (var item in items) { var nav = targetControl.GetItemNavigator(item); if (nav.GoToParent()) { var itemModel = item as TreeNodeModel; var parentModel = nav.CurrentItem as TreeNodeModel; if ((itemModel != null) && (parentModel != null)) { var index = parentModel.Children.IndexOf(itemModel); if (index != -1) { if ((parentModel == targetModel) && (index < targetDropIndex)) { targetDropIndex--; } parentModel.Children.RemoveAt(index); } else { break; } } else { break; } targetModel.Children.Insert(Math.Max(0, Math.Min(targetModel.Children.Count, targetDropIndex++)), itemModel); targetModel.IsExpanded = true; // Focus the last item if (items[items.Count - 1] == item) { targetControl.FocusItem(itemModel); } } } } } } }