Skip to content

Commit

Permalink
Merge pull request #489 from mangoyoga80/features/DropTargetHint
Browse files Browse the repository at this point in the history
DropTarget hint and DropHighlightAdorner background brush
  • Loading branch information
punker76 authored Dec 5, 2024
2 parents 9cbf0bf + 4673a32 commit 6c5f2c2
Show file tree
Hide file tree
Showing 29 changed files with 1,037 additions and 31 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,5 @@ Does your company use `GongSolutions.WPF.DragDrop`? Ask your manager or marketi
![screenshot04](./screenshots/2016-09-03_00h53_21.png)

![gif02](./screenshots/DragDropSample01.gif)

![gif03](./screenshots/DragHint-Demo.gif)
Binary file added screenshots/DragHint-Demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions src/GongSolutions.WPF.DragDrop/DefaultDropHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ public static bool CanAcceptData(IDropInfo dropInfo)

public static IEnumerable ExtractData(object data)
{
if (data == null)
{
return Enumerable.Empty<object>();
}

if (data is IEnumerable enumerable and not string)
{
return enumerable;
Expand Down Expand Up @@ -200,6 +205,11 @@ protected static bool IsChildOf(UIElement targetItem, UIElement sourceItem)

protected static bool TestCompatibleTypes(IEnumerable target, object data)
{
if (data == null)
{
return false;
}

bool InterfaceFilter(Type t, object o) => (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>));

var enumerableInterfaces = target.GetType().FindInterfaces(InterfaceFilter, null);
Expand All @@ -217,6 +227,11 @@ protected static bool TestCompatibleTypes(IEnumerable target, object data)
}
}

public virtual void DropHint(IDropHintInfo dropHintInfo)
{
dropHintInfo.DropTargetHintAdorner = DropTargetAdorners.Hint;
}

#if !NETCOREAPP3_1_OR_GREATER
/// <inheritdoc />
public void DragEnter(IDropInfo dropInfo)
Expand All @@ -234,6 +249,15 @@ public virtual void DragOver(IDropInfo dropInfo)
dropInfo.Effects = copyData ? DragDropEffects.Copy : DragDropEffects.Move;
var isTreeViewItem = dropInfo.InsertPosition.HasFlag(RelativeInsertPosition.TargetItemCenter) && dropInfo.VisualTargetItem is TreeViewItem;
dropInfo.DropTargetAdorner = isTreeViewItem ? DropTargetAdorners.Highlight : DropTargetAdorners.Insert;

dropInfo.DropTargetHintState = DropHintState.Active;
dropInfo.DropTargetHintAdorner = DropTargetAdorners.Hint;
}
else
{
dropInfo.Effects = DragDropEffects.None;
dropInfo.DropTargetHintAdorner = DropTargetAdorners.Hint;
dropInfo.DropTargetHintState = DropHintState.Error;
}
}

Expand Down
103 changes: 103 additions & 0 deletions src/GongSolutions.WPF.DragDrop/DragDrop.Properties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,31 @@ public static void SetDropTargetAdornerBrush(DependencyObject element, Brush val
element.SetValue(DropTargetAdornerBrushProperty, value);
}

/// <summary>
/// Gets or sets the brush to use for the <see cref="DropTargetHighlightAdorner"/>.
/// </summary>
public static readonly DependencyProperty DropTargetHighlightBrushProperty = DependencyProperty.RegisterAttached(
"DropTargetHighlightBrush", typeof(Brush), typeof(DragDrop), new PropertyMetadata(default(Brush)));

/// <summary>Helper for setting <see cref="DropTargetHighlightBrushProperty"/> on <paramref name="element"/>.</summary>
/// <param name="element"><see cref="DependencyObject"/> to set <see cref="DropTargetHighlightBrushProperty"/> on.</param>
/// <param name="value">The brush to use for the background of <see cref="DropTargetHighlightAdorner"/>.</param>
/// <remarks>Sets the brush for the DropTargetAdorner.</remarks>
[AttachedPropertyBrowsableForType(typeof(UIElement))]
public static void SetDropTargetHighlightBrush(DependencyObject element, Brush value)
{
element.SetValue(DropTargetHighlightBrushProperty, value);
}

