Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add logic to disallow child items in tree views #453

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 33 additions & 5 deletions src/GongSolutions.WPF.DragDrop/DropInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ namespace GongSolutions.Wpf.DragDrop
/// </remarks>
public class DropInfo : IDropInfo
{
private readonly ItemsControl itemParent;
private readonly DragEventArgs eventArgs;
private ItemsControl itemParent;
private bool? acceptChildItem;

/// <inheritdoc />
public object Data { get; set; }
Expand Down Expand Up @@ -152,6 +154,25 @@ public bool IsSameDragDropContextAsSource
/// <inheritdoc />
public EventType EventType { get; }

/// <summary>
/// Indicates if the drop target can accept the dragged data as a child item (applies to tree view items).
/// </summary>
/// <remarks>
/// Changing this value will update other properties.
/// </remarks>
public bool AcceptChildItem
{
get => this.acceptChildItem.GetValueOrDefault();
set
{
if (value != this.acceptChildItem)
{
this.acceptChildItem = value;
Update();
}
}
}

/// <summary>
/// Initializes a new instance of the DropInfo class.
/// </summary>
Expand All @@ -161,6 +182,7 @@ public bool IsSameDragDropContextAsSource
/// <param name="eventType">The type of the underlying event (tunneled or bubbled).</param>
public DropInfo(object sender, DragEventArgs e, [CanBeNull] IDragInfo dragInfo, EventType eventType)
{
this.eventArgs = e;
this.DragInfo = dragInfo;
this.KeyStates = e.KeyStates;
this.EventType = eventType;
Expand Down Expand Up @@ -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<TabPanel>(this.VisualTarget, this.DropPosition))
Expand Down Expand Up @@ -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;
Expand All @@ -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)
{
Expand All @@ -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)
Expand Down Expand Up @@ -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)
{
Expand Down
15 changes: 15 additions & 0 deletions src/Showcase/Models/FilesDropHandler.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
10 changes: 10 additions & 0 deletions src/Showcase/Models/SampleData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

namespace Showcase.WPF.DragDrop.Models
{
using MahApps.Metro.IconPacks;

public class SampleData
{
public SampleData()
Expand Down Expand Up @@ -36,14 +38,18 @@ 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;
}
}

Expand Down Expand Up @@ -82,6 +88,10 @@ public SampleData()

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

public ObservableCollection<TreeNode> TreeCollectionFiles { get; set; } = new ObservableCollection<TreeNode>();

public FilesDropHandler FilesDropHandler { get; set; } = new FilesDropHandler();

public GroupedDropHandler GroupedDropHandler { get; set; } = new GroupedDropHandler();

public ObservableCollection<GroupedItem> GroupedCollection { get; set; } = new ObservableCollection<GroupedItem>();
Expand Down
13 changes: 13 additions & 0 deletions src/Showcase/Models/TreeNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TreeNode> _children;
private bool _isCloned;
Expand All @@ -19,6 +21,17 @@ public TreeNode(string caption)
this.Children = new ObservableCollection<TreeNode>();
}

public PackIconMaterialKind Icon
{
get => this._icon;
set
{
if (value == this._icon) return;
this._icon = value;
this.OnPropertyChanged();
}
}

public string Caption
{
get => this._caption;
Expand Down
39 changes: 39 additions & 0 deletions src/Showcase/Views/TreeViewSamples.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -137,6 +138,44 @@
</ScrollViewer>
</DockPanel>
</TabItem>

<TabItem Header="Files">
<TabItem.Resources>
<Style x:Key="BoundTreeViewItemStyle" TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="Padding" Value="2" />
</Style>
</TabItem.Resources>
<DockPanel>
<TextBlock DockPanel.Dock="Top"
Style="{StaticResource SampleHeaderTextBlockStyle}"
Text="Files and Folders" />
<TextBlock DockPanel.Dock="Top"
Style="{StaticResource DefaultTextBlockStyle}"
Text="Demonstrates custom logic which defines if a TreeView item can receive child items (folders) or not (files)." />
<TreeView dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.DropHandler="{Binding Data.FilesDropHandler}"
dd:DragDrop.UseDefaultDragAdorner="True"
dd:DragDrop.UseDefaultEffectDataTemplate="True"
dd:DragDrop.SelectDroppedItems="True"
Height="NaN"
ItemContainerStyle="{StaticResource BoundTreeViewItemStyle}"
ItemsSource="{Binding Data.TreeCollectionFiles}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<iconPacks:PackIconMaterial Focusable="False"
Foreground="Gray"
Kind="{Binding Icon}" />
<TextBlock Margin="4,2,2,2"
Text="{Binding Caption}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</DockPanel>
</TabItem>
</TabControl>

</Grid>
Expand Down