From ed7b9ae0c8a2ef260b1fe5b760e17c2e4a7397b6 Mon Sep 17 00:00:00 2001 From: Lucas Trzesniewski Date: Sat, 3 Sep 2022 21:26:14 +0200 Subject: [PATCH 1/2] Add logic to disallow child items in tree views --- src/GongSolutions.WPF.DragDrop/DropInfo.cs | 38 ++++++++++++++++++--- src/Showcase/Models/FilesDropHandler.cs | 15 +++++++++ src/Showcase/Models/SampleData.cs | 10 ++++++ src/Showcase/Models/TreeNode.cs | 13 ++++++++ src/Showcase/Views/TreeViewSamples.xaml | 39 ++++++++++++++++++++++ 5 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 src/Showcase/Models/FilesDropHandler.cs diff --git a/src/GongSolutions.WPF.DragDrop/DropInfo.cs b/src/GongSolutions.WPF.DragDrop/DropInfo.cs index 740afed0..fe611009 100644 --- a/src/GongSolutions.WPF.DragDrop/DropInfo.cs +++ b/src/GongSolutions.WPF.DragDrop/DropInfo.cs @@ -21,7 +21,9 @@ namespace GongSolutions.Wpf.DragDrop /// public class DropInfo : IDropInfo { - private readonly ItemsControl itemParent; + private readonly DragEventArgs eventArgs; + private ItemsControl itemParent; + private bool? acceptChildItem; /// public object Data { get; set; } @@ -152,6 +154,25 @@ public bool IsSameDragDropContextAsSource /// public EventType EventType { get; } + /// + /// Indicates if the drop target can accept the dragged data as a child item (applies to tree view items). + /// + /// + /// Changing this value will update other properties. + /// + public bool AcceptChildItem + { + get => this.acceptChildItem.GetValueOrDefault(); + set + { + if (value != this.acceptChildItem) + { + this.acceptChildItem = value; + Update(); + } + } + } + /// /// Initializes a new instance of the DropInfo class. /// @@ -161,6 +182,7 @@ public bool IsSameDragDropContextAsSource /// The type of the underlying event (tunneled or bubbled). public DropInfo(object sender, DragEventArgs e, [CanBeNull] IDragInfo dragInfo, EventType eventType) { + this.eventArgs = e; this.DragInfo = dragInfo; this.KeyStates = e.KeyStates; this.EventType = eventType; @@ -200,6 +222,11 @@ public DropInfo(object sender, DragEventArgs e, [CanBeNull] IDragInfo dragInfo, // visual target can be null, so give us a point... this.DropPosition = this.VisualTarget != null ? e.GetPosition(this.VisualTarget) : new Point(); + Update(); + } + + private void Update() + { if (this.VisualTarget is TabControl) { if (!HitTestUtilities.HitTest4Type(this.VisualTarget, this.DropPosition)) @@ -256,10 +283,11 @@ public DropInfo(object sender, DragEventArgs e, [CanBeNull] IDragInfo dragInfo, var tvItemIsExpanded = tvItem is { HasHeader: true, HasItems: true, IsExpanded: true }; var itemRenderSize = tvItemIsExpanded ? tvItem.GetHeaderSize() : item.RenderSize; + this.acceptChildItem ??= tvItem != null; if (this.VisualTargetOrientation == Orientation.Vertical) { - var currentYPos = e.GetPosition(item).Y; + var currentYPos = this.eventArgs.GetPosition(item).Y; var targetHeight = itemRenderSize.Height; var topGap = targetHeight * 0.25; @@ -285,7 +313,7 @@ public DropInfo(object sender, DragEventArgs e, [CanBeNull] IDragInfo dragInfo, this.InsertPosition = RelativeInsertPosition.BeforeTargetItem; } - if (currentYPos > topGap && currentYPos < bottomGap) + if (this.AcceptChildItem && currentYPos > topGap && currentYPos < bottomGap) { if (tvItem != null) { @@ -300,7 +328,7 @@ public DropInfo(object sender, DragEventArgs e, [CanBeNull] IDragInfo dragInfo, } else { - var currentXPos = e.GetPosition(item).X; + var currentXPos = this.eventArgs.GetPosition(item).X; var targetWidth = itemRenderSize.Width; if (this.VisualTargetFlowDirection == FlowDirection.RightToLeft) @@ -328,7 +356,7 @@ public DropInfo(object sender, DragEventArgs e, [CanBeNull] IDragInfo dragInfo, } } - if (currentXPos > targetWidth * 0.25 && currentXPos < targetWidth * 0.75) + if (this.AcceptChildItem && currentXPos > targetWidth * 0.25 && currentXPos < targetWidth * 0.75) { if (tvItem != null) { diff --git a/src/Showcase/Models/FilesDropHandler.cs b/src/Showcase/Models/FilesDropHandler.cs new file mode 100644 index 00000000..6b52389f --- /dev/null +++ b/src/Showcase/Models/FilesDropHandler.cs @@ -0,0 +1,15 @@ +namespace Showcase.WPF.DragDrop.Models; + +using GongSolutions.Wpf.DragDrop; +using MahApps.Metro.IconPacks; + +public class FilesDropHandler : DefaultDropHandler +{ + public override void DragOver(IDropInfo dropInfo) + { + if (dropInfo is DropInfo { TargetItem: not TreeNode { Icon: PackIconMaterialKind.Folder } } typedDropInfo) + typedDropInfo.AcceptChildItem = false; + + base.DragOver(dropInfo); + } +} \ No newline at end of file diff --git a/src/Showcase/Models/SampleData.cs b/src/Showcase/Models/SampleData.cs index 4b1a12f4..322c0f66 100644 --- a/src/Showcase/Models/SampleData.cs +++ b/src/Showcase/Models/SampleData.cs @@ -5,6 +5,8 @@ namespace Showcase.WPF.DragDrop.Models { + using MahApps.Metro.IconPacks; + public class SampleData { public SampleData() @@ -37,15 +39,19 @@ public SampleData() for (int r = 1; r <= 6; r++) { var root = new TreeNode($"Root {r}"); + var folder = new TreeNode($"Folder {r}") { Icon = PackIconMaterialKind.Folder }; for (var i = 0; i < ((r % 2) == 0 ? 8 : 3); ++i) { root.Children.Add(new TreeNode($"Item {i + 10 * r}")); + folder.Children.Add(new TreeNode($"File {i + 10 * r}") { Icon = PackIconMaterialKind.File }); } this.TreeCollection1.Add(root); + this.TreeCollectionFiles.Add(folder); if (r == 2) { root.IsExpanded = true; + folder.IsExpanded = true; } } @@ -85,6 +91,10 @@ public SampleData() public ObservableCollection TreeCollection2 { get; set; } = new ObservableCollection(); + public ObservableCollection TreeCollectionFiles { get; set; } = new ObservableCollection(); + + public FilesDropHandler FilesDropHandler { get; set; } = new FilesDropHandler(); + public GroupedDropHandler GroupedDropHandler { get; set; } = new GroupedDropHandler(); public ObservableCollection GroupedCollection { get; set; } = new ObservableCollection(); diff --git a/src/Showcase/Models/TreeNode.cs b/src/Showcase/Models/TreeNode.cs index ceaa7210..a7b8204b 100644 --- a/src/Showcase/Models/TreeNode.cs +++ b/src/Showcase/Models/TreeNode.cs @@ -3,11 +3,13 @@ using System.ComponentModel; using System.Runtime.CompilerServices; using JetBrains.Annotations; +using MahApps.Metro.IconPacks; namespace Showcase.WPF.DragDrop.Models { public class TreeNode : INotifyPropertyChanged, ICloneable { + private PackIconMaterialKind _icon; private string _caption; private ObservableCollection _children; private bool _isCloned; @@ -19,6 +21,17 @@ public TreeNode(string caption) this.Children = new ObservableCollection(); } + public PackIconMaterialKind Icon + { + get => this._icon; + set + { + if (value == this._icon) return; + this._icon = value; + this.OnPropertyChanged(); + } + } + public string Caption { get => this._caption; diff --git a/src/Showcase/Views/TreeViewSamples.xaml b/src/Showcase/Views/TreeViewSamples.xaml index 9d3dfe50..7ec38ecf 100644 --- a/src/Showcase/Views/TreeViewSamples.xaml +++ b/src/Showcase/Views/TreeViewSamples.xaml @@ -6,6 +6,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:viewModels="clr-namespace:Showcase.WPF.DragDrop.ViewModels" xmlns:views="clr-namespace:Showcase.WPF.DragDrop.Views" + xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks" d:DataContext="{d:DesignInstance viewModels:MainViewModel}" d:DesignHeight="400" d:DesignWidth="600" @@ -146,6 +147,44 @@ + + + + + + + + + + + + + + + + + + + + From d88cb3a0dfa96e2b8abc512b7cb6b63ff7616cec Mon Sep 17 00:00:00 2001 From: punker76 Date: Thu, 5 Dec 2024 11:59:29 +0100 Subject: [PATCH 2/2] refactor: move AcceptChildItem to interface BREAKING CHANGE: This change will be going to next major version, so we can add this to the interface. --- src/GongSolutions.WPF.DragDrop/DropInfo.cs | 26 +++++++-------------- src/GongSolutions.WPF.DragDrop/IDropInfo.cs | 8 +++++++ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/GongSolutions.WPF.DragDrop/DropInfo.cs b/src/GongSolutions.WPF.DragDrop/DropInfo.cs index fe611009..d8f3a9fc 100644 --- a/src/GongSolutions.WPF.DragDrop/DropInfo.cs +++ b/src/GongSolutions.WPF.DragDrop/DropInfo.cs @@ -13,10 +13,10 @@ namespace GongSolutions.Wpf.DragDrop /// /// Holds information about a the target of a drag drop operation. /// - /// + /// /// - /// The class holds all of the framework's information about the current - /// target of a drag. It is used by method to determine whether + /// The class holds all of the framework's information about the current + /// target of a drag. It is used by method to determine whether /// the current drop target is valid, and by to perform the drop. /// public class DropInfo : IDropInfo @@ -154,21 +154,16 @@ public bool IsSameDragDropContextAsSource /// public EventType EventType { get; } - /// - /// Indicates if the drop target can accept the dragged data as a child item (applies to tree view items). - /// - /// - /// Changing this value will update other properties. - /// + /// public bool AcceptChildItem { get => this.acceptChildItem.GetValueOrDefault(); set { - if (value != this.acceptChildItem) + if (value != this.acceptChildItem.GetValueOrDefault()) { this.acceptChildItem = value; - Update(); + this.Update(); } } } @@ -222,17 +217,14 @@ public DropInfo(object sender, DragEventArgs e, [CanBeNull] IDragInfo dragInfo, // visual target can be null, so give us a point... this.DropPosition = this.VisualTarget != null ? e.GetPosition(this.VisualTarget) : new Point(); - Update(); + this.Update(); } private void Update() { - if (this.VisualTarget is TabControl) + if (this.VisualTarget is TabControl && !HitTestUtilities.HitTest4Type(this.VisualTarget, this.DropPosition)) { - if (!HitTestUtilities.HitTest4Type(this.VisualTarget, this.DropPosition)) - { - return; - } + return; } if (this.VisualTarget is ItemsControl itemsControl) diff --git a/src/GongSolutions.WPF.DragDrop/IDropInfo.cs b/src/GongSolutions.WPF.DragDrop/IDropInfo.cs index 37d5b66b..ad8916ae 100644 --- a/src/GongSolutions.WPF.DragDrop/IDropInfo.cs +++ b/src/GongSolutions.WPF.DragDrop/IDropInfo.cs @@ -154,5 +154,13 @@ public interface IDropInfo /// Gets the current mode of the underlying routed event. /// EventType EventType { get; } + + /// + /// Indicates if the drop target can accept the dragged data as a child item (applies to tree view items). + /// + /// + /// Changing this value will update other properties. + /// + bool AcceptChildItem { get; set; } } } \ No newline at end of file