/// <summary>Helper for setting <see cref="DropTargetAdornerBrushProperty"/> on <paramref name="element"/>.</summary>
/// <param name="element"><see cref="DependencyObject"/> to set <see cref="DropTargetAdornerBrushProperty"/> on.</param>
/// <remarks>Sets the brush for the DropTargetAdorner.</remarks>
[AttachedPropertyBrowsableForType(typeof(UIElement))]
public static Brush GetDropTargetHighlightBrush(DependencyObject element)
{
return (Brush)element.GetValue(DropTargetHighlightBrushProperty);
}

/// <summary>
/// Gets or sets the pen for the DropTargetAdorner.
/// </summary>
Expand Down Expand Up @@ -718,6 +743,84 @@ public static void SetDragDropCopyKeyState(DependencyObject element, DragDropKey
element.SetValue(DragDropCopyKeyStateProperty, value);
}

/// <summary>
/// Data template for displaying drop hint.
/// </summary>
public static readonly DependencyProperty DropHintDataTemplateProperty = DependencyProperty.RegisterAttached(
"DropHintDataTemplate", typeof(DataTemplate), typeof(DragDrop));

/// <summary>
/// Helper method for setting the <see cref="DropHintDataTemplateProperty"/> on the given <paramref name="element"/>.
/// This property is used when <see cref="UseDropTargetHintProperty"/> is set to <c>true</c> to display hint overlay
/// </summary>
/// <param name="element">The element to set the drop hint template for</param>
/// <param name="value">The <see cref="DataTemplate"/> to display</param>
[AttachedPropertyBrowsableForType(typeof(UIElement))]
public static void SetDropHintDataTemplate(DependencyObject element, DataTemplate value)
{
element.SetValue(DropHintDataTemplateProperty, value);
}

[AttachedPropertyBrowsableForType(typeof(UIElement))]
public static DataTemplate GetDropHintDataTemplate(DependencyObject element)
{
return (DataTemplate)element.GetValue(DropHintDataTemplateProperty);
}

/// <summary>
/// Get or set whether drop target hint is used to indicate where the user can drop.
/// </summary>
public static readonly DependencyProperty UseDropTargetHintProperty
= DependencyProperty.RegisterAttached("UseDropTargetHint",
typeof(bool),
typeof(DragDrop),
new PropertyMetadata(default(bool), OnUseDropTargetHintChanged));

/// <summary>Helper for setting <see cref="UseDropTargetHintProperty"/> on <paramref name="element"/>.</summary>
/// <param name="element"><see cref="DependencyObject"/> to set <see cref="UseDropTargetHintProperty"/> on.</param>
/// <param name="value">UseDropTargetHintProperty property value.</param>
/// <remarks>Sets whether the hint adorner should be displayed.</remarks>
[AttachedPropertyBrowsableForType(typeof(UIElement))]
public static void SetUseDropTargetHint(DependencyObject element, bool value)
{
element.SetValue(UseDropTargetHintProperty, value);
}

/// <summary>Helper for getting <see cref="UseDropTargetHintProperty"/> from <paramref name="element"/>.</summary>
/// <param name="element"><see cref="DependencyObject"/> to read <see cref="UseDropTargetHintProperty"/> from.</param>
/// <remarks>Gets whether if the default DragAdorner is used.</remarks>
/// <returns>UseDropTargetHintProperty property value.</returns>
[AttachedPropertyBrowsableForType(typeof(UIElement))]
public static bool GetUseDropTargetHint(DependencyObject element)
{
return (bool)element.GetValue(UseDropTargetHintProperty);
}

/// <summary>
/// Implements side effects for when the UseDropTargetHintProperty changes.
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
private static void OnUseDropTargetHintChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var dropTarget = d as UIElement;
if (dropTarget == null)
{
return;
}

// Add or remove drop target from hint cache.
bool useDropTargetHint = (bool)e.NewValue;
if (useDropTargetHint)
{
DropHintHelpers.AddDropHintTarget(dropTarget);
}
else
{
DropHintHelpers.RemoveDropHintTarget(dropTarget);
}
}

