/// <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 ToolboxTreeNodeModel; 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: // When dropping before or after, a new node will be inserted as a child to the parent of the target var nav = targetControl.GetItemNavigator(targetItem); if (nav != null) { // Cache the original target var targetChildModel = targetModel; // Quit if unable to move to navigate to the parent if (!nav.GoToParent()) { return; } // Update the target item to be the parent of the original target targetItem = nav.CurrentItem; targetModel = targetItem as ToolboxTreeNodeModel; // Quit if the new target is not the expected model if (targetModel == null) { return; } // Resolve index of the new node based on whether it should be before // or after the original target. var index = targetModel.Children.IndexOf(targetChildModel); if (index != -1) { targetDropIndex = index + (dropArea == TreeItemDropArea.After ? 1 : 0); } } break; } // Resolve the category for the drop target if (!TryGetCategory(targetControl, targetModel, out var targetCategoryModel)) { MessageBox.Show("Unable to determine the drop category.", "Drag and Drop", MessageBoxButton.OK); return; } // Get the dragged controls (only control nodes are currently supported) List <ControlTreeNodeModel> sourceControlModels = GetDraggedModels(e.Data, targetControl) .OfType <ControlTreeNodeModel>() .ToList(); if (sourceControlModels.Count > 0) { // Check each item and validate that various drop operations are allowed before actually executing the drop foreach (var sourceModel in sourceControlModels) { if (sourceModel == targetModel) { MessageBox.Show("Cannot drop an item on itself.", "Drag and Drop", MessageBoxButton.OK); return; } else { var nav = targetControl.GetItemNavigator(sourceModel); 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 == sourceModel) { 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; } else if ((originalEffects & DragDropEffects.Copy) == DragDropEffects.Copy) { e.Effects = DragDropEffects.Copy; e.Handled = true; } if (IsFavoritesCategory(targetCategoryModel)) { // Controls dragged into favorites category will not actually be moved. They will be added as favorites. foreach (var sourceControlModel in sourceControlModels) { sourceControlModel.IsFavorite = true; } } else { // Complete operation bool isMove = e.Effects == DragDropEffects.Move; foreach (var sourceControlModel in sourceControlModels) { // Resolve the source category of the dragged control if (!TryGetCategory(targetControl, sourceControlModel, out var sourceCategoryModel)) { break; } if (isMove) { // Remove the control from the original category var index = sourceCategoryModel.Children.IndexOf(sourceControlModel); if (index != -1) { if ((sourceCategoryModel == targetCategoryModel) && (index < targetDropIndex)) { targetDropIndex--; } sourceCategoryModel.Children.RemoveAt(index); } else { // Quit processing if any source cannot be located break; } // Add the control to the new category (may be the same as the source if it was repositioned) int resolvedTargetDropIndex = Math.Max(0, Math.Min(targetCategoryModel.Children.Count, targetDropIndex++)); targetCategoryModel.Children.Insert(resolvedTargetDropIndex, sourceControlModel); targetCategoryModel.IsExpanded = true; // Focus the last item that was moved if (sourceControlModels[sourceControlModels.Count - 1] == sourceControlModel) { // Must dispatch the focus changes to allow the view to update based on changes in the models Dispatcher.CurrentDispatcher.BeginInvoke((Action)(() => { targetControl.FocusItem(sourceControlModel); }), DispatcherPriority.Normal); } } } } } } }
/// <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 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, item data present, and the drop target is recognized... if ((dropArea != TreeItemDropArea.None) && (e.Data.GetDataPresent(TreeListBox.ItemDataFormat)) && (TryGetModelsFromItem(targetControl, targetItem, out var targetCategoryModel, out _))) { // Locate the dragged model. If multiple objects were selected, only the first object is used for reference. // The dragged item must be defined in the target control for the drag operation to be allowed. var sourceItem = GetDraggedModels(e.Data, targetControl).FirstOrDefault(); if ((sourceItem != null) && (TryGetModelsFromItem(targetControl, sourceItem, out var sourceCategoryModel, out _))) { if (IsFavoritesCategory(sourceCategoryModel)) { // Dragging from the "Favorites" category is not supported within the Toolbox control. Favorites can only // be dragged outside of the control (i.e. to the designer surface). e.Effects = DragDropEffects.None; e.Handled = true; } else if (IsFavoritesCategory(targetCategoryModel)) { // Dropping on the "Favorites" category will only change if the control is a favorite and will not // alter the location of the source control in the toolbox, so use the 'Copy' effect. if ((e.AllowedEffects & DragDropEffects.Copy) == DragDropEffects.Copy) { e.Effects = DragDropEffects.Copy; e.Handled = true; } } else { // Controls cannot exist in more than one category, so only move effect is allowed if ((e.AllowedEffects & DragDropEffects.Move) == DragDropEffects.Move) { e.Effects = DragDropEffects.Move; e.Handled = true; } } if ((e.Handled) && (e.Effects != DragDropEffects.None)) { // TreeItems can be dropped 'Before', 'On', or 'After' a target node, but drop targets may not support // all of these scenarios. Coerce the drop area, as needed, based on the target node. if (targetItem is CategoryTreeNodeModel) { // Since controls can only be children to a category, not siblings, coerce all drops to 'On' dropArea = TreeItemDropArea.On; } else if (targetItem is EmptyPlaceholderTreeNodeModel) { // When the control is dropped, the empty placeholder will be remove and the dropped control // will be added as first child to the category, so coerce all drops to 'Before' the placeholder. dropArea = TreeItemDropArea.Before; } else if ((targetItem is ControlTreeNodeModel) && (dropArea == TreeItemDropArea.On)) { // A control cannot have children, so coerce dropping 'On' a control to 'After' (i.e. next sibling) dropArea = TreeItemDropArea.After; } 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); } } } } } } }