Skip to content

Commit 9cbf0bf

Browse files
authored
Merge pull request #453 from ltrzesniewski/files-folders
Add logic to disallow child items in tree views
2 parents c5ddce1 + d88cb3a commit 9cbf0bf

File tree

6 files changed

+118
-13
lines changed

6 files changed

+118
-13
lines changed

src/GongSolutions.WPF.DragDrop/DropInfo.cs

+33-13
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,17 @@ namespace GongSolutions.Wpf.DragDrop
1313
/// <summary>
1414
/// Holds information about a the target of a drag drop operation.
1515
/// </summary>
16-
///
16+
///
1717
/// <remarks>
18-
/// The <see cref="DropInfo"/> class holds all of the framework's information about the current
19-
/// target of a drag. It is used by <see cref="IDropTarget.DragOver"/> method to determine whether
18+
/// The <see cref="DropInfo"/> class holds all of the framework's information about the current
19+
/// target of a drag. It is used by <see cref="IDropTarget.DragOver"/> method to determine whether
2020
/// the current drop target is valid, and by <see cref="IDropTarget.Drop"/> to perform the drop.
2121
/// </remarks>
2222
public class DropInfo : IDropInfo
2323
{
24-
private readonly ItemsControl itemParent;
24+
private readonly DragEventArgs eventArgs;
25+
private ItemsControl itemParent;
26+
private bool? acceptChildItem;
2527

2628
/// <inheritdoc />
2729
public object Data { get; set; }
@@ -152,6 +154,20 @@ public bool IsSameDragDropContextAsSource
152154
/// <inheritdoc />
153155
public EventType EventType { get; }
154156

157+
/// <inheritdoc />
158+
public bool AcceptChildItem
159+
{
160+
get => this.acceptChildItem.GetValueOrDefault();
161+
set
162+
{
163+
if (value != this.acceptChildItem.GetValueOrDefault())
164+
{
165+
this.acceptChildItem = value;
166+
this.Update();
167+
}
168+
}
169+
}
170+
155171
/// <summary>
156172
/// Initializes a new instance of the DropInfo class.
157173
/// </summary>
@@ -161,6 +177,7 @@ public bool IsSameDragDropContextAsSource
161177
/// <param name="eventType">The type of the underlying event (tunneled or bubbled).</param>
162178
public DropInfo(object sender, DragEventArgs e, [CanBeNull] IDragInfo dragInfo, EventType eventType)
163179
{
180+
this.eventArgs = e;
164181
this.DragInfo = dragInfo;
165182
this.KeyStates = e.KeyStates;
166183
this.EventType = eventType;
@@ -200,12 +217,14 @@ public DropInfo(object sender, DragEventArgs e, [CanBeNull] IDragInfo dragInfo,
200217
// visual target can be null, so give us a point...
201218
this.DropPosition = this.VisualTarget != null ? e.GetPosition(this.VisualTarget) : new Point();
202219

203-
if (this.VisualTarget is TabControl)
220+
this.Update();
221+
}
222+
223+
private void Update()
224+
{
225+
if (this.VisualTarget is TabControl && !HitTestUtilities.HitTest4Type<TabPanel>(this.VisualTarget, this.DropPosition))
204226
{
205-
if (!HitTestUtilities.HitTest4Type<TabPanel>(this.VisualTarget, this.DropPosition))
206-
{
207-
return;
208-
}
227+
return;
209228
}
210229

211230
if (this.VisualTarget is ItemsControl itemsControl)
@@ -256,10 +275,11 @@ public DropInfo(object sender, DragEventArgs e, [CanBeNull] IDragInfo dragInfo,
256275

257276
var tvItemIsExpanded = tvItem is { HasHeader: true, HasItems: true, IsExpanded: true };
258277
var itemRenderSize = tvItemIsExpanded ? tvItem.GetHeaderSize() : item.RenderSize;
278+
this.acceptChildItem ??= tvItem != null;
259279

260280
if (this.VisualTargetOrientation == Orientation.Vertical)
261281
{
262-
var currentYPos = e.GetPosition(item).Y;
282+
var currentYPos = this.eventArgs.GetPosition(item).Y;
263283
var targetHeight = itemRenderSize.Height;
264284

265285
var topGap = targetHeight * 0.25;
@@ -285,7 +305,7 @@ public DropInfo(object sender, DragEventArgs e, [CanBeNull] IDragInfo dragInfo,
285305
this.InsertPosition = RelativeInsertPosition.BeforeTargetItem;
286306
}
287307

288-
if (currentYPos > topGap && currentYPos < bottomGap)
308+
if (this.AcceptChildItem && currentYPos > topGap && currentYPos < bottomGap)
289309
{
290310
if (tvItem != null)
291311
{
@@ -300,7 +320,7 @@ public DropInfo(object sender, DragEventArgs e, [CanBeNull] IDragInfo dragInfo,
300320
}
301321
else
302322
{
303-
var currentXPos = e.GetPosition(item).X;
323+
var currentXPos = this.eventArgs.GetPosition(item).X;
304324
var targetWidth = itemRenderSize.Width;
305325

306326
if (this.VisualTargetFlowDirection == FlowDirection.RightToLeft)
@@ -328,7 +348,7 @@ public DropInfo(object sender, DragEventArgs e, [CanBeNull] IDragInfo dragInfo,
328348
}
329349
}
330350

331-
if (currentXPos > targetWidth * 0.25 && currentXPos < targetWidth * 0.75)
351+
if (this.AcceptChildItem && currentXPos > targetWidth * 0.25 && currentXPos < targetWidth * 0.75)
332352
{
333353
if (tvItem != null)
334354
{

src/GongSolutions.WPF.DragDrop/IDropInfo.cs

+8
Original file line numberDiff line numberDiff line change
@@ -154,5 +154,13 @@ public interface IDropInfo
154154
/// Gets the current mode of the underlying routed event.
155155
/// </summary>
156156
EventType EventType { get; }
157+
158+
/// <summary>
159+
/// Indicates if the drop target can accept the dragged data as a child item (applies to tree view items).
160+
/// </summary>
161+
/// <remarks>
162+
/// Changing this value will update other properties.
163+
/// </remarks>
164+
bool AcceptChildItem { get; set; }
157165
}
158166
}
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace Showcase.WPF.DragDrop.Models;
2+
3+
using GongSolutions.Wpf.DragDrop;
4+
using MahApps.Metro.IconPacks;
5+
6+
public class FilesDropHandler : DefaultDropHandler
7+
{
8+
public override void DragOver(IDropInfo dropInfo)
9+
{
10+
if (dropInfo is DropInfo { TargetItem: not TreeNode { Icon: PackIconMaterialKind.Folder } } typedDropInfo)
11+
typedDropInfo.AcceptChildItem = false;
12+
13+
base.DragOver(dropInfo);
14+
}
15+
}

src/Showcase/Models/SampleData.cs

+10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
namespace Showcase.WPF.DragDrop.Models
77
{
8+
using MahApps.Metro.IconPacks;
9+
810
public class SampleData
911
{
1012
public SampleData()
@@ -37,15 +39,19 @@ public SampleData()
3739
for (int r = 1; r <= 6; r++)
3840
{
3941
var root = new TreeNode($"Root {r}");
42+
var folder = new TreeNode($"Folder {r}") { Icon = PackIconMaterialKind.Folder };
4043
for (var i = 0; i < ((r % 2) == 0 ? 8 : 3); ++i)
4144
{
4245
root.Children.Add(new TreeNode($"Item {i + 10 * r}"));
46+
folder.Children.Add(new TreeNode($"File {i + 10 * r}") { Icon = PackIconMaterialKind.File });
4347
}
4448

4549
this.TreeCollection1.Add(root);
50+
this.TreeCollectionFiles.Add(folder);
4651
if (r == 2)
4752
{
4853
root.IsExpanded = true;
54+
folder.IsExpanded = true;
4955
}
5056
}
5157

@@ -85,6 +91,10 @@ public SampleData()
8591

8692
public ObservableCollection<TreeNode> TreeCollection2 { get; set; } = new ObservableCollection<TreeNode>();
8793

94+
public ObservableCollection<TreeNode> TreeCollectionFiles { get; set; } = new ObservableCollection<TreeNode>();
95+
96+
public FilesDropHandler FilesDropHandler { get; set; } = new FilesDropHandler();
97+
8898
public GroupedDropHandler GroupedDropHandler { get; set; } = new GroupedDropHandler();
8999

90100
public ObservableCollection<GroupedItem> GroupedCollection { get; set; } = new ObservableCollection<GroupedItem>();

src/Showcase/Models/TreeNode.cs

+13
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
using System.ComponentModel;
44
using System.Runtime.CompilerServices;
55
using JetBrains.Annotations;
6+
using MahApps.Metro.IconPacks;
67

78
namespace Showcase.WPF.DragDrop.Models
89
{
910
public class TreeNode : INotifyPropertyChanged, ICloneable
1011
{
12+
private PackIconMaterialKind _icon;
1113
private string _caption;
1214
private ObservableCollection<TreeNode> _children;
1315
private bool _isCloned;
@@ -19,6 +21,17 @@ public TreeNode(string caption)
1921
this.Children = new ObservableCollection<TreeNode>();
2022
}
2123

24+
public PackIconMaterialKind Icon
25+
{
26+
get => this._icon;
27+
set
28+
{
29+
if (value == this._icon) return;
30+
this._icon = value;
31+
this.OnPropertyChanged();
32+
}
33+
}
34+
2235
public string Caption
2336
{
2437
get => this._caption;

src/Showcase/Views/TreeViewSamples.xaml

+39
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
77
xmlns:viewModels="clr-namespace:Showcase.WPF.DragDrop.ViewModels"
88
xmlns:views="clr-namespace:Showcase.WPF.DragDrop.Views"
9+
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
910
d:DataContext="{d:DesignInstance viewModels:MainViewModel}"
1011
d:DesignHeight="400"
1112
d:DesignWidth="600"
@@ -146,6 +147,44 @@
146147
</ScrollViewer>
147148
</DockPanel>
148149
</TabItem>
150+
151+
<TabItem Header="Files">
152+
<TabItem.Resources>
153+
<Style x:Key="BoundTreeViewItemStyle" TargetType="{x:Type TreeViewItem}">
154+
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
155+
<Setter Property="Padding" Value="2" />
156+
</Style>
157+
</TabItem.Resources>
158+
<DockPanel>
159+
<TextBlock DockPanel.Dock="Top"
160+
Style="{StaticResource SampleHeaderTextBlockStyle}"
161+
Text="Files and Folders" />
162+
<TextBlock DockPanel.Dock="Top"
163+
Style="{StaticResource DefaultTextBlockStyle}"
164+
Text="Demonstrates custom logic which defines if a TreeView item can receive child items (folders) or not (files)." />
165+
<TreeView dd:DragDrop.IsDragSource="True"
166+
dd:DragDrop.IsDropTarget="True"
167+
dd:DragDrop.DropHandler="{Binding Data.FilesDropHandler}"
168+
dd:DragDrop.UseDefaultDragAdorner="True"
169+
dd:DragDrop.UseDefaultEffectDataTemplate="True"
170+
dd:DragDrop.SelectDroppedItems="True"
171+
Height="NaN"
172+
ItemContainerStyle="{StaticResource BoundTreeViewItemStyle}"
173+
ItemsSource="{Binding Data.TreeCollectionFiles}">
174+
<TreeView.ItemTemplate>
175+
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
176+
<StackPanel Orientation="Horizontal">
177+
<iconPacks:PackIconMaterial Focusable="False"
178+
Foreground="Gray"
179+
Kind="{Binding Icon}" />
180+
<TextBlock Margin="4,2,2,2"
181+
Text="{Binding Caption}" />
182+
</StackPanel>
183+
</HierarchicalDataTemplate>
184+
</TreeView.ItemTemplate>
185+
</TreeView>
186+
</DockPanel>
187+
</TabItem>
149188
</TabControl>
150189

151190
</Grid>

0 commit comments

Comments
 (0)