Skip to content

Commit

Permalink
Merge pull request #453 from ltrzesniewski/files-folders
Browse files Browse the repository at this point in the history
Add logic to disallow child items in tree views
  • Loading branch information
punker76 authored Dec 5, 2024
2 parents c5ddce1 + d88cb3a commit 9cbf0bf
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 13 deletions.
46 changes: 33 additions & 13 deletions src/GongSolutions.WPF.DragDrop/DropInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@ namespace GongSolutions.Wpf.DragDrop
/// <summary>
/// Holds information about a the target of a drag drop operation.
/// </summary>
///
///
/// <remarks>
/// The <see cref="DropInfo"/> class holds all of the framework's information about the current
/// target of a drag. It is used by <see cref="IDropTarget.DragOver"/> method to determine whether
/// The <see cref="DropInfo"/> class holds all of the framework's information about the current
/// target of a drag. It is used by <see cref="IDropTarget.DragOver"/> method to determine whether
/// the current drop target is valid, and by <see cref="IDropTarget.Drop"/> to perform the drop.
/// </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,20 @@ public bool IsSameDragDropContextAsSource
/// <inheritdoc />
public EventType EventType { get; }

/// <inheritdoc />
public bool AcceptChildItem
{
get => this.acceptChildItem.GetValueOrDefault();
set
{
if (value != this.acceptChildItem.GetValueOrDefault())
{
this.acceptChildItem = value;
this.Update();
}
}
}

/// <summary>
/// Initializes a new instance of the DropInfo class.
/// </summary>
Expand All @@ -161,6 +177,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,12 +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();

if (this.VisualTarget is TabControl)
this.Update();
}

private void Update()
{
if (this.VisualTarget is TabControl && !HitTestUtilities.HitTest4Type<TabPanel>(this.VisualTarget, this.DropPosition))
{
if (!HitTestUtilities.HitTest4Type<TabPanel>(this.VisualTarget, this.DropPosition))
{
return;
}
return;
}

if (this.VisualTarget is ItemsControl itemsControl)
Expand Down Expand Up @@ -256,10 +275,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 +305,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 +320,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 +348,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
8 changes: 8 additions & 0 deletions src/GongSolutions.WPF.DragDrop/IDropInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,5 +154,13 @@ public interface IDropInfo
/// Gets the current mode of the underlying routed event.
/// </summary>
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>
bool AcceptChildItem { get; set; }
}
}
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 @@ -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;
}
}

Expand Down Expand Up @@ -85,6 +91,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 @@ -146,6 +147,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

0 comments on commit 9cbf0bf

Please sign in to comment.