/// <summary>
/// Gets or sets whether if the default DragAdorner should be use.
/// </summary>
Expand Down
44 changes: 39 additions & 5 deletions src/GongSolutions.WPF.DragDrop/DragDrop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ namespace GongSolutions.Wpf.DragDrop
{
public static partial class DragDrop
{
/// <summary>
/// Get the <see cref="DataTemplate"/> for the drop hint, or return the default template if not set.
/// </summary>
/// <param name="sender"></param>
/// <returns></returns>
internal static DataTemplate TryGetDropHintDataTemplate(UIElement sender)
{
if (sender == null)
{
return null;
}

return GetDropHintDataTemplate(sender) ?? DropHintHelpers.GetDefaultDropHintTemplate();
}

/// <summary>
/// Gets the drag handler from the drag info or from the sender, if the drag info is null
/// </summary>
Expand All @@ -33,7 +48,7 @@ private static IDragSource TryGetDragHandler(IDragInfo dragInfo, UIElement sende
/// <param name="dropInfo">the drop info object</param>
/// <param name="sender">the sender from an event, e.g. drag over</param>
/// <returns></returns>
private static IDropTarget TryGetDropHandler(IDropInfo dropInfo, UIElement sender)
internal static IDropTarget TryGetDropHandler(IDropInfo dropInfo, UIElement sender)
{
var dropHandler = (dropInfo?.VisualTarget != null ? GetDropHandler(dropInfo.VisualTarget) : null) ?? (sender != null ? GetDropHandler(sender) : null);

Expand All @@ -55,7 +70,7 @@ private static IDragInfoBuilder TryGetDragInfoBuilder(DependencyObject sender)
/// </summary>
/// <param name="sender">the sender from an event, e.g. drag over</param>
/// <returns></returns>
private static IDropInfoBuilder TryGetDropInfoBuilder(DependencyObject sender)
internal static IDropInfoBuilder TryGetDropInfoBuilder(DependencyObject sender)
{
return sender != null ? GetDropInfoBuilder(sender) : null;
}
Expand Down Expand Up @@ -650,6 +665,7 @@ private static void DoDragSourceMove(object sender, Func<IInputElement, Point> g
}
});

DropHintHelpers.OnDragStart(dragInfo);
var dragDropHandler = dragInfo.DragDropHandler ?? System.Windows.DragDrop.DoDragDrop;
var dragDropEffects = dragDropHandler(dragInfo.VisualSource, dataObject, dragInfo.Effects);
if (dragDropEffects == DragDropEffects.None)
Expand All @@ -658,9 +674,11 @@ private static void DoDragSourceMove(object sender, Func<IInputElement, Point> g
DragDropPreview = null;
DragDropEffectPreview = null;
DropTargetAdorner = null;
DropHintHelpers.OnDropFinished();
Mouse.OverrideCursor = null;
}

DropHintHelpers.OnDropFinished();
dragHandler.DragDropOperationFinished(dragDropEffects, dragInfo);
}
catch (Exception ex)
Expand Down Expand Up @@ -712,7 +730,14 @@ private static void OnRealTargetDragLeave(object sender, DragEventArgs e)
var dropInfo = dropInfoBuilder?.CreateDropInfo(sender, e, dragInfo, eventType) ?? new DropInfo(sender, e, dragInfo, eventType);
var dropHandler = TryGetDropHandler(dropInfo, sender as UIElement);

dropHandler?.DragLeave(dropInfo);
if(dropHandler != null)
{
dropHandler.DragLeave(dropInfo);
if(_dragInProgress)
{
DropHintHelpers.OnDragLeave(sender, dropHandler, dragInfo);
}
}

DragDropEffectPreview = null;
DropTargetAdorner = null;
Expand Down Expand Up @@ -757,6 +782,7 @@ private static void DropTargetOnDragOver(object sender, DragEventArgs e, EventTy
}

dropHandler.DragOver(dropInfo);
DropHintHelpers.DragOver(sender, dropInfo);

if (dragInfo is not null)
{
Expand Down Expand Up @@ -828,6 +854,15 @@ private static void DropTargetOnDragOver(object sender, DragEventArgs e, EventTy
}
}

if(adorner is DropTargetHighlightAdorner highlightAdorner)
{
var highlightBrush = GetDropTargetHighlightBrush(dropInfo.VisualTarget);
if (highlightBrush != null)
{
highlightAdorner.Background = highlightBrush;
}
}

adorner.DropInfo = dropInfo;
adorner.InvalidateVisual();
}
Expand Down Expand Up @@ -891,7 +926,7 @@ private static void DropTargetOnDrop(object sender, DragEventArgs e, EventType e
DragDropPreview = null;
DragDropEffectPreview = null;
DropTargetAdorner = null;

DropHintHelpers.OnDropFinished();
dropHandler.DragOver(dropInfo);

if (itemsSorter != null && dropInfo.Data is IEnumerable enumerable and not string)
Expand All @@ -901,7 +936,6 @@ private static void DropTargetOnDrop(object sender, DragEventArgs e, EventType e

dropHandler.Drop(dropInfo);
dragHandler.Dropped(dropInfo);

e.Effects = dropInfo.Effects;
e.Handled = !dropInfo.NotHandled;

Expand Down
11 changes: 5 additions & 6 deletions src/GongSolutions.WPF.DragDrop/DragInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@
namespace GongSolutions.Wpf.DragDrop
{
/// <summary>
/// Holds information about a the source of a drag drop operation.
/// Holds information about the source of a drag drop operation.
/// </summary>
///
/// <remarks>
/// The <see cref="DragInfo"/> class holds all of the framework's information about the source
/// of a drag. It is used by <see cref="IDragSource.StartDrag"/> to determine whether a drag
/// The <see cref="DragInfo"/> class holds all the framework's information about the source
/// of a drag. It is used by <see cref="IDragSource.StartDrag"/> to determine whether a drag
/// can start, and what the dragged data should be.
/// </remarks>
public class DragInfo : IDragInfo
Expand Down Expand Up @@ -167,7 +166,7 @@ public DragInfo(object sender, object originalSource, MouseButton mouseButton, F
this.SourceItems = selectedItems;

// Some controls (I'm looking at you TreeView!) haven't updated their
// SelectedItem by this point. Check to see if there 1 or less item in
// SelectedItem by this point. Check to see if there 1 or less item in
// the SourceItems collection, and if so, override the control's SelectedItems with the clicked item.
//
// The control has still the old selected items at the mouse down event, so we should check this and give only the real selected item to the user.
Expand Down Expand Up @@ -209,7 +208,7 @@ internal void RefreshSelectedItems(object sender)
this.SourceItems = selectedItems;

// Some controls (I'm looking at you TreeView!) haven't updated their
// SelectedItem by this point. Check to see if there 1 or less item in
// SelectedItem by this point. Check to see if there 1 or less item in
// the SourceItems collection, and if so, override the control's SelectedItems with the clicked item.
//
// The control has still the old selected items at the mouse down event, so we should check this and give only the real selected item to the user.
Expand Down
59 changes: 59 additions & 0 deletions src/GongSolutions.WPF.DragDrop/DropHintData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;

namespace GongSolutions.Wpf.DragDrop
{
/// <summary>
/// Data presented in drop hint adorner.
/// </summary>
public class DropHintData : INotifyPropertyChanged
{
private DropHintState hintState;
private string hintText;

public DropHintData(DropHintState hintState, string hintText)
{
this.HintState = hintState;
this.HintText = hintText;
}

/// <summary>
/// The hint text to display to the user. See <see cref="IDropInfo.DropHintText"/>
/// and <see cref="IDropHintInfo.DropHintText"/>.
/// </summary>
public string HintText
{
get => this.hintText;
set
{
if (value == this.hintText) return;
this.hintText = value;
this.OnPropertyChanged();
}
}

/// <summary>
/// The hint state to display different colors for hints. See <see cref="IDropInfo.DropTargetHintState"/>
/// and <see cref="IDropHintInfo.DropTargetHintState"/>.
/// </summary>
public DropHintState HintState
{
get => this.hintState;
set
{
if (value == this.hintState) return;
this.hintState = value;
this.OnPropertyChanged();
}
}

public event PropertyChangedEventHandler PropertyChanged;

[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Loading

0 comments on commit 6c5f2c2

Please sign in to comment.