项目结构调整

This commit is contained in:
艾竹
2023-04-16 20:11:40 +08:00
parent cbfbf96033
commit 81f91f3f35
2124 changed files with 218 additions and 5516 deletions

View File

@@ -0,0 +1,94 @@
using System;
using System.Collections;
using System.Linq;
using System.Windows;
using GongSolutions.Wpf.DragDrop.Utilities;
namespace GongSolutions.Wpf.DragDrop
{
/// <summary>
/// The default drag handler for GongSolutions.Wpf.DragDrop.
/// </summary>
public class DefaultDragHandler : IDragSource
{
/// <summary>
/// Queries whether a drag can be started.
/// </summary>
/// <param name="dragInfo">Information about the drag.</param>
/// <remarks>
/// To allow a drag to be started, the <see cref="DragInfo.Effects" /> property on <paramref name="dragInfo" />
/// should be set to a value other than <see cref="DragDropEffects.None" />.
/// </remarks>
public virtual void StartDrag(IDragInfo dragInfo)
{
var items = TypeUtilities.CreateDynamicallyTypedList(dragInfo.SourceItems).Cast<object>().ToList();
if (items.Count > 1)
{
dragInfo.Data = items;
}
else
{
// special case: if the single item is an enumerable then we can not drop it as single item
var singleItem = items.FirstOrDefault();
if (singleItem is IEnumerable && !(singleItem is string))
{
dragInfo.Data = items;
}
else
{
dragInfo.Data = singleItem;
}
}
dragInfo.Effects = dragInfo.Data != null ? DragDropEffects.Copy | DragDropEffects.Move : DragDropEffects.None;
}
/// <summary>
/// Determines whether this instance [can start drag] the specified drag information.
/// </summary>
/// <param name="dragInfo">The drag information.</param>
/// <returns></returns>
public virtual bool CanStartDrag(IDragInfo dragInfo)
{
return true;
}
/// <summary>
/// Notifies the drag handler that a drop has occurred.
/// </summary>
/// <param name="dropInfo">Information about the drop.</param>
public virtual void Dropped(IDropInfo dropInfo)
{
}
/// <summary>
/// Notifies the drag handler that a drag and drop operation has finished.
/// </summary>
/// <param name="operationResult">The operation result.</param>
/// <param name="dragInfo">The drag information.</param>
public virtual void DragDropOperationFinished(DragDropEffects operationResult, IDragInfo dragInfo)
{
// nothing here
}
/// <summary>
/// Notifies the drag handler that a drag has been aborted.
/// </summary>
public virtual void DragCancelled()
{
}
/// <summary>
/// Notifies that an exception has occurred upon dragging.
/// </summary>
/// <param name="exception">The exception that occurrred.</param>
/// <returns>
/// Boolean indicating whether the exception is handled in the drag handler.
/// False will rethrow the exception.
/// </returns>
public virtual bool TryCatchOccurredException(Exception exception)
{
return false;
}
}
}

View File

@@ -0,0 +1,285 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using GongSolutions.Wpf.DragDrop.Utilities;
using JetBrains.Annotations;
namespace GongSolutions.Wpf.DragDrop
{
/// <summary>
/// A default insertion drop handler for the most common usages
/// </summary>
public class DefaultDropHandler : IDropTarget
{
/// <summary>
/// Test the specified drop information for the right data.
/// </summary>
/// <param name="dropInfo">The drop information.</param>
public static bool CanAcceptData(IDropInfo dropInfo)
{
if (dropInfo == null || dropInfo.DragInfo == null)
{
return false;
}
if (!dropInfo.IsSameDragDropContextAsSource)
{
return false;
}
// do not drop on itself
var isTreeViewItem = dropInfo.InsertPosition.HasFlag(RelativeInsertPosition.TargetItemCenter)
&& dropInfo.VisualTargetItem is TreeViewItem;
if (isTreeViewItem && dropInfo.VisualTargetItem == dropInfo.DragInfo.VisualSourceItem)
{
return false;
}
if (dropInfo.DragInfo.SourceCollection == dropInfo.TargetCollection)
{
var targetList = dropInfo.TargetCollection.TryGetList();
return targetList != null;
}
// else if (dropInfo.DragInfo.SourceCollection is ItemCollection) {
// return false;
// }
else if (dropInfo.TargetCollection == null)
{
return false;
}
else
{
if (TestCompatibleTypes(dropInfo.TargetCollection, dropInfo.Data))
{
var isChildOf = IsChildOf(dropInfo.VisualTargetItem, dropInfo.DragInfo.VisualSourceItem);
return !isChildOf;
}
else
{
return false;
}
}
}
public static IEnumerable ExtractData(object data)
{
if (data is IEnumerable && !(data is string))
{
return (IEnumerable)data;
}
return Enumerable.Repeat(data, 1);
}
/// <summary>
/// Clears the current selected items and selects the given items.
/// </summary>
/// <param name="dropInfo">The drop information.</param>
/// <param name="items">The items which should be select.</param>
/// <param name="applyTemplate">if set to <c>true</c> then for all items the ApplyTemplate will be invoked.</param>
/// <param name="focusVisualTarget">if set to <c>true</c> the visual target will be focused.</param>
/// <exception cref="System.ArgumentNullException"><paramref name="dropInfo" /> is <see langword="null" /></exception>
/// <exception cref="System.ArgumentNullException"><paramref name="dropInfo" /> is <see langword="null" /></exception>
public static void SelectDroppedItems([NotNull] IDropInfo dropInfo, [NotNull] IEnumerable items, bool applyTemplate = true, bool focusVisualTarget = true)
{
if (dropInfo == null) throw new ArgumentNullException(nameof(dropInfo));
if (items == null) throw new ArgumentNullException(nameof(items));
var itemsControl = dropInfo.VisualTarget as ItemsControl;
if (itemsControl != null)
{
var tvItem = dropInfo.VisualTargetItem as TreeViewItem;
var tvItemIsExpanded = tvItem != null && tvItem.HasHeader && tvItem.HasItems && tvItem.IsExpanded;
var itemsParent = tvItemIsExpanded ? tvItem : (dropInfo.VisualTargetItem != null ? ItemsControl.ItemsControlFromItemContainer(dropInfo.VisualTargetItem) : itemsControl);
itemsParent = itemsParent ?? itemsControl;
itemsParent.ClearSelectedItems();
foreach (var obj in items)
{
if (applyTemplate)
{
// call ApplyTemplate for TabItem in TabControl to avoid this error:
//
// System.Windows.Data Error: 4 : Cannot find source for binding with reference
var container = itemsParent.ItemContainerGenerator.ContainerFromItem(obj) as FrameworkElement;
container?.ApplyTemplate();
}
itemsParent.SetItemSelected(obj, true);
}
if (focusVisualTarget)
{
itemsControl.Focus();
}
}
}
/// <summary>
/// Determines whether the data of the drag drop action should be copied otherwise moved.
/// </summary>
/// <param name="dropInfo">The DropInfo with a valid DragInfo.</param>
public static bool ShouldCopyData(IDropInfo dropInfo)
{
// default should always the move action/effect
if (dropInfo == null || dropInfo.DragInfo == null)
{
return false;
}
var copyData = ((dropInfo.DragInfo.DragDropCopyKeyState != default(DragDropKeyStates)) && dropInfo.KeyStates.HasFlag(dropInfo.DragInfo.DragDropCopyKeyState))
|| dropInfo.DragInfo.DragDropCopyKeyState.HasFlag(DragDropKeyStates.LeftMouseButton);
copyData = copyData
//&& (dropInfo.DragInfo.VisualSource != dropInfo.VisualTarget)
&& !(dropInfo.DragInfo.SourceItem is HeaderedContentControl)
&& !(dropInfo.DragInfo.SourceItem is HeaderedItemsControl)
&& !(dropInfo.DragInfo.SourceItem is ListBoxItem);
return copyData;
}
/// <summary>
/// Updates the current drag state.
/// </summary>
/// <param name="dropInfo">Information about the drag.</param>
/// <remarks>
/// To allow a drop at the current drag position, the <see cref="DropInfo.Effects" /> property on
/// <paramref name="dropInfo" /> should be set to a value other than <see cref="DragDropEffects.None" />
/// and <see cref="DropInfo.Data" /> should be set to a non-null value.
/// </remarks>
public virtual void DragOver(IDropInfo dropInfo)
{
if (CanAcceptData(dropInfo))
{
dropInfo.Effects = ShouldCopyData(dropInfo) ? DragDropEffects.Copy : DragDropEffects.Move;
var isTreeViewItem = dropInfo.InsertPosition.HasFlag(RelativeInsertPosition.TargetItemCenter) && dropInfo.VisualTargetItem is TreeViewItem;
dropInfo.DropTargetAdorner = isTreeViewItem ? DropTargetAdorners.Highlight : DropTargetAdorners.Insert;
}
}
/// <summary>
/// Performs a drop.
/// </summary>
/// <param name="dropInfo">Information about the drop.</param>
public virtual void Drop(IDropInfo dropInfo)
{
if (dropInfo == null || dropInfo.DragInfo == null)
{
return;
}
var insertIndex = dropInfo.UnfilteredInsertIndex;
var itemsControl = dropInfo.VisualTarget as ItemsControl;
if (itemsControl != null)
{
var editableItems = itemsControl.Items as IEditableCollectionView;
if (editableItems != null)
{
var newItemPlaceholderPosition = editableItems.NewItemPlaceholderPosition;
if (newItemPlaceholderPosition == NewItemPlaceholderPosition.AtBeginning && insertIndex == 0)
{
++insertIndex;
}
else if (newItemPlaceholderPosition == NewItemPlaceholderPosition.AtEnd && insertIndex == itemsControl.Items.Count)
{
--insertIndex;
}
}
}
var destinationList = dropInfo.TargetCollection.TryGetList();
var data = ExtractData(dropInfo.Data).OfType<object>().ToList();
var copyData = ShouldCopyData(dropInfo);
if (!copyData)
{
var sourceList = dropInfo.DragInfo.SourceCollection.TryGetList();
if (sourceList != null)
{
foreach (var o in data)
{
var index = sourceList.IndexOf(o);
if (index != -1)
{
sourceList.RemoveAt(index);
// so, is the source list the destination list too ?
if (destinationList != null && Equals(sourceList, destinationList) && index < insertIndex)
{
--insertIndex;
}
}
}
}
}
if (destinationList != null)
{
var objects2Insert = new List<object>();
// check for cloning
var cloneData = dropInfo.Effects.HasFlag(DragDropEffects.Copy) || dropInfo.Effects.HasFlag(DragDropEffects.Link);
foreach (var o in data)
{
var obj2Insert = o;
if (cloneData)
{
var cloneable = o as ICloneable;
if (cloneable != null)
{
obj2Insert = cloneable.Clone();
}
}
objects2Insert.Add(obj2Insert);
destinationList.Insert(insertIndex++, obj2Insert);
}
var selectDroppedItems = itemsControl is TabControl || (itemsControl != null && DragDrop.GetSelectDroppedItems(itemsControl));
if (selectDroppedItems)
{
SelectDroppedItems(dropInfo, objects2Insert);
}
}
}
protected static bool IsChildOf(UIElement targetItem, UIElement sourceItem)
{
var parent = ItemsControl.ItemsControlFromItemContainer(targetItem);
while (parent != null)
{
if (parent == sourceItem)
{
return true;
}
parent = ItemsControl.ItemsControlFromItemContainer(parent);
}
return false;
}
protected static bool TestCompatibleTypes(IEnumerable target, object data)
{
TypeFilter filter = (t, o) => { return (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)); };
var enumerableInterfaces = target.GetType().FindInterfaces(filter, null);
var enumerableTypes = from i in enumerableInterfaces
select i.GetGenericArguments().Single();
if (enumerableTypes.Count() > 0)
{
var dataType = TypeUtilities.GetCommonBaseClass(ExtractData(data));
return enumerableTypes.Any(t => t.IsAssignableFrom(dataType));
}
else
{
return target is IList;
}
}
}
}

View File

@@ -0,0 +1,30 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- NuGet -->
<PropertyGroup>
<Authors>Jan Karger, Steven Kirk, mitchell.jon</Authors>
<PackageId>gong-wpf-dragdrop</PackageId>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageProjectUrl>https://github.com/punker76/gong-wpf-dragdrop</PackageProjectUrl>
<PackageIconUrl>https://raw.github.com/punker76/gong-wpf-dragdrop/master/GongSolutions.Wpf.DragDrop.png</PackageIconUrl>
<PackageTags>WPF;Windows;UI;XAML;Toolkit;Library;.NET</PackageTags>
<PackageReleaseNotes>https://github.com/punker76/gong-wpf-dragdrop/releases</PackageReleaseNotes>
<RepositoryUrl>https://github.com/punker76/gong-wpf-dragdrop.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<GenerateLibraryLayout>true</GenerateLibraryLayout>
</PropertyGroup>
<!-- Sign assembly -->
<PropertyGroup>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>$(MSBuildProjectDirectory)\..\gong.public.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\LICENSE" Pack="true" PackagePath="" />
</ItemGroup>
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.targets', '$(MSBuildThisFileDirectory)../'))" />
</Project>

View File

@@ -0,0 +1,76 @@
using System.Windows.Documents;
using System.Windows;
using System.Windows.Media;
namespace GongSolutions.Wpf.DragDrop
{
internal class DragAdorner : Adorner
{
public DragAdorner(UIElement adornedElement, UIElement adornment, Point translation, DragDropEffects effects = DragDropEffects.None)
: base(adornedElement)
{
this._translation = translation;
this.m_AdornerLayer = AdornerLayer.GetAdornerLayer(adornedElement);
this.m_AdornerLayer.Add(this);
this.m_Adornment = adornment;
this.IsHitTestVisible = false;
this.Effects = effects;
}
public DragDropEffects Effects { get; private set; }
public Point MousePosition
{
get { return this.m_MousePosition; }
set
{
if (this.m_MousePosition != value)
{
this.m_MousePosition = value;
this.m_AdornerLayer.Update(this.AdornedElement);
}
}
}
public void Detatch()
{
this.m_AdornerLayer.Remove(this);
}
protected override Size ArrangeOverride(Size finalSize)
{
this.m_Adornment.Arrange(new Rect(finalSize));
return finalSize;
}
public override GeneralTransform GetDesiredTransform(GeneralTransform transform)
{
var result = new GeneralTransformGroup();
result.Children.Add(base.GetDesiredTransform(transform));
result.Children.Add(new TranslateTransform(this.MousePosition.X + this._translation.X, this.MousePosition.Y + this._translation.Y));
return result;
}
protected override Visual GetVisualChild(int index)
{
return this.m_Adornment;
}
protected override Size MeasureOverride(Size constraint)
{
this.m_Adornment.Measure(constraint);
return this.m_Adornment.DesiredSize;
}
protected override int VisualChildrenCount
{
get { return 1; }
}
private readonly AdornerLayer m_AdornerLayer;
private readonly UIElement m_Adornment;
private Point m_MousePosition;
private Point _translation;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,738 @@
using System;
using System.Collections;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using GongSolutions.Wpf.DragDrop.Icons;
using GongSolutions.Wpf.DragDrop.Utilities;
namespace GongSolutions.Wpf.DragDrop
{
public static partial class DragDrop
{
private static void CreateDragAdorner(DropInfo dropInfo)
{
var dragInfo = dropInfo.DragInfo;
var template = GetDropAdornerTemplate(dropInfo.VisualTarget) ?? GetDragAdornerTemplate(dragInfo.VisualSource);
var templateSelector = GetDropAdornerTemplateSelector(dropInfo.VisualTarget) ?? GetDragAdornerTemplateSelector(dragInfo.VisualSource);
UIElement adornment = null;
var useDefaultDragAdorner = template == null && templateSelector == null && GetUseDefaultDragAdorner(dragInfo.VisualSource);
var useVisualSourceItemSizeForDragAdorner = GetUseVisualSourceItemSizeForDragAdorner(dragInfo.VisualSource);
if (useDefaultDragAdorner)
{
template = dragInfo.VisualSourceItem.GetCaptureScreenDataTemplate(dragInfo.VisualSourceFlowDirection);
}
if (template != null || templateSelector != null)
{
if (dragInfo.Data is IEnumerable && !(dragInfo.Data is string))
{
if (!useDefaultDragAdorner && ((IEnumerable)dragInfo.Data).Cast<object>().Count() <= 10)
{
var itemsControl = new ItemsControl();
itemsControl.ItemsSource = (IEnumerable)dragInfo.Data;
itemsControl.ItemTemplate = template;
itemsControl.ItemTemplateSelector = templateSelector;
itemsControl.Tag = dragInfo;
if (useVisualSourceItemSizeForDragAdorner)
{
var bounds = VisualTreeHelper.GetDescendantBounds(dragInfo.VisualSourceItem);
itemsControl.SetValue(FrameworkElement.MinWidthProperty, bounds.Width);
}
// The ItemsControl doesn't display unless we create a grid to contain it.
// Not quite sure why we need this...
var grid = new Grid();
grid.Children.Add(itemsControl);
adornment = grid;
}
}
else
{
var contentPresenter = new ContentPresenter();
contentPresenter.Content = dragInfo.Data;
contentPresenter.ContentTemplate = template;
contentPresenter.ContentTemplateSelector = templateSelector;
contentPresenter.Tag = dragInfo;
if (useVisualSourceItemSizeForDragAdorner)
{
var bounds = VisualTreeHelper.GetDescendantBounds(dragInfo.VisualSourceItem);
contentPresenter.SetValue(FrameworkElement.MinWidthProperty, bounds.Width);
contentPresenter.SetValue(FrameworkElement.MinHeightProperty, bounds.Height);
}
adornment = contentPresenter;
}
}
if (adornment != null)
{
if (useDefaultDragAdorner)
{
adornment.SetCurrentValue(UIElement.OpacityProperty, GetDefaultDragAdornerOpacity(dragInfo.VisualSource));
}
var rootElement = RootElementFinder.FindRoot(dropInfo.VisualTarget ?? dragInfo.VisualSource);
DragAdorner = new DragAdorner(rootElement, adornment, GetDragAdornerTranslation(dragInfo.VisualSource));
}
}
private static void CreateEffectAdorner(DropInfo dropInfo)
{
var dragInfo = m_DragInfo;
var template = GetEffectAdornerTemplate(dragInfo.VisualSource, dropInfo.Effects, dropInfo.DestinationText, dropInfo.EffectText);
if (template != null)
{
var rootElement = RootElementFinder.FindRoot(dropInfo.VisualTarget ?? dragInfo.VisualSource);
var adornment = new ContentPresenter();
adornment.Content = dragInfo.Data;
adornment.ContentTemplate = template;
EffectAdorner = new DragAdorner(rootElement, adornment, GetEffectAdornerTranslation(dragInfo.VisualSource), dropInfo.Effects);
}
}
private static DataTemplate GetEffectAdornerTemplate(UIElement target, DragDropEffects effect, string destinationText, string effectText = null)
{
switch (effect)
{
case DragDropEffects.All:
// TODO: Add default template for EffectAll
return GetEffectAllAdornerTemplate(target);
case DragDropEffects.Copy:
return GetEffectCopyAdornerTemplate(target) ?? CreateDefaultEffectDataTemplate(target, IconFactory.EffectCopy, effectText == null ? "Copy to" : effectText, destinationText);
case DragDropEffects.Link:
return GetEffectLinkAdornerTemplate(target) ?? CreateDefaultEffectDataTemplate(target, IconFactory.EffectLink, effectText == null ? "Link to" : effectText, destinationText);
case DragDropEffects.Move:
return GetEffectMoveAdornerTemplate(target) ?? CreateDefaultEffectDataTemplate(target, IconFactory.EffectMove, effectText == null ? "Move to" : effectText, destinationText);
case DragDropEffects.None:
return GetEffectNoneAdornerTemplate(target) ?? CreateDefaultEffectDataTemplate(target, IconFactory.EffectNone, effectText == null ? "None" : effectText, destinationText);
case DragDropEffects.Scroll:
// TODO: Add default template EffectScroll
return GetEffectScrollAdornerTemplate(target);
default:
return null;
}
}
private static DataTemplate CreateDefaultEffectDataTemplate(UIElement target, BitmapImage effectIcon, string effectText, string destinationText)
{
if (!GetUseDefaultEffectDataTemplate(target))
{
return null;
}
var fontSize = SystemFonts.MessageFontSize; // before 11d
// The icon
var imageFactory = new FrameworkElementFactory(typeof(Image));
imageFactory.SetValue(Image.SourceProperty, effectIcon);
imageFactory.SetValue(FrameworkElement.HeightProperty, 12d);
imageFactory.SetValue(FrameworkElement.WidthProperty, 12d);
// Only the icon for no effect
if (Equals(effectIcon, GongSolutions.Wpf.DragDrop.Icons.IconFactory.EffectNone))
{
return new DataTemplate { VisualTree = imageFactory };
}
// Some margin for the icon
imageFactory.SetValue(FrameworkElement.MarginProperty, new Thickness(0, 0, 3, 0));
imageFactory.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Center);
// Add effect text
var effectTextBlockFactory = new FrameworkElementFactory(typeof(TextBlock));
effectTextBlockFactory.SetValue(TextBlock.TextProperty, effectText);
effectTextBlockFactory.SetValue(TextBlock.FontSizeProperty, fontSize);
effectTextBlockFactory.SetValue(TextBlock.ForegroundProperty, Brushes.Blue);
effectTextBlockFactory.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Center);
// Add destination text
var destinationTextBlockFactory = new FrameworkElementFactory(typeof(TextBlock));
destinationTextBlockFactory.SetValue(TextBlock.TextProperty, destinationText);
destinationTextBlockFactory.SetValue(TextBlock.FontSizeProperty, fontSize);
destinationTextBlockFactory.SetValue(TextBlock.ForegroundProperty, Brushes.DarkBlue);
destinationTextBlockFactory.SetValue(TextBlock.MarginProperty, new Thickness(3, 0, 0, 0));
destinationTextBlockFactory.SetValue(TextBlock.FontWeightProperty, FontWeights.DemiBold);
destinationTextBlockFactory.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Center);
// Create containing panel
var stackPanelFactory = new FrameworkElementFactory(typeof(StackPanel));
stackPanelFactory.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
stackPanelFactory.SetValue(FrameworkElement.MarginProperty, new Thickness(2));
stackPanelFactory.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Center);
stackPanelFactory.AppendChild(imageFactory);
stackPanelFactory.AppendChild(effectTextBlockFactory);
stackPanelFactory.AppendChild(destinationTextBlockFactory);
// Add border
var borderFactory = new FrameworkElementFactory(typeof(Border));
var stopCollection = new GradientStopCollection
{
new GradientStop(Colors.White, 0.0),
new GradientStop(Colors.AliceBlue, 1.0)
};
var gradientBrush = new LinearGradientBrush(stopCollection)
{
StartPoint = new Point(0, 0),
EndPoint = new Point(0, 1)
};
borderFactory.SetValue(Panel.BackgroundProperty, gradientBrush);
borderFactory.SetValue(Border.BorderBrushProperty, Brushes.DimGray);
borderFactory.SetValue(Border.CornerRadiusProperty, new CornerRadius(3));
borderFactory.SetValue(Border.BorderThicknessProperty, new Thickness(1));
borderFactory.SetValue(Border.SnapsToDevicePixelsProperty, true);
borderFactory.SetValue(TextOptions.TextFormattingModeProperty, TextFormattingMode.Display);
borderFactory.SetValue(TextOptions.TextRenderingModeProperty, TextRenderingMode.ClearType);
borderFactory.SetValue(TextOptions.TextHintingModeProperty, TextHintingMode.Fixed);
borderFactory.AppendChild(stackPanelFactory);
// Finally add content to template
return new DataTemplate { VisualTree = borderFactory };
}
private static void Scroll(DropInfo dropInfo, DragEventArgs e)
{
if (dropInfo == null || dropInfo.TargetScrollViewer == null)
{
return;
}
var scrollViewer = dropInfo.TargetScrollViewer;
var scrollingMode = dropInfo.TargetScrollingMode;
var position = e.GetPosition(scrollViewer);
var scrollMargin = Math.Min(scrollViewer.FontSize * 2, scrollViewer.ActualHeight / 2);
if (scrollingMode == ScrollingMode.Both || scrollingMode == ScrollingMode.HorizontalOnly)
{
if (position.X >= scrollViewer.ActualWidth - scrollMargin && scrollViewer.HorizontalOffset < scrollViewer.ExtentWidth - scrollViewer.ViewportWidth)
{
scrollViewer.LineRight();
}
else if (position.X < scrollMargin && scrollViewer.HorizontalOffset > 0)
{
scrollViewer.LineLeft();
}
}
if (scrollingMode == ScrollingMode.Both || scrollingMode == ScrollingMode.VerticalOnly)
{
if (position.Y >= scrollViewer.ActualHeight - scrollMargin && scrollViewer.VerticalOffset < scrollViewer.ExtentHeight - scrollViewer.ViewportHeight)
{
scrollViewer.LineDown();
}
else if (position.Y < scrollMargin && scrollViewer.VerticalOffset > 0)
{
scrollViewer.LineUp();
}
}
}
/// <summary>
/// Gets the drag handler from the drag info or from the sender, if the drag info is null
/// </summary>
/// <param name="dragInfo">the drag info object</param>
/// <param name="sender">the sender from an event, e.g. mouse down, mouse move</param>
/// <returns></returns>
private static IDragSource TryGetDragHandler(DragInfo dragInfo, UIElement sender)
{
IDragSource dragHandler = null;
if (dragInfo != null && dragInfo.VisualSource != null)
{
dragHandler = GetDragHandler(dragInfo.VisualSource);
}
if (dragHandler == null && sender != null)
{
dragHandler = GetDragHandler(sender);
}
return dragHandler ?? DefaultDragHandler;
}
/// <summary>
/// Gets the drop handler from the drop info or from the sender, if the drop info is null
/// </summary>
/// <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(DropInfo dropInfo, UIElement sender)
{
IDropTarget dropHandler = null;
if (dropInfo != null && dropInfo.VisualTarget != null)
{
dropHandler = GetDropHandler(dropInfo.VisualTarget);
}
if (dropHandler == null && sender != null)
{
dropHandler = GetDropHandler(sender);
}
return dropHandler ?? DefaultDropHandler;
}
private static void DragSourceOnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DoMouseButtonDown(sender, e);
}
private static void DragSourceOnMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
DoMouseButtonDown(sender, e);
}
private static void DoMouseButtonDown(object sender, MouseButtonEventArgs e)
{
m_DragInfo = null;
// Ignore the click if clickCount != 1 or the user has clicked on a scrollbar.
var elementPosition = e.GetPosition((IInputElement)sender);
if (e.ClickCount != 1
|| (sender as UIElement).IsDragSourceIgnored()
|| (e.Source as UIElement).IsDragSourceIgnored()
|| (e.OriginalSource as UIElement).IsDragSourceIgnored()
|| (sender is TabControl) && !HitTestUtilities.HitTest4Type<TabPanel>(sender, elementPosition)
|| HitTestUtilities.HitTest4Type<RangeBase>(sender, elementPosition)
|| HitTestUtilities.HitTest4Type<TextBoxBase>(sender, elementPosition)
|| HitTestUtilities.HitTest4Type<PasswordBox>(sender, elementPosition)
|| HitTestUtilities.HitTest4Type<ComboBox>(sender, elementPosition)
|| HitTestUtilities.HitTest4GridViewColumnHeader(sender, elementPosition)
|| HitTestUtilities.HitTest4DataGridTypes(sender, elementPosition)
|| HitTestUtilities.IsNotPartOfSender(sender, e))
{
return;
}
var dragInfo = new DragInfo(sender, e);
if (dragInfo.VisualSource is ItemsControl control && control.CanSelectMultipleItems())
{
control.Focus();
}
if (dragInfo.VisualSourceItem == null)
{
return;
}
var dragHandler = TryGetDragHandler(dragInfo, sender as UIElement);
if (!dragHandler.CanStartDrag(dragInfo))
{
return;
}
// If the sender is a list box that allows multiple selections, ensure that clicking on an
// already selected item does not change the selection, otherwise dragging multiple items
// is made impossible.
var itemsControl = sender as ItemsControl;
if ((Keyboard.Modifiers & ModifierKeys.Shift) == 0 && (Keyboard.Modifiers & ModifierKeys.Control) == 0 && dragInfo.VisualSourceItem != null && itemsControl != null && itemsControl.CanSelectMultipleItems())
{
var selectedItems = itemsControl.GetSelectedItems().OfType<object>().ToList();
if (selectedItems.Count > 1 && selectedItems.Contains(dragInfo.SourceItem))
{
m_ClickSupressItem = dragInfo.SourceItem;
e.Handled = true;
}
}
m_DragInfo = dragInfo;
}
private static void DragSourceOnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
DoMouseButtonUp(sender, e);
}
private static void DragSourceOnMouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
DoMouseButtonUp(sender, e);
}
private static void DoMouseButtonUp(object sender, MouseButtonEventArgs e)
{
var elementPosition = e.GetPosition((IInputElement)sender);
if ((sender is TabControl) && !HitTestUtilities.HitTest4Type<TabPanel>(sender, elementPosition))
{
m_DragInfo = null;
m_ClickSupressItem = null;
return;
}
var dragInfo = m_DragInfo;
// If we prevented the control's default selection handling in DragSource_PreviewMouseLeftButtonDown
// by setting 'e.Handled = true' and a drag was not initiated, manually set the selection here.
var itemsControl = sender as ItemsControl;
if (itemsControl != null && dragInfo != null && m_ClickSupressItem != null && m_ClickSupressItem == dragInfo.SourceItem)
{
if ((Keyboard.Modifiers & ModifierKeys.Control) != 0)
{
itemsControl.SetItemSelected(dragInfo.SourceItem, false);
}
else if ((Keyboard.Modifiers & ModifierKeys.Shift) == 0)
{
itemsControl.SetSelectedItem(dragInfo.SourceItem);
}
}
m_DragInfo = null;
m_ClickSupressItem = null;
}
private static void DragSourceOnMouseMove(object sender, MouseEventArgs e)
{
var dragInfo = m_DragInfo;
if (dragInfo != null && !m_DragInProgress)
{
// the start from the source
var dragStart = dragInfo.DragStartPosition;
// do nothing if mouse left/right button is released or the pointer is captured
if (dragInfo.MouseButton == MouseButton.Left && e.LeftButton == MouseButtonState.Released)
{
m_DragInfo = null;
return;
}
if (DragDrop.GetCanDragWithMouseRightButton(dragInfo.VisualSource) && dragInfo.MouseButton == MouseButton.Right && e.RightButton == MouseButtonState.Released)
{
m_DragInfo = null;
return;
}
// current mouse position
var position = e.GetPosition((IInputElement)sender);
// prevent selection changing while drag operation
dragInfo.VisualSource?.ReleaseMouseCapture();
// only if the sender is the source control and the mouse point differs from an offset
if (dragInfo.VisualSource == sender
&& (Math.Abs(position.X - dragStart.X) > DragDrop.GetMinimumHorizontalDragDistance(dragInfo.VisualSource) ||
Math.Abs(position.Y - dragStart.Y) > DragDrop.GetMinimumVerticalDragDistance(dragInfo.VisualSource)))
{
dragInfo.RefreshSelectedItems(sender, e);
var dragHandler = TryGetDragHandler(dragInfo, sender as UIElement);
if (dragHandler.CanStartDrag(dragInfo))
{
dragHandler.StartDrag(dragInfo);
if (dragInfo.Effects != DragDropEffects.None)
{
var dataObject = dragInfo.DataObject;
if (dataObject == null)
{
if (dragInfo.Data == null)
{
// it's bad if the Data is null, cause the DataObject constructor will raise an ArgumentNullException
m_DragInfo = null; // maybe not necessary or should not set here to null
return;
}
dataObject = new DataObject(dragInfo.DataFormat.Name, dragInfo.Data);
}
try
{
m_DragInProgress = true;
var dragDropHandler = dragInfo.DragDropHandler ?? System.Windows.DragDrop.DoDragDrop;
var dragDropEffects = dragDropHandler(dragInfo.VisualSource, dataObject, dragInfo.Effects);
if (dragDropEffects == DragDropEffects.None)
{
dragHandler.DragCancelled();
}
dragHandler.DragDropOperationFinished(dragDropEffects, dragInfo);
}
catch (Exception ex)
{
if (!dragHandler.TryCatchOccurredException(ex))
{
throw;
}
}
finally
{
m_DragInProgress = false;
m_DragInfo = null;
}
}
}
}
}
}
private static void DragSourceOnQueryContinueDrag(object sender, QueryContinueDragEventArgs e)
{
if (e.Action == DragAction.Cancel || e.EscapePressed)
{
DragAdorner = null;
EffectAdorner = null;
DropTargetAdorner = null;
Mouse.OverrideCursor = null;
}
}
private static void DropTargetOnDragEnter(object sender, DragEventArgs e)
{
DropTargetOnDragOver(sender, e, EventType.Bubbled);
}
private static void DropTargetOnPreviewDragEnter(object sender, DragEventArgs e)
{
DropTargetOnDragOver(sender, e, EventType.Tunneled);
}
private static void DropTargetOnDragLeave(object sender, DragEventArgs e)
{
DragAdorner = null;
EffectAdorner = null;
DropTargetAdorner = null;
}
private static void DropTargetOnDragOver(object sender, DragEventArgs e)
{
DropTargetOnDragOver(sender, e, EventType.Bubbled);
}
private static void DropTargetOnPreviewDragOver(object sender, DragEventArgs e)
{
DropTargetOnDragOver(sender, e, EventType.Tunneled);
}
private static void DropTargetOnDragOver(object sender, DragEventArgs e, EventType eventType)
{
var elementPosition = e.GetPosition((IInputElement)sender);
var dragInfo = m_DragInfo;
var dropInfo = new DropInfo(sender, e, dragInfo, eventType);
var dropHandler = TryGetDropHandler(dropInfo, sender as UIElement);
var itemsControl = dropInfo.VisualTarget;
dropHandler.DragOver(dropInfo);
if (DragAdorner == null && dragInfo != null)
{
CreateDragAdorner(dropInfo);
}
if (DragAdorner != null)
{
var tempAdornerPos = e.GetPosition(DragAdorner.AdornedElement);
if (tempAdornerPos.X >= 0 && tempAdornerPos.Y >= 0)
{
_adornerPos = tempAdornerPos;
}
// Fixed the flickering adorner - Size changes to zero 'randomly'...?
if (DragAdorner.RenderSize.Width > 0 && DragAdorner.RenderSize.Height > 0)
{
_adornerSize = DragAdorner.RenderSize;
}
if (dragInfo != null)
{
// move the adorner
var offsetX = _adornerSize.Width * -GetDragMouseAnchorPoint(dragInfo.VisualSource).X;
var offsetY = _adornerSize.Height * -GetDragMouseAnchorPoint(dragInfo.VisualSource).Y;
_adornerPos.Offset(offsetX, offsetY);
var maxAdornerPosX = DragAdorner.AdornedElement.RenderSize.Width;
var adornerPosRightX = (_adornerPos.X + _adornerSize.Width);
if (adornerPosRightX > maxAdornerPosX)
{
_adornerPos.Offset(-adornerPosRightX + maxAdornerPosX, 0);
}
if (_adornerPos.Y < 0)
{
_adornerPos.Y = 0;
}
}
DragAdorner.MousePosition = _adornerPos;
DragAdorner.InvalidateVisual();
}
Scroll(dropInfo, e);
if (HitTestUtilities.HitTest4Type<ScrollBar>(sender, elementPosition)
|| HitTestUtilities.HitTest4GridViewColumnHeader(sender, elementPosition)
|| HitTestUtilities.HitTest4DataGridTypesOnDragOver(sender, elementPosition))
{
e.Effects = DragDropEffects.None;
e.Handled = true;
return;
}
// If the target is an ItemsControl then update the drop target adorner.
if (itemsControl != null)
{
// Display the adorner in the control's ItemsPresenter. If there is no
// ItemsPresenter provided by the style, try getting hold of a
// ScrollContentPresenter and using that.
UIElement adornedElement = null;
if (itemsControl is TabControl)
{
adornedElement = itemsControl.GetVisualDescendent<TabPanel>();
}
else if (itemsControl is DataGrid || (itemsControl as ListView)?.View is GridView)
{
adornedElement = itemsControl.GetVisualDescendent<ScrollContentPresenter>() as UIElement ?? itemsControl.GetVisualDescendent<ItemsPresenter>() as UIElement ?? itemsControl;
}
else
{
adornedElement = itemsControl.GetVisualDescendent<ItemsPresenter>() as UIElement ?? itemsControl.GetVisualDescendent<ScrollContentPresenter>() as UIElement ?? itemsControl;
}
if (adornedElement != null)
{
if (dropInfo.DropTargetAdorner == null)
{
DropTargetAdorner = null;
}
else if (!dropInfo.DropTargetAdorner.IsInstanceOfType(DropTargetAdorner))
{
DropTargetAdorner = DropTargetAdorner.Create(dropInfo.DropTargetAdorner, adornedElement, dropInfo);
}
var adorner = DropTargetAdorner;
if (adorner != null)
{
var adornerBrush = GetDropTargetAdornerBrush(dropInfo.VisualTarget);
if (adornerBrush != null)
{
adorner.Pen.SetCurrentValue(Pen.BrushProperty, adornerBrush);
}
adorner.DropInfo = dropInfo;
adorner.InvalidateVisual();
}
}
}
// Set the drag effect adorner if there is one
if (dragInfo != null && (EffectAdorner == null || EffectAdorner.Effects != dropInfo.Effects))
{
CreateEffectAdorner(dropInfo);
}
if (EffectAdorner != null)
{
var adornerPos = e.GetPosition(EffectAdorner.AdornedElement);
//adornerPos.Offset(20, 20);
EffectAdorner.MousePosition = adornerPos;
EffectAdorner.InvalidateVisual();
}
e.Effects = dropInfo.Effects;
e.Handled = !dropInfo.NotHandled;
if (!dropInfo.IsSameDragDropContextAsSource)
{
e.Effects = DragDropEffects.None;
}
}
private static void DropTargetOnDrop(object sender, DragEventArgs e)
{
DropTargetOnDrop(sender, e, EventType.Bubbled);
}
private static void DropTargetOnPreviewDrop(object sender, DragEventArgs e)
{
DropTargetOnDrop(sender, e, EventType.Tunneled);
}
private static void DropTargetOnDrop(object sender, DragEventArgs e, EventType eventType)
{
var dragInfo = m_DragInfo;
var dropInfo = new DropInfo(sender, e, dragInfo, eventType);
var dropHandler = TryGetDropHandler(dropInfo, sender as UIElement);
var dragHandler = TryGetDragHandler(dragInfo, sender as UIElement);
DragAdorner = null;
EffectAdorner = null;
DropTargetAdorner = null;
dropHandler.DragOver(dropInfo);
dropHandler.Drop(dropInfo);
dragHandler.Dropped(dropInfo);
e.Effects = dropInfo.Effects;
e.Handled = !dropInfo.NotHandled;
Mouse.OverrideCursor = null;
}
private static void DropTargetOnGiveFeedback(object sender, GiveFeedbackEventArgs e)
{
if (EffectAdorner != null)
{
e.UseDefaultCursors = false;
e.Handled = true;
if (Mouse.OverrideCursor != Cursors.Arrow)
{
Mouse.OverrideCursor = Cursors.Arrow;
}
}
else
{
e.UseDefaultCursors = true;
e.Handled = true;
if (Mouse.OverrideCursor != null)
{
Mouse.OverrideCursor = null;
}
}
}
private static DragAdorner _DragAdorner;
private static DragAdorner DragAdorner
{
get { return _DragAdorner; }
set
{
_DragAdorner?.Detatch();
_DragAdorner = value;
}
}
private static DragAdorner _EffectAdorner;
private static DragAdorner EffectAdorner
{
get { return _EffectAdorner; }
set
{
_EffectAdorner?.Detatch();
_EffectAdorner = value;
}
}
private static DropTargetAdorner _DropTargetAdorner;
private static DropTargetAdorner DropTargetAdorner
{
get { return _DropTargetAdorner; }
set
{
_DropTargetAdorner?.Detatch();
_DropTargetAdorner = value;
}
}
private static DragInfo m_DragInfo;
private static bool m_DragInProgress;
private static object m_ClickSupressItem;
private static Point _adornerPos;
private static Size _adornerSize;
}
}

View File

@@ -0,0 +1,286 @@
using System;
using System.Collections;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using GongSolutions.Wpf.DragDrop.Utilities;
namespace GongSolutions.Wpf.DragDrop
{
/// <summary>
/// Holds information about a 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
/// can start, and what the dragged data should be.
/// </remarks>
public class DragInfo : IDragInfo
{
/// <summary>
/// Initializes a new instance of the DragInfo class.
/// </summary>
///
/// <param name="sender">
/// The sender of the mouse event that initiated the drag.
/// </param>
///
/// <param name="e">
/// The mouse event that initiated the drag.
/// </param>
public DragInfo(object sender, MouseButtonEventArgs e)
{
this.Effects = DragDropEffects.None;
this.MouseButton = e.ChangedButton;
this.VisualSource = sender as UIElement;
this.DragStartPosition = e.GetPosition(this.VisualSource);
this.DragDropCopyKeyState = DragDrop.GetDragDropCopyKeyState(this.VisualSource);
var dataFormat = DragDrop.GetDataFormat(this.VisualSource);
if (dataFormat != null)
{
this.DataFormat = dataFormat;
}
var sourceElement = e.OriginalSource as UIElement;
// If we can't cast object as a UIElement it might be a FrameworkContentElement, if so try and use its parent.
if (sourceElement == null && e.OriginalSource is FrameworkContentElement)
{
sourceElement = ((FrameworkContentElement)e.OriginalSource).Parent as UIElement;
}
if (sender is ItemsControl)
{
var itemsControl = (ItemsControl)sender;
this.SourceGroup = itemsControl.FindGroup(this.DragStartPosition);
this.VisualSourceFlowDirection = itemsControl.GetItemsPanelFlowDirection();
UIElement item = null;
if (sourceElement != null)
{
item = itemsControl.GetItemContainer(sourceElement);
}
if (item == null)
{
if (DragDrop.GetDragDirectlySelectedOnly(this.VisualSource))
{
item = itemsControl.GetItemContainerAt(e.GetPosition(itemsControl));
}
else
{
item = itemsControl.GetItemContainerAt(e.GetPosition(itemsControl), itemsControl.GetItemsPanelOrientation());
}
}
if (item != null)
{
// Remember the relative position of the item being dragged
this.PositionInDraggedItem = e.GetPosition(item);
var itemParent = ItemsControl.ItemsControlFromItemContainer(item);
if (itemParent != null)
{
this.SourceCollection = itemParent.ItemsSource ?? itemParent.Items;
if (itemParent != itemsControl)
{
var tvItem = item as TreeViewItem;
if (tvItem != null)
{
var tv = tvItem.GetVisualAncestor<TreeView>();
if (tv != null && tv != itemsControl && !tv.IsDragSource())
{
return;
}
}
else if (itemsControl.ItemContainerGenerator.IndexFromContainer(itemParent) < 0 && !itemParent.IsDragSource())
{
return;
}
}
this.SourceIndex = itemParent.ItemContainerGenerator.IndexFromContainer(item);
this.SourceItem = itemParent.ItemContainerGenerator.ItemFromContainer(item);
}
else
{
this.SourceIndex = -1;
}
var selectedItems = itemsControl.GetSelectedItems().OfType<object>().Where(i => i != CollectionView.NewItemPlaceholder).ToList();
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
// 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.
if (selectedItems.Count <= 1 || this.SourceItem != null && !selectedItems.Contains(this.SourceItem))
{
this.SourceItems = Enumerable.Repeat(this.SourceItem, 1);
}
this.VisualSourceItem = item;
}
else
{
this.SourceCollection = itemsControl.ItemsSource ?? itemsControl.Items;
}
}
else
{
this.SourceItem = (sender as FrameworkElement)?.DataContext;
if (this.SourceItem != null)
{
this.SourceItems = Enumerable.Repeat(this.SourceItem, 1);
}
this.VisualSourceItem = sourceElement;
this.PositionInDraggedItem = sourceElement != null ? e.GetPosition(sourceElement) : this.DragStartPosition;
}
if (this.SourceItems == null)
{
this.SourceItems = Enumerable.Empty<object>();
}
}
internal void RefreshSelectedItems(object sender, MouseEventArgs e)
{
if (sender is ItemsControl)
{
var itemsControl = (ItemsControl)sender;
var selectedItems = itemsControl.GetSelectedItems().OfType<object>().Where(i => i != CollectionView.NewItemPlaceholder).ToList();
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
// 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.
if (selectedItems.Count <= 1 || this.SourceItem != null && !selectedItems.Contains(this.SourceItem))
{
this.SourceItems = Enumerable.Repeat(this.SourceItem, 1);
}
}
}
/// <summary>
/// Gets or sets the data format which will be used for the drag and drop actions.
/// </summary>
/// <value>The data format.</value>
public DataFormat DataFormat { get; set; } = DragDrop.DataFormat;
/// <summary>
/// Gets or sets the drag data.
/// </summary>
///
/// <remarks>
/// This must be set by a drag handler in order for a drag to start.
/// </remarks>
public object Data { get; set; }
/// <summary>
/// Gets the position of the click that initiated the drag, relative to <see cref="VisualSource"/>.
/// </summary>
public Point DragStartPosition { get; private set; }
/// <summary>
/// Gets the point where the cursor was relative to the item being dragged when the drag was started.
/// </summary>
public Point PositionInDraggedItem { get; private set; }
/// <summary>
/// Gets or sets the allowed effects for the drag.
/// </summary>
///
/// <remarks>
/// This must be set to a value other than <see cref="DragDropEffects.None"/> by a drag handler in order
/// for a drag to start.
/// </remarks>
public DragDropEffects Effects { get; set; }
/// <summary>
/// Gets the mouse button that initiated the drag.
/// </summary>
public MouseButton MouseButton { get; private set; }
/// <summary>
/// Gets the collection that the source ItemsControl is bound to.
/// </summary>
///
/// <remarks>
/// If the control that initated the drag is unbound or not an ItemsControl, this will be null.
/// </remarks>
public IEnumerable SourceCollection { get; private set; }
/// <summary>
/// Gets the position from where the item was dragged.
/// </summary>
/// <value>The index of the source.</value>
public int SourceIndex { get; private set; }
/// <summary>
/// Gets the object that a dragged item is bound to.
/// </summary>
///
/// <remarks>
/// If the control that initated the drag is unbound or not an ItemsControl, this will be null.
/// </remarks>
public object SourceItem { get; private set; }
/// <summary>
/// Gets a collection of objects that the selected items in an ItemsControl are bound to.
/// </summary>
///
/// <remarks>
/// If the control that initated the drag is unbound or not an ItemsControl, this will be empty.
/// </remarks>
public IEnumerable SourceItems { get; private set; }
/// <summary>
/// Gets the group from a dragged item if the drag is currently from an ItemsControl with groups.
/// </summary>
public CollectionViewGroup SourceGroup { get; private set; }
/// <summary>
/// Gets the control that initiated the drag.
/// </summary>
public UIElement VisualSource { get; private set; }
/// <summary>
/// Gets the item in an ItemsControl that started the drag.
/// </summary>
///
/// <remarks>
/// If the control that initiated the drag is an ItemsControl, this property will hold the item
/// container of the clicked item. For example, if <see cref="VisualSource"/> is a ListBox this
/// will hold a ListBoxItem.
/// </remarks>
public UIElement VisualSourceItem { get; private set; }
/// <summary>
/// Gets the FlowDirection of the current drag source.
/// </summary>
public FlowDirection VisualSourceFlowDirection { get; private set; }
/// <summary>
/// Gets the <see cref="IDataObject"/> which is used by the drag and drop operation. Set it to
/// a custom instance if custom drag and drop behavior is needed.
/// </summary>
public object DataObject { get; set; }
/// <summary>Initiates a drag-and-drop operation.</summary>
/// <returns>One of the <see cref="T:System.Windows.DragDropEffects" /> values that specifies the final effect that was performed during the drag-and-drop operation.</returns>
public Func<DependencyObject, object, DragDropEffects, DragDropEffects> DragDropHandler { get; set; } = System.Windows.DragDrop.DoDragDrop;
/// <summary>
/// Gets the drag drop copy key state indicating the effect of the drag drop operation.
/// </summary>
public DragDropKeyStates DragDropCopyKeyState { get; private set; }
}
}

View File

@@ -0,0 +1,452 @@
using System;
using System.Collections;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using GongSolutions.Wpf.DragDrop.Utilities;
using JetBrains.Annotations;
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 current drop target is valid, and by <see cref="IDropTarget.Drop"/> to perform the drop.
/// </remarks>
public class DropInfo : IDropInfo
{
private ItemsControl itemParent = null;
private UIElement item = null;
/// <summary>
/// Initializes a new instance of the DropInfo class.
/// </summary>
///
/// <param name="sender">
/// The sender of the drag event.
/// </param>
///
/// <param name="e">
/// The drag event.
/// </param>
///
/// <param name="dragInfo">
/// Information about the source of the drag, if the drag came from within the framework.
/// </param>
///
/// <param name="eventType">
/// The type of the underlying event (tunneled or bubbled).
/// </param>
public DropInfo(object sender, DragEventArgs e, [CanBeNull] DragInfo dragInfo, EventType eventType)
{
this.DragInfo = dragInfo;
this.KeyStates = e.KeyStates;
this.EventType = eventType;
var dataFormat = dragInfo?.DataFormat;
this.Data = dataFormat != null && e.Data.GetDataPresent(dataFormat.Name) ? e.Data.GetData(dataFormat.Name) : e.Data;
this.VisualTarget = sender as UIElement;
// if there is no drop target, find another
if (!this.VisualTarget.IsDropTarget())
{
// try to find next element
var element = this.VisualTarget.TryGetNextAncestorDropTargetElement();
if (element != null)
{
this.VisualTarget = element;
}
}
// try find ScrollViewer
var dropTargetScrollViewer = DragDrop.GetDropTargetScrollViewer(this.VisualTarget);
if (dropTargetScrollViewer != null)
{
this.TargetScrollViewer = dropTargetScrollViewer;
}
else if (this.VisualTarget is TabControl)
{
var tabPanel = this.VisualTarget.GetVisualDescendent<TabPanel>();
this.TargetScrollViewer = tabPanel?.GetVisualAncestor<ScrollViewer>();
}
else
{
this.TargetScrollViewer = this.VisualTarget?.GetVisualDescendent<ScrollViewer>();
}
this.TargetScrollingMode = this.VisualTarget != null ? DragDrop.GetDropScrollingMode(this.VisualTarget) : ScrollingMode.Both;
// 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)
{
if (!HitTestUtilities.HitTest4Type<TabPanel>(this.VisualTarget, this.DropPosition))
{
return;
}
}
if (this.VisualTarget is ItemsControl)
{
var itemsControl = (ItemsControl)this.VisualTarget;
//System.Diagnostics.Debug.WriteLine(">>> Name = {0}", itemsControl.Name);
// get item under the mouse
item = itemsControl.GetItemContainerAt(this.DropPosition);
var directlyOverItem = item != null;
this.TargetGroup = itemsControl.FindGroup(this.DropPosition);
this.VisualTargetOrientation = itemsControl.GetItemsPanelOrientation();
this.VisualTargetFlowDirection = itemsControl.GetItemsPanelFlowDirection();
if (item == null)
{
// ok, no item found, so maybe we can found an item at top, left, right or bottom
item = itemsControl.GetItemContainerAt(this.DropPosition, this.VisualTargetOrientation);
directlyOverItem = DropPosition.DirectlyOverElement(this.item, itemsControl);
}
if (item == null && this.TargetGroup != null && this.TargetGroup.IsBottomLevel)
{
var itemData = this.TargetGroup.Items.FirstOrDefault();
if (itemData != null)
{
item = itemsControl.ItemContainerGenerator.ContainerFromItem(itemData) as UIElement;
directlyOverItem = DropPosition.DirectlyOverElement(this.item, itemsControl);
}
}
if (item != null)
{
itemParent = ItemsControl.ItemsControlFromItemContainer(item);
this.VisualTargetOrientation = itemParent.GetItemsPanelOrientation();
this.VisualTargetFlowDirection = itemParent.GetItemsPanelFlowDirection();
this.InsertIndex = itemParent.ItemContainerGenerator.IndexFromContainer(item);
this.TargetCollection = itemParent.ItemsSource ?? itemParent.Items;
var tvItem = item as TreeViewItem;
if (directlyOverItem || tvItem != null)
{
this.VisualTargetItem = item;
this.TargetItem = itemParent.ItemContainerGenerator.ItemFromContainer(item);
}
var expandedTVItem = tvItem != null && tvItem.HasHeader && tvItem.HasItems && tvItem.IsExpanded;
var itemRenderSize = expandedTVItem ? tvItem.GetHeaderSize() : item.RenderSize;
if (this.VisualTargetOrientation == Orientation.Vertical)
{
var currentYPos = e.GetPosition(item).Y;
var targetHeight = itemRenderSize.Height;
var topGap = targetHeight * 0.25;
var bottomGap = targetHeight * 0.75;
if (currentYPos > targetHeight / 2)
{
if (expandedTVItem && (currentYPos < topGap || currentYPos > bottomGap))
{
this.VisualTargetItem = tvItem.ItemContainerGenerator.ContainerFromIndex(0) as UIElement;
this.TargetItem = this.VisualTargetItem != null ? tvItem.ItemContainerGenerator.ItemFromContainer(this.VisualTargetItem) : null;
this.TargetCollection = tvItem.ItemsSource ?? tvItem.Items;
this.InsertIndex = 0;
this.InsertPosition = RelativeInsertPosition.BeforeTargetItem;
}
else
{
this.InsertIndex++;
this.InsertPosition = RelativeInsertPosition.AfterTargetItem;
}
}
else
{
this.InsertPosition = RelativeInsertPosition.BeforeTargetItem;
}
if (currentYPos > topGap && currentYPos < bottomGap)
{
if (tvItem != null)
{
this.TargetCollection = tvItem.ItemsSource ?? tvItem.Items;
this.InsertIndex = this.TargetCollection != null ? this.TargetCollection.OfType<object>().Count() : 0;
}
this.InsertPosition |= RelativeInsertPosition.TargetItemCenter;
}
//System.Diagnostics.Debug.WriteLine("==> DropInfo: pos={0}, idx={1}, Y={2}, Item={3}", this.InsertPosition, this.InsertIndex, currentYPos, item);
}
else
{
var currentXPos = e.GetPosition(item).X;
var targetWidth = itemRenderSize.Width;
if (this.VisualTargetFlowDirection == FlowDirection.RightToLeft)
{
if (currentXPos > targetWidth / 2)
{
this.InsertPosition = RelativeInsertPosition.BeforeTargetItem;
}
else
{
this.InsertIndex++;
this.InsertPosition = RelativeInsertPosition.AfterTargetItem;
}
}
else if (this.VisualTargetFlowDirection == FlowDirection.LeftToRight)
{
if (currentXPos > targetWidth / 2)
{
this.InsertIndex++;
this.InsertPosition = RelativeInsertPosition.AfterTargetItem;
}
else
{
this.InsertPosition = RelativeInsertPosition.BeforeTargetItem;
}
}
if (currentXPos > targetWidth * 0.25 && currentXPos < targetWidth * 0.75)
{
if (tvItem != null)
{
this.TargetCollection = tvItem.ItemsSource ?? tvItem.Items;
this.InsertIndex = this.TargetCollection != null ? this.TargetCollection.OfType<object>().Count() : 0;
}
this.InsertPosition |= RelativeInsertPosition.TargetItemCenter;
}
//System.Diagnostics.Debug.WriteLine("==> DropInfo: pos={0}, idx={1}, X={2}, Item={3}", this.InsertPosition, this.InsertIndex, currentXPos, item);
}
}
else
{
this.TargetCollection = itemsControl.ItemsSource ?? itemsControl.Items;
this.InsertIndex = itemsControl.Items.Count;
//System.Diagnostics.Debug.WriteLine("==> DropInfo: pos={0}, item=NULL, idx={1}", this.InsertPosition, this.InsertIndex);
}
}
else
{
this.VisualTargetItem = this.VisualTarget;
}
}
/// <summary>
/// Gets the drag data.
/// </summary>
///
/// <remarks>
/// If the drag came from within the framework, this will hold:
///
/// - The dragged data if a single item was dragged.
/// - A typed IEnumerable if multiple items were dragged.
/// </remarks>
public object Data { get; private set; }
/// <summary>
/// Gets a <see cref="DragInfo"/> object holding information about the source of the drag,
/// if the drag came from within the framework.
/// </summary>
public IDragInfo DragInfo { get; private set; }
/// <summary>
/// Gets the mouse position relative to the VisualTarget
/// </summary>
public Point DropPosition { get; private set; }
/// <summary>
/// Gets or sets the class of drop target to display.
/// </summary>
///
/// <remarks>
/// The standard drop target adorner classes are held in the <see cref="DropTargetAdorners"/>
/// class.
/// </remarks>
public Type DropTargetAdorner { get; set; }
/// <summary>
/// Gets or sets the allowed effects for the drop.
/// </summary>
///
/// <remarks>
/// This must be set to a value other than <see cref="DragDropEffects.None"/> by a drop handler in order
/// for a drop to be possible.
/// </remarks>
public DragDropEffects Effects { get; set; }
/// <summary>
/// Gets the current insert position within <see cref="TargetCollection"/>.
/// </summary>
public int InsertIndex { get; private set; }
/// <summary>
/// Gets the current insert position within the source (unfiltered) <see cref="TargetCollection"/>.
/// </summary>
/// <remarks>
/// This should be only used in a Drop action.
/// This works only correct with different objects (string, int, etc won't work correct).
/// </remarks>
public int UnfilteredInsertIndex
{
get
{
var insertIndex = this.InsertIndex;
if (itemParent != null)
{
var itemSourceAsList = itemParent.ItemsSource.TryGetList();
if (itemSourceAsList != null && itemParent.Items != null && itemParent.Items.Count != itemSourceAsList.Count)
{
if (insertIndex >= 0 && insertIndex < itemParent.Items.Count)
{
var indexOf = itemSourceAsList.IndexOf(itemParent.Items[insertIndex]);
if (indexOf >= 0)
{
return indexOf;
}
}
else if (itemParent.Items.Count > 0 && insertIndex == itemParent.Items.Count)
{
var indexOf = itemSourceAsList.IndexOf(itemParent.Items[insertIndex - 1]);
if (indexOf >= 0)
{
return indexOf + 1;
}
}
}
}
return insertIndex;
}
}
/// <summary>
/// Gets the collection that the target ItemsControl is bound to.
/// </summary>
///
/// <remarks>
/// If the current drop target is unbound or not an ItemsControl, this will be null.
/// </remarks>
public IEnumerable TargetCollection { get; private set; }
/// <summary>
/// Gets the object that the current drop target is bound to.
/// </summary>
///
/// <remarks>
/// If the current drop target is unbound or not an ItemsControl, this will be null.
/// </remarks>
public object TargetItem { get; private set; }
/// <summary>
/// Gets the current group target.
/// </summary>
///
/// <remarks>
/// If the drag is currently over an ItemsControl with groups, describes the group that
/// the drag is currently over.
/// </remarks>
public CollectionViewGroup TargetGroup { get; private set; }
/// <summary>
/// Gets the ScrollViewer control for the visual target.
/// </summary>
public ScrollViewer TargetScrollViewer { get; private set; }
/// <summary>
/// Gets or Sets the ScrollingMode for the drop action.
/// </summary>
public ScrollingMode TargetScrollingMode { get; set; }
/// <summary>
/// Gets the control that is the current drop target.
/// </summary>
public UIElement VisualTarget { get; private set; }
/// <summary>
/// Gets the item in an ItemsControl that is the current drop target.
/// </summary>
///
/// <remarks>
/// If the current drop target is unbound or not an ItemsControl, this will be null.
/// </remarks>
public UIElement VisualTargetItem { get; private set; }
/// <summary>
/// Gets the orientation of the current drop target.
/// </summary>
public Orientation VisualTargetOrientation { get; private set; }
/// <summary>
/// Gets the orientation of the current drop target.
/// </summary>
public FlowDirection VisualTargetFlowDirection { get; private set; }
/// <summary>
/// Gets and sets the text displayed in the DropDropEffects adorner.
/// </summary>
public string DestinationText { get; set; }
/// <summary>
/// Gets and sets the effect text displayed in the DropDropEffects adorner.
/// </summary>
public string EffectText { get; set; }
/// <summary>
/// Gets the relative position the item will be inserted to compared to the TargetItem
/// </summary>
public RelativeInsertPosition InsertPosition { get; private set; }
/// <summary>
/// Gets a flag enumeration indicating the current state of the SHIFT, CTRL, and ALT keys, as well as the state of the mouse buttons.
/// </summary>
public DragDropKeyStates KeyStates { get; private set; }
public bool NotHandled { get; set; }
/// <summary>
/// Gets a value indicating whether the target is in the same context as the source, <see cref="DragDrop.DragDropContextProperty" />.
/// </summary>
public bool IsSameDragDropContextAsSource
{
get
{
// Check if DragInfo stuff exists
if (this.DragInfo == null || this.DragInfo.VisualSource == null)
{
return true;
}
// A target should be exists
if (this.VisualTarget == null)
{
return true;
}
// Source element has a drag context constraint, we need to check the target property matches.
var sourceContext = this.DragInfo.VisualSource.GetValue(DragDrop.DragDropContextProperty) as string;
if (String.IsNullOrEmpty(sourceContext))
{
return true;
}
var targetContext = this.VisualTarget.GetValue(DragDrop.DragDropContextProperty) as string;
return string.Equals(sourceContext, targetContext);
}
}
/// <summary>
/// Gets the current mode of the underlying routed event.
/// </summary>
public EventType EventType { get; }
}
[Flags]
public enum RelativeInsertPosition
{
None = 0,
BeforeTargetItem = 1,
AfterTargetItem = 2,
TargetItemCenter = 4
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
namespace GongSolutions.Wpf.DragDrop
{
public abstract class DropTargetAdorner : Adorner
{
[Obsolete("This constructor is obsolete and will be deleted in next major release.")]
public DropTargetAdorner(UIElement adornedElement)
: this(adornedElement, (DropInfo)null)
{
}
public DropTargetAdorner(UIElement adornedElement, DropInfo dropInfo)
: base(adornedElement)
{
this.DropInfo = dropInfo;
this.IsHitTestVisible = false;
this.AllowDrop = false;
this.SnapsToDevicePixels = true;
this.m_AdornerLayer = AdornerLayer.GetAdornerLayer(adornedElement);
this.m_AdornerLayer.Add(this);
}
public DropInfo DropInfo { get; set; }
/// <summary>
/// Gets or Sets the pen which can be used for the render process.
/// </summary>
public Pen Pen { get; set; } = new Pen(Brushes.Gray, 2);
public void Detatch()
{
this.m_AdornerLayer.Remove(this);
}
internal static DropTargetAdorner Create(Type type, UIElement adornedElement, IDropInfo dropInfo)
{
if (!typeof(DropTargetAdorner).IsAssignableFrom(type))
{
throw new InvalidOperationException("The requested adorner class does not derive from DropTargetAdorner.");
}
return type.GetConstructor(new[] { typeof(UIElement), typeof(DropInfo) })?.Invoke(new object[] { adornedElement, dropInfo }) as DropTargetAdorner;
}
private readonly AdornerLayer m_AdornerLayer;
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace GongSolutions.Wpf.DragDrop
{
public class DropTargetAdorners
{
/// <summary>
/// Gets the type of the default highlight target adorner.
/// </summary>
public static Type Highlight { get; } = typeof(DropTargetHighlightAdorner);
/// <summary>
/// Gets the type of the default insert target adorner.
/// </summary>
public static Type Insert { get; } = typeof(DropTargetInsertionAdorner);
}
}

View File

@@ -0,0 +1,53 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace GongSolutions.Wpf.DragDrop
{
public class DropTargetHighlightAdorner : DropTargetAdorner
{
[Obsolete("This constructor is obsolete and will be deleted in next major release.")]
public DropTargetHighlightAdorner(UIElement adornedElement)
: base(adornedElement, (DropInfo)null)
{
}
public DropTargetHighlightAdorner(UIElement adornedElement, DropInfo dropInfo)
: base(adornedElement, dropInfo)
{
}
/// <summary>
/// When overridden in a derived class, participates in rendering operations that are directed by the layout system.
/// The rendering instructions for this element are not used directly when this method is invoked, and are instead preserved for
/// later asynchronous use by layout and drawing.
/// </summary>
/// <param name="drawingContext">The drawing instructions for a specific element. This context is provided to the layout system.</param>
protected override void OnRender(DrawingContext drawingContext)
{
var dropInfo = this.DropInfo;
var visualTargetItem = dropInfo.VisualTargetItem;
if (visualTargetItem != null)
{
var rect = Rect.Empty;
var tvItem = visualTargetItem as TreeViewItem;
if (tvItem != null && VisualTreeHelper.GetChildrenCount(tvItem) > 0)
{
var descendant = VisualTreeHelper.GetDescendantBounds(tvItem);
var translatePoint = tvItem.TranslatePoint(new Point(), this.AdornedElement);
var itemRect = new Rect(translatePoint, tvItem.RenderSize);
descendant.Union(itemRect);
translatePoint.Offset(1, 0);
rect = new Rect(translatePoint, new Size(descendant.Width - translatePoint.X - 1, tvItem.ActualHeight));
}
if (rect.IsEmpty)
{
rect = new Rect(visualTargetItem.TranslatePoint(new Point(), this.AdornedElement), VisualTreeHelper.GetDescendantBounds(visualTargetItem).Size);
}
drawingContext.DrawRoundedRectangle(null, this.Pen, rect, 2, 2);
}
}
}
}

View File

@@ -0,0 +1,213 @@
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Controls;
using GongSolutions.Wpf.DragDrop.Utilities;
using System.Windows.Controls.Primitives;
namespace GongSolutions.Wpf.DragDrop
{
public class DropTargetInsertionAdorner : DropTargetAdorner
{
[Obsolete("This constructor is obsolete and will be deleted in next major release.")]
public DropTargetInsertionAdorner(UIElement adornedElement)
: base(adornedElement, (DropInfo)null)
{
}
public DropTargetInsertionAdorner(UIElement adornedElement, DropInfo dropInfo)
: base(adornedElement, dropInfo)
{
}
/// <summary>
/// When overridden in a derived class, participates in rendering operations that are directed by the layout system.
/// The rendering instructions for this element are not used directly when this method is invoked, and are instead preserved for
/// later asynchronous use by layout and drawing.
/// </summary>
/// <param name="drawingContext">The drawing instructions for a specific element. This context is provided to the layout system.</param>
protected override void OnRender(DrawingContext drawingContext)
{
var dropInfo = this.DropInfo;
var itemsControl = dropInfo.VisualTarget as ItemsControl;
if (itemsControl != null)
{
// Get the position of the item at the insertion index. If the insertion point is
// to be after the last item, then get the position of the last item and add an
// offset later to draw it at the end of the list.
ItemsControl itemParent;
var visualTargetItem = dropInfo.VisualTargetItem;
if (visualTargetItem != null)
{
itemParent = ItemsControl.ItemsControlFromItemContainer(visualTargetItem);
}
else
{
itemParent = itemsControl;
}
// this could be happen with a thread scenario where items are removed very quickly
if (itemParent == null)
{
return;
}
var itemsCount = itemParent.Items.Count;
var index = Math.Min(dropInfo.InsertIndex, itemsCount - 1);
var lastItemInGroup = false;
var targetGroup = dropInfo.TargetGroup;
if (targetGroup != null && targetGroup.IsBottomLevel && dropInfo.InsertPosition.HasFlag(RelativeInsertPosition.AfterTargetItem))
{
var indexOf = targetGroup.Items.IndexOf(dropInfo.TargetItem);
lastItemInGroup = indexOf == targetGroup.ItemCount - 1;
if (lastItemInGroup && dropInfo.InsertIndex != itemsCount)
{
index--;
}
}
var itemContainer = (UIElement)itemParent.ItemContainerGenerator.ContainerFromIndex(index);
var showAlwaysDropTargetAdorner = itemContainer == null && DragDrop.GetShowAlwaysDropTargetAdorner(itemParent);
if (showAlwaysDropTargetAdorner)
{
itemContainer = itemParent;
}
if (itemContainer != null)
{
var itemRect = new Rect(itemContainer.TranslatePoint(new Point(), this.AdornedElement), itemContainer.RenderSize);
Point point1,
point2;
double rotation = 0;
// I really don't know why I did this
//
// var viewportWidth = double.MaxValue;
// var viewportHeight = double.MaxValue;
// if (DropInfo.TargetScrollViewer != null)
// {
// if (DropInfo.TargetScrollViewer.ScrollableWidth != 0)
// {
// viewportWidth = DropInfo.TargetScrollViewer.ViewportWidth;
// }
//
// if (DropInfo.TargetScrollViewer.ScrollableHeight != 0)
// {
// viewportHeight = DropInfo.TargetScrollViewer.ViewportHeight;
// }
// }
if (dropInfo.VisualTargetOrientation == Orientation.Vertical)
{
if ((dropInfo.InsertIndex == itemsCount) || lastItemInGroup)
{
if (itemsCount > 0)
{
itemRect.Y += itemContainer.RenderSize.Height;
}
else
{
if ((itemsControl as ListView)?.View is GridView)
{
var header = itemsControl.GetVisualDescendent<GridViewHeaderRowPresenter>();
if (header != null)
{
itemRect.Y += header.RenderSize.Height;
}
}
else if (itemsControl is DataGrid)
{
var header = itemsControl.GetVisualDescendent<DataGridColumnHeadersPresenter>();
if (header != null)
{
itemRect.Y += header.RenderSize.Height;
}
}
itemRect.Y += this.Pen.Thickness;
}
}
var itemRectRight = itemRect.Right; //Math.Min(itemRect.Right, viewportWidth);
var itemRectLeft = itemRect.X < 0 ? 0 : itemRect.X;
point1 = new Point(itemRectLeft, itemRect.Y);
point2 = new Point(itemRectRight, itemRect.Y);
}
else
{
if (dropInfo.VisualTargetFlowDirection == FlowDirection.LeftToRight && dropInfo.InsertIndex == itemsCount)
{
if (itemsCount > 0)
{
itemRect.X += itemContainer.RenderSize.Width;
}
else
{
itemRect.X += this.Pen.Thickness;
}
}
else if (dropInfo.VisualTargetFlowDirection == FlowDirection.RightToLeft && dropInfo.InsertIndex != itemsCount)
{
if (itemsCount > 0)
{
itemRect.X += itemContainer.RenderSize.Width;
}
else
{
itemRect.X += this.Pen.Thickness;
}
}
var itemRectTop = itemRect.Y < 0 ? 0 : itemRect.Y;
var itemRectBottom = itemRect.Bottom; //Math.Min(itemRect.Bottom, viewportHeight);
point1 = new Point(itemRect.X, itemRectTop);
point2 = new Point(itemRect.X, itemRectBottom);
rotation = 90;
}
drawingContext.DrawLine(this.Pen, point1, point2);
this.DrawTriangle(drawingContext, point1, rotation);
this.DrawTriangle(drawingContext, point2, 180 + rotation);
}
}
}
private void DrawTriangle(DrawingContext drawingContext, Point origin, double rotation)
{
drawingContext.PushTransform(new TranslateTransform(origin.X, origin.Y));
drawingContext.PushTransform(new RotateTransform(rotation));
drawingContext.DrawGeometry(this.Pen.Brush, null, m_Triangle);
drawingContext.Pop();
drawingContext.Pop();
}
static DropTargetInsertionAdorner()
{
// Create the pen and triangle in a static constructor and freeze them to improve performance.
const int triangleSize = 5;
var firstLine = new LineSegment(new Point(0, -triangleSize), false);
firstLine.Freeze();
var secondLine = new LineSegment(new Point(0, triangleSize), false);
secondLine.Freeze();
var figure = new PathFigure { StartPoint = new Point(triangleSize, 0) };
figure.Segments.Add(firstLine);
figure.Segments.Add(secondLine);
figure.Freeze();
m_Triangle = new PathGeometry();
m_Triangle.Figures.Add(figure);
m_Triangle.Freeze();
}
private static readonly PathGeometry m_Triangle;
}
}

View File

@@ -0,0 +1,10 @@
namespace GongSolutions.Wpf.DragDrop
{
public enum EventType
{
Auto,
Tunneled,
Bubbled,
TunneledBubbled
}
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<!-- Project properties -->
<PropertyGroup>
<TargetFrameworks>net45;net46;netcoreapp3.1</TargetFrameworks>
<AssemblyName>GongSolutions.WPF.DragDrop</AssemblyName>
<Title>gong-wpf-dragdrop</Title>
<RootNamespace>GongSolutions.WPF.DragDrop</RootNamespace>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<!-- reference includes -->
<ItemGroup>
<PackageReference Include="WpfAnalyzers" Version="2.4.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="JetBrains.Annotations" Version="2020.3.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,124 @@
using System;
using System.Collections;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
namespace GongSolutions.Wpf.DragDrop
{
public interface IDragInfo
{
/// <summary>
/// Gets or sets the data format which will be used for the drag and drop actions.
/// </summary>
/// <value>The data format.</value>
DataFormat DataFormat { get; set; }
/// <summary>
/// Gets or sets the drag data.
/// </summary>
///
/// <remarks>
/// This must be set by a drag handler in order for a drag to start.
/// </remarks>
object Data { get; set; }
/// <summary>
/// Gets the position of the click that initiated the drag, relative to <see cref="VisualSource"/>.
/// </summary>
Point DragStartPosition { get; }
/// <summary>
/// Gets the point where the cursor was relative to the item being dragged when the drag was started.
/// </summary>
Point PositionInDraggedItem { get; }
/// <summary>
/// Gets or sets the allowed effects for the drag.
/// </summary>
///
/// <remarks>
/// This must be set to a value other than <see cref="DragDropEffects.None"/> by a drag handler in order
/// for a drag to start.
/// </remarks>
DragDropEffects Effects { get; set; }
/// <summary>
/// Gets the mouse button that initiated the drag.
/// </summary>
MouseButton MouseButton { get; }
/// <summary>
/// Gets the collection that the source ItemsControl is bound to.
/// </summary>
///
/// <remarks>
/// If the control that initated the drag is unbound or not an ItemsControl, this will be null.
/// </remarks>
IEnumerable SourceCollection { get; }
/// <summary>
/// Gets the position from where the item was dragged.
/// </summary>
int SourceIndex { get; }
/// <summary>
/// Gets the object that a dragged item is bound to.
/// </summary>
///
/// <remarks>
/// If the control that initated the drag is unbound or not an ItemsControl, this will be null.
/// </remarks>
object SourceItem { get; }
/// <summary>
/// Gets a collection of objects that the selected items in an ItemsControl are bound to.
/// </summary>
///
/// <remarks>
/// If the control that initated the drag is unbound or not an ItemsControl, this will be empty.
/// </remarks>
IEnumerable SourceItems { get; }
/// <summary>
/// Gets the group from a dragged item if the drag is currently from an ItemsControl with groups.
/// </summary>
CollectionViewGroup SourceGroup { get; }
/// <summary>
/// Gets the control that initiated the drag.
/// </summary>
UIElement VisualSource { get; }
/// <summary>
/// Gets the item in an ItemsControl that started the drag.
/// </summary>
///
/// <remarks>
/// If the control that initiated the drag is an ItemsControl, this property will hold the item
/// container of the clicked item. For example, if <see cref="VisualSource"/> is a ListBox this
/// will hold a ListBoxItem.
/// </remarks>
UIElement VisualSourceItem { get; }
/// <summary>
/// Gets the FlowDirection of the current drag source.
/// </summary>
FlowDirection VisualSourceFlowDirection { get; }
/// <summary>
/// Gets the <see cref="IDataObject"/> which is used by the drag and drop operation. Set it to
/// a custom instance if custom drag and drop behavior is needed.
/// </summary>
object DataObject { get; set; }
/// <summary>Initiates a drag-and-drop operation.</summary>
/// <returns>One of the <see cref="T:System.Windows.DragDropEffects" /> values that specifies the final effect that was performed during the drag-and-drop operation.</returns>
Func<DependencyObject, object, DragDropEffects, DragDropEffects> DragDropHandler { get; set; }
/// <summary>
/// Gets the drag drop copy key state indicating the effect of the drag drop operation.
/// </summary>
DragDropKeyStates DragDropCopyKeyState { get; }
}
}

View File

@@ -0,0 +1,64 @@
using System;
using System.Windows;
namespace GongSolutions.Wpf.DragDrop
{
/// <summary>
/// Interface implemented by Drag Handlers.
/// </summary>
public interface IDragSource
{
/// <summary>
/// Queries whether a drag can be started.
/// </summary>
///
/// <param name="dragInfo">
/// Information about the drag.
/// </param>
///
/// <remarks>
/// To allow a drag to be started, the <see cref="DragInfo.Effects"/> property on <paramref name="dragInfo"/>
/// should be set to a value other than <see cref="DragDropEffects.None"/>.
/// </remarks>
void StartDrag(IDragInfo dragInfo);
/// <summary>
/// With this action it's possible to check if the drag/drop operation is allowed to start
/// e.g. check for a UIElement inside a list view item, that should not start a drag/drop operation
/// </summary>
bool CanStartDrag(IDragInfo dragInfo);
/// <summary>
/// Notifies the drag handler that a drop has occurred.
/// </summary>
///
/// <param name="dropInfo">
/// Information about the drop.
/// </param>
void Dropped(IDropInfo dropInfo);
/// <summary>
/// Notifies the drag handler that a drag and drop operation has finished.
/// </summary>
/// <param name="operationResult">The operation result.</param>
/// <param name="dragInfo">The drag information.</param>
void DragDropOperationFinished(DragDropEffects operationResult, IDragInfo dragInfo);
/// <summary>
/// Notifies the drag handler that a drag has been aborted.
/// </summary>
void DragCancelled();
/// <summary>
/// Notifies that an exception has occurred upon dragging.
/// </summary>
/// <param name="exception">
/// The exception that occurrred.
/// </param>
/// <returns>
/// Boolean indicating whether the exception is handled in the drag handler.
/// False will rethrow the exception.
/// </returns>
bool TryCatchOccurredException(Exception exception);
}
}

View File

@@ -0,0 +1,155 @@
using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace GongSolutions.Wpf.DragDrop
{
public interface IDropInfo
{
/// <summary>
/// Gets the drag data.
/// </summary>
///
/// <remarks>
/// If the drag came from within the framework, this will hold:
///
/// - The dragged data if a single item was dragged.
/// - A typed IEnumerable if multiple items were dragged.
/// </remarks>
object Data { get; }
/// <summary>
/// Gets a <see cref="DragInfo"/> object holding information about the source of the drag,
/// if the drag came from within the framework.
/// </summary>
IDragInfo DragInfo { get; }
/// <summary>
/// Gets the mouse position relative to the VisualTarget
/// </summary>
Point DropPosition { get; }
/// <summary>
/// Gets or sets the class of drop target to display.
/// </summary>
///
/// <remarks>
/// The standard drop target adorner classes are held in the <see cref="DropTargetAdorners"/>
/// class.
/// </remarks>
Type DropTargetAdorner { get; set; }
/// <summary>
/// Gets or sets the allowed effects for the drop.
/// </summary>
///
/// <remarks>
/// This must be set to a value other than <see cref="DragDropEffects.None"/> by a drop handler in order
/// for a drop to be possible.
/// </remarks>
DragDropEffects Effects { get; set; }
/// <summary>
/// Gets the current insert position within <see cref="TargetCollection"/>.
/// </summary>
int InsertIndex { get; }
/// <summary>
/// Gets the current insert position within the source (unfiltered) <see cref="TargetCollection"/>.
/// </summary>
/// <remarks>
/// This should be only used in a Drop action.
/// This works only correct with different objects (string, int, etc won't work correct).
/// </remarks>
int UnfilteredInsertIndex { get; }
/// <summary>
/// Gets the collection that the target ItemsControl is bound to.
/// </summary>
///
/// <remarks>
/// If the current drop target is unbound or not an ItemsControl, this will be null.
/// </remarks>
IEnumerable TargetCollection { get; }
/// <summary>
/// Gets the object that the current drop target is bound to.
/// </summary>
///
/// <remarks>
/// If the current drop target is unbound or not an ItemsControl, this will be null.
/// </remarks>
object TargetItem { get; }
/// <summary>
/// Gets the current group target.
/// </summary>
///
/// <remarks>
/// If the drag is currently over an ItemsControl with groups, describes the group that
/// the drag is currently over.
/// </remarks>
CollectionViewGroup TargetGroup { get; }
/// <summary>
/// Gets the control that is the current drop target.
/// </summary>
UIElement VisualTarget { get; }
/// <summary>
/// Gets the item in an ItemsControl that is the current drop target.
/// </summary>
///
/// <remarks>
/// If the current drop target is unbound or not an ItemsControl, this will be null.
/// </remarks>
UIElement VisualTargetItem { get; }
/// <summary>
/// Gets the orientation of the current drop target.
/// </summary>
Orientation VisualTargetOrientation { get; }
/// <summary>
/// Gets the FlowDirection of the current drop target.
/// </summary>
FlowDirection VisualTargetFlowDirection { get; }
/// <summary>
/// Gets and sets the text displayed in the DropDropEffects adorner.
/// </summary>
string DestinationText { get; set; }
/// <summary>
/// Gets and sets the effect text displayed in the DropDropEffects adorner.
/// </summary>
string EffectText { get; set; }
/// <summary>
/// Gets the relative position the item will be inserted to compared to the TargetItem
/// </summary>
RelativeInsertPosition InsertPosition { get; }
/// <summary>
/// Gets a flag enumeration indicating the current state of the SHIFT, CTRL, and ALT keys, as well as the state of the mouse buttons.
/// </summary>
DragDropKeyStates KeyStates { get; }
/// <summary>
/// Indicates if the drop info should be handled by itself (useful for child elements)
/// </summary>
bool NotHandled { get; set; }
/// <summary>
/// Gets a value indicating whether the target is in the same context as the source, <see cref="DragDrop.DragDropContextProperty" />.
/// </summary>
bool IsSameDragDropContextAsSource { get; }
/// <summary>
/// Gets the current mode of the underlying routed event.
/// </summary>
EventType EventType { get; }
}
}

View File

@@ -0,0 +1,34 @@
using System.Windows;
namespace GongSolutions.Wpf.DragDrop
{
/// <summary>
/// Interface implemented by Drop Handlers.
/// </summary>
public interface IDropTarget
{
/// <summary>
/// Updates the current drag state.
/// </summary>
///
/// <param name="dropInfo">
/// Information about the drag.
/// </param>
///
/// <remarks>
/// To allow a drop at the current drag position, the <see cref="DropInfo.Effects"/> property on
/// <paramref name="dropInfo"/> should be set to a value other than <see cref="DragDropEffects.None"/>
/// and <see cref="DropInfo.Data"/> should be set to a non-null value.
/// </remarks>
void DragOver(IDropInfo dropInfo);
/// <summary>
/// Performs a drop.
/// </summary>
///
/// <param name="dropInfo">
/// Information about the drop.
/// </param>
void Drop(IDropInfo dropInfo);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 B

View File

@@ -0,0 +1,111 @@
using System;
using System.IO;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace GongSolutions.Wpf.DragDrop.Icons
{
/// <summary>
/// Static class to provide access to standard application images
/// </summary>
public static class IconFactory
{
/// <summary>
/// Gets the icon which can be used for the effect type None.
/// </summary>
public static BitmapImage EffectNone { get; } = GetImage("EffectNone.png", 12);
/// <summary>
/// Gets the icon which can be used for the effect type Copy.
/// </summary>
public static BitmapImage EffectCopy { get; } = GetImage("EffectCopy.png", 12);
/// <summary>
/// Gets the icon which can be used for the effect type Move.
/// </summary>
public static BitmapImage EffectMove { get; } = GetImage("EffectMove.png", 12);
/// <summary>
/// Gets the icon which can be used for the effect type Link.
/// </summary>
public static BitmapImage EffectLink { get; } = GetImage("EffectLink.png", 12);
/// <summary>
/// Loads an image based on the name and size required.
/// Images need to be marked as 'Resource' in the project for them be loaded.
/// </summary>
/// <param name="iconName">Name of the icon</param>
/// <param name="size">The size of the icon</param>
/// <returns>The image to be displayed</returns>
private static BitmapImage GetImage(string iconName, int size)
{
var uri = new Uri($@"pack://application:,,,/GongSolutions.Wpf.DragDrop;component/Icons/{iconName}", UriKind.RelativeOrAbsolute);
var icon = new BitmapImage(uri);
icon.DecodePixelHeight = size;
icon.DecodePixelWidth = size;
return icon;
}
/// <summary>
/// Creates a custom cursor.
/// </summary>
/// <param name="rx"></param>
/// <param name="ry"></param>
/// <param name="brush"></param>
/// <param name="pen"></param>
/// <returns></returns>
public static Cursor CreateCursor(double rx, double ry, SolidColorBrush brush, Pen pen)
{
var vis = new DrawingVisual();
using (var dc = vis.RenderOpen())
{
dc.DrawRectangle(brush, new Pen(Brushes.Black, 0.1), new Rect(0, 0, rx, ry));
dc.Close();
}
var rtb = new RenderTargetBitmap(64, 64, 96, 96, PixelFormats.Pbgra32);
rtb.Render(vis);
using (var ms1 = new MemoryStream())
{
var penc = new PngBitmapEncoder();
penc.Frames.Add(BitmapFrame.Create(rtb));
penc.Save(ms1);
var pngBytes = ms1.ToArray();
var size = pngBytes.GetLength(0);
//.cur format spec http://en.wikipedia.org/wiki/ICO_(file_format)
using (var ms = new MemoryStream())
{
{
//ICONDIR Structure
ms.Write(BitConverter.GetBytes((Int16)0), 0, 2); //Reserved must be zero; 2 bytes
ms.Write(BitConverter.GetBytes((Int16)2), 0, 2); //image type 1 = ico 2 = cur; 2 bytes
ms.Write(BitConverter.GetBytes((Int16)1), 0, 2); //number of images; 2 bytes
}
{
//ICONDIRENTRY structure
ms.WriteByte(32); //image width in pixels
ms.WriteByte(32); //image height in pixels
ms.WriteByte(0); //Number of Colors in the color palette. Should be 0 if the image doesn't use a color palette
ms.WriteByte(0); //reserved must be 0
ms.Write(BitConverter.GetBytes((Int16)(rx / 2.0)), 0, 2); //2 bytes. In CUR format: Specifies the horizontal coordinates of the hotspot in number of pixels from the left.
ms.Write(BitConverter.GetBytes((Int16)(ry / 2.0)), 0, 2); //2 bytes. In CUR format: Specifies the vertical coordinates of the hotspot in number of pixels from the top.
ms.Write(BitConverter.GetBytes(size), 0, 4); //Specifies the size of the image's data in bytes
ms.Write(BitConverter.GetBytes((Int32)22), 0, 4); //Specifies the offset of BMP or PNG data from the beginning of the ICO/CUR file
}
ms.Write(pngBytes, 0, size); //write the png data.
ms.Seek(0, SeekOrigin.Begin);
return new Cursor(ms);
}
}
}
}
}

View File

@@ -0,0 +1,12 @@
using System.Runtime.InteropServices;
using System.Windows.Markup;
[assembly: XmlnsPrefix("urn:gong-wpf-dragdrop", "dd")]
[assembly: XmlnsDefinition("urn:gong-wpf-dragdrop", "GongSolutions.Wpf.DragDrop")]
[assembly: XmlnsDefinition("urn:gong-wpf-dragdrop", "GongSolutions.Wpf.DragDrop.Utilities")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("940084f7-d48e-41b3-9e0d-cf574d587643")]

View File

@@ -0,0 +1,13 @@
namespace GongSolutions.Wpf.DragDrop
{
/// <summary>
/// Specifies how <see cref="T:System.Windows.Controls.ScrollViewer" /> reacts to drop operation.
/// </summary>
public enum ScrollingMode
{
None,
HorizontalOnly,
VerticalOnly,
Both
}
}

View File

@@ -0,0 +1,96 @@
namespace GongSolutions.Wpf.DragDrop.Utilities
{
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Windows;
using System.Windows.Media;
/// <summary>
/// A helper class for Dpi logicm cause Microsoft hides this with the internal flag.
/// </summary>
public static class DpiHelper
{
private static Matrix _transformToDevice;
private static Matrix _transformToLogical;
public static double DpiX = 0d;
public static double DpiY = 0d;
[SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
static DpiHelper()
{
var dpiXProperty = typeof(SystemParameters).GetProperty("DpiX", BindingFlags.NonPublic | BindingFlags.Static);
var dpiYProperty = typeof(SystemParameters).GetProperty("Dpi", BindingFlags.NonPublic | BindingFlags.Static);
var pixelsPerInchX = (int)dpiXProperty.GetValue(null, null); //SystemParameters.DpiX;
DpiX = (double)pixelsPerInchX;
var pixelsPerInchY = (int)dpiYProperty.GetValue(null, null); //SystemParameters.Dpi;
DpiY = (double)pixelsPerInchY;
_transformToLogical = Matrix.Identity;
_transformToLogical.Scale(96d / (double)pixelsPerInchX, 96d / (double)pixelsPerInchY);
_transformToDevice = Matrix.Identity;
_transformToDevice.Scale((double)pixelsPerInchX / 96d, (double)pixelsPerInchY / 96d);
}
/// <summary>
/// Convert a point in device independent pixels (1/96") to a point in the system coordinates.
/// </summary>
/// <param name="logicalPoint">A point in the logical coordinate system.</param>
/// <returns>Returns the point converted to the system's coordinates.</returns>
public static Point LogicalPixelsToDevice(Point logicalPoint)
{
return _transformToDevice.Transform(logicalPoint);
}
/// <summary>
/// Convert a point in system coordinates to a point in device independent pixels (1/96").
/// </summary>
/// <param name="devicePoint">A point in the physical coordinate system.</param>
/// <returns>Returns the point converted to the device independent coordinate system.</returns>
public static Point DevicePixelsToLogical(Point devicePoint)
{
return _transformToLogical.Transform(devicePoint);
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
public static Rect LogicalRectToDevice(Rect logicalRectangle)
{
Point topLeft = LogicalPixelsToDevice(new Point(logicalRectangle.Left, logicalRectangle.Top));
Point bottomRight = LogicalPixelsToDevice(new Point(logicalRectangle.Right, logicalRectangle.Bottom));
return new Rect(topLeft, bottomRight);
}
public static Rect DeviceRectToLogical(Rect deviceRectangle)
{
Point topLeft = DevicePixelsToLogical(new Point(deviceRectangle.Left, deviceRectangle.Top));
Point bottomRight = DevicePixelsToLogical(new Point(deviceRectangle.Right, deviceRectangle.Bottom));
return new Rect(topLeft, bottomRight);
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
public static Size LogicalSizeToDevice(Size logicalSize)
{
Point pt = LogicalPixelsToDevice(new Point(logicalSize.Width, logicalSize.Height));
return new Size { Width = pt.X, Height = pt.Y };
}
public static Size DeviceSizeToLogical(Size deviceSize)
{
Point pt = DevicePixelsToLogical(new Point(deviceSize.Width, deviceSize.Height));
return new Size(pt.X, pt.Y);
}
public static Thickness LogicalThicknessToDevice(Thickness logicalThickness)
{
Point topLeft = LogicalPixelsToDevice(new Point(logicalThickness.Left, logicalThickness.Top));
Point bottomRight = LogicalPixelsToDevice(new Point(logicalThickness.Right, logicalThickness.Bottom));
return new Thickness(topLeft.X, topLeft.Y, bottomRight.X, bottomRight.Y);
}
}
}

View File

@@ -0,0 +1,127 @@
using System.Windows;
using System.Windows.Media;
namespace GongSolutions.Wpf.DragDrop.Utilities
{
using System;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
public static class DragDropExtensions
{
/// <summary>
/// Determines whether the given element is ignored on drag start (<see cref="DragDrop.DragSourceIgnoreProperty"/>).
/// </summary>
/// <param name="element">The given element.</param>
/// <returns>Element is ignored or not.</returns>
public static bool IsDragSourceIgnored(this UIElement element)
{
return element != null && DragDrop.GetDragSourceIgnore(element);
}
/// <summary>
/// Determines whether the given element is ignored on drop action (<see cref="DragDrop.IsDragSourceProperty"/>).
/// </summary>
/// <param name="element">The given element.</param>
/// <returns>Element is ignored or not.</returns>
public static bool IsDragSource(this UIElement element)
{
return element != null && DragDrop.GetIsDragSource(element);
}
/// <summary>
/// Determines whether the given element is ignored on drop action (<see cref="DragDrop.IsDropTargetProperty"/>).
/// </summary>
/// <param name="element">The given element.</param>
/// <returns>Element is ignored or not.</returns>
public static bool IsDropTarget(this UIElement element)
{
return element != null && DragDrop.GetIsDropTarget(element);
}
/// <summary>
/// Gets if drop position is directly over element
/// </summary>
/// <param name="dropPosition">Drop position</param>
/// <param name="element">element to check whether or not the drop position is directly over or not</param>
/// <param name="relativeToElement">element to which the drop position is related</param>
/// <returns>drop position is directly over element or not</returns>
public static bool DirectlyOverElement(this Point dropPosition, UIElement element, UIElement relativeToElement)
{
if (element == null)
return false;
var relativeItemPosition = element.TranslatePoint(new Point(0, 0), relativeToElement);
var relativeDropPosition = new Point(dropPosition.X - relativeItemPosition.X, dropPosition.Y - relativeItemPosition.Y);
return VisualTreeHelper.GetDescendantBounds(element).Contains(relativeDropPosition);
}
/// <summary>
/// Capture screen and create data template containing the captured image
/// </summary>
/// <param name="element">visual source to capture screen of</param>
/// <param name="visualSourceFlowDirection">Flowdirection of visual source</param>
/// <returns></returns>
public static DataTemplate GetCaptureScreenDataTemplate(this UIElement element, FlowDirection visualSourceFlowDirection)
{
DataTemplate template = null;
var bs = CaptureScreen(element, visualSourceFlowDirection);
if (bs != null)
{
var factory = new FrameworkElementFactory(typeof(Image));
factory.SetValue(Image.SourceProperty, bs);
factory.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
factory.SetValue(RenderOptions.BitmapScalingModeProperty, BitmapScalingMode.HighQuality);
factory.SetValue(FrameworkElement.WidthProperty, bs.Width);
factory.SetValue(FrameworkElement.HeightProperty, bs.Height);
factory.SetValue(FrameworkElement.HorizontalAlignmentProperty, HorizontalAlignment.Left);
factory.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Top);
template = new DataTemplate { VisualTree = factory };
}
return template;
}
// Helper to generate the image - I grabbed this off Google
// somewhere. -- Chris Bordeman cbordeman@gmail.com
private static BitmapSource CaptureScreen(Visual target, FlowDirection flowDirection)
{
if (target == null)
{
return null;
}
var dpiX = DpiHelper.DpiX;
var dpiY = DpiHelper.DpiY;
var bounds = VisualTreeHelper.GetDescendantBounds(target);
var dpiBounds = DpiHelper.LogicalRectToDevice(bounds);
var pixelWidth = (int)Math.Ceiling(dpiBounds.Width);
var pixelHeight = (int)Math.Ceiling(dpiBounds.Height);
if (pixelWidth < 0 || pixelHeight < 0)
{
return null;
}
var rtb = new RenderTargetBitmap(pixelWidth, pixelHeight, dpiX, dpiY, PixelFormats.Pbgra32);
var dv = new DrawingVisual();
using (var ctx = dv.RenderOpen())
{
var vb = new VisualBrush(target);
if (flowDirection == FlowDirection.RightToLeft)
{
var transformGroup = new TransformGroup();
transformGroup.Children.Add(new ScaleTransform(-1, 1));
transformGroup.Children.Add(new TranslateTransform(bounds.Size.Width - 1, 0));
ctx.PushTransform(transformGroup);
}
ctx.DrawRectangle(vb, null, new Rect(new Point(), bounds.Size));
}
rtb.Render(dv);
return rtb;
}
}
}

View File

@@ -0,0 +1,135 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Controls.Primitives;
namespace GongSolutions.Wpf.DragDrop.Utilities
{
public static class HitTestUtilities
{
public static bool HitTest4Type<T>(object sender, Point elementPosition)
where T : UIElement
{
var uiElement = GetHitTestElement4Type<T>(sender, elementPosition);
return uiElement != null && uiElement.Visibility == Visibility.Visible;
}
private static T GetHitTestElement4Type<T>(object sender, Point elementPosition)
where T : UIElement
{
var visual = sender as Visual;
if (visual == null)
{
return null;
}
var hit = VisualTreeHelper.HitTest(visual, elementPosition);
if (hit == null)
{
return null;
}
var uiElement = hit.VisualHit.GetVisualAncestor<T>();
return uiElement;
}
public static bool HitTest4GridViewColumnHeader(object sender, Point elementPosition)
{
if (sender is ListView)
{
// no drag&drop for column header
var columnHeader = GetHitTestElement4Type<GridViewColumnHeader>(sender, elementPosition);
if (columnHeader != null && (columnHeader.Role == GridViewColumnHeaderRole.Floating || columnHeader.Visibility == Visibility.Visible))
{
return true;
}
}
return false;
}
public static bool HitTest4DataGridTypes(object sender, Point elementPosition)
{
if (sender is DataGrid)
{
// no drag&drop for column header
var columnHeader = GetHitTestElement4Type<DataGridColumnHeader>(sender, elementPosition);
if (columnHeader != null && columnHeader.Visibility == Visibility.Visible)
{
return true;
}
// no drag&drop for row header
var rowHeader = GetHitTestElement4Type<DataGridRowHeader>(sender, elementPosition);
if (rowHeader != null && rowHeader.Visibility == Visibility.Visible)
{
// no drag&drop for row header gripper
var thumb = GetHitTestElement4Type<Thumb>(sender, elementPosition);
if (thumb != null)
{
return true;
}
}
// drag&drop only for data grid row
var dataRow = GetHitTestElement4Type<DataGridRow>(sender, elementPosition);
return dataRow == null || Equals(dataRow.DataContext, CollectionView.NewItemPlaceholder);
}
return false;
}
public static bool HitTest4DataGridTypesOnDragOver(object sender, Point elementPosition)
{
if (sender is DataGrid)
{
// no drag&drop on column header
var columnHeader = GetHitTestElement4Type<DataGridColumnHeader>(sender, elementPosition);
if (columnHeader != null && columnHeader.Visibility == Visibility.Visible)
{
return true;
}
}
return false;
}
/// <summary>
/// thx to @osicka from issue #84
///
/// e.g. original source is part of a popup (e.g. ComboBox drop down), the hit test needs to be done on the original source.
/// Because the popup is not attached to the visual tree of the sender.
/// This function test this by looping back from the original source to the sender and if it didn't end up at the sender stopped the drag.
/// </summary>
public static bool IsNotPartOfSender(object sender, MouseButtonEventArgs e)
{
var visual = e.OriginalSource as Visual;
if (visual == null)
{
return false;
}
var hit = VisualTreeHelper.HitTest(visual, e.GetPosition((IInputElement)visual));
if (hit == null)
{
return false;
}
else
{
var depObj = e.OriginalSource as DependencyObject;
if (depObj == null)
{
return false;
}
if (depObj == sender)
{
return false;
}
var item = VisualTreeHelper.GetParent(depObj.FindVisualTreeRoot());
//var item = VisualTreeHelper.GetParent(e.OriginalSource as DependencyObject);
while (item != null && item != sender)
{
item = VisualTreeHelper.GetParent(item);
}
return item != sender;
}
}
}
}

View File

@@ -0,0 +1,622 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Reflection;
using System.Collections;
using System.Windows.Controls.Primitives;
namespace GongSolutions.Wpf.DragDrop.Utilities
{
public static class ItemsControlExtensions
{
public static CollectionViewGroup FindGroup(this ItemsControl itemsControl, Point position)
{
if (itemsControl.Items.Groups == null || itemsControl.Items.Groups.Count == 0)
{
return null;
}
var element = itemsControl.InputHitTest(position) as DependencyObject;
if (element != null)
{
var groupItem = element.GetVisualAncestor<GroupItem>();
// drag after last item - get group of it
if (groupItem == null && itemsControl.Items.Count > 0)
{
var lastItem = itemsControl.ItemContainerGenerator.ContainerFromItem(itemsControl.Items.GetItemAt(itemsControl.Items.Count - 1)) as FrameworkElement;
if (lastItem != null)
{
var itemEndpoint = lastItem.PointToScreen(new Point(lastItem.ActualWidth, lastItem.ActualHeight));
var positionToScreen = itemsControl.PointToScreen(position);
switch (itemsControl.GetItemsPanelOrientation())
{
case Orientation.Horizontal:
// assume LeftToRight
groupItem = itemEndpoint.X <= positionToScreen.X ? lastItem.GetVisualAncestor<GroupItem>() : null;
break;
case Orientation.Vertical:
groupItem = itemEndpoint.Y <= positionToScreen.Y ? lastItem.GetVisualAncestor<GroupItem>() : null;
break;
}
}
}
if (groupItem != null)
{
return groupItem.Content as CollectionViewGroup;
}
}
return null;
}
public static bool CanSelectMultipleItems(this ItemsControl itemsControl)
{
if (itemsControl is MultiSelector multiSelector)
{
// The CanSelectMultipleItems property is protected. Use reflection to
// get its value anyway.
var propertyInfo = multiSelector.GetType().GetProperty("CanSelectMultipleItems", BindingFlags.Instance | BindingFlags.NonPublic);
return propertyInfo != null && (bool)propertyInfo.GetValue(multiSelector, null);
}
else if (itemsControl is ListBox listBox)
{
return listBox.SelectionMode != SelectionMode.Single;
}
else
{
return false;
}
}
public static UIElement GetItemContainer(this ItemsControl itemsControl, DependencyObject child)
{
bool isItemContainer;
var itemType = GetItemContainerType(itemsControl, out isItemContainer);
if (itemType != null)
{
return isItemContainer
? (UIElement)child.GetVisualAncestor(itemType, itemsControl)
: (UIElement)child.GetVisualAncestor(itemType, itemsControl, itemsControl.GetType());
}
return null;
}
public static UIElement GetItemContainerAt(this ItemsControl itemsControl, Point position)
{
var inputElement = itemsControl.InputHitTest(position);
var uiElement = inputElement as UIElement;
if (uiElement != null)
{
return GetItemContainer(itemsControl, uiElement);
}
// ContentElement's such as Run's within TextBlock's could not be used as drop target items, because they are not UIElement's.
var contentElement = inputElement as ContentElement;
if (contentElement != null)
{
return GetItemContainer(itemsControl, contentElement);
}
return null;
}
public static UIElement GetItemContainerAt(this ItemsControl itemsControl, Point position, Orientation searchDirection)
{
bool isItemContainer;
var itemContainerType = GetItemContainerType(itemsControl, out isItemContainer);
Geometry hitTestGeometry;
if (typeof(TreeViewItem).IsAssignableFrom(itemContainerType))
{
hitTestGeometry = new LineGeometry(new Point(0, position.Y), new Point(itemsControl.RenderSize.Width, position.Y));
}
else
{
var geometryGroup = new GeometryGroup();
geometryGroup.Children.Add(new LineGeometry(new Point(0, position.Y), new Point(itemsControl.RenderSize.Width, position.Y)));
geometryGroup.Children.Add(new LineGeometry(new Point(position.X, 0), new Point(position.X, itemsControl.RenderSize.Height)));
hitTestGeometry = geometryGroup;
}
var hits = new HashSet<DependencyObject>();
VisualTreeHelper.HitTest(itemsControl,
obj =>
{
// Viewport3D is not good for us
// Stop on ScrollBar to improve performance (e.g. at DataGrid)
if (obj is Viewport3D || (itemsControl is DataGrid && obj is ScrollBar))
{
return HitTestFilterBehavior.Stop;
}
return HitTestFilterBehavior.Continue;
},
result =>
{
var itemContainer = isItemContainer
? result.VisualHit.GetVisualAncestor(itemContainerType, itemsControl)
: result.VisualHit.GetVisualAncestor(itemContainerType, itemsControl, itemsControl.GetType());
if (itemContainer != null && ((UIElement)itemContainer).IsVisible == true)
{
var tvItem = itemContainer as TreeViewItem;
if (tvItem != null)
{
var tv = tvItem.GetVisualAncestor<TreeView>();
if (tv == itemsControl)
{
hits.Add(itemContainer);
}
}
else
{
if (itemsControl.ItemContainerGenerator.IndexFromContainer(itemContainer) >= 0)
{
hits.Add(itemContainer);
}
}
}
return HitTestResultBehavior.Continue;
},
new GeometryHitTestParameters(hitTestGeometry));
return GetClosest(itemsControl, hits, position, searchDirection);
}
public static Type GetItemContainerType(this ItemsControl itemsControl, out bool isItemContainer)
{
// determines if the itemsControl is not a ListView, ListBox or TreeView
isItemContainer = false;
if (typeof(TabControl).IsAssignableFrom(itemsControl.GetType()))
{
return typeof(TabItem);
}
if (typeof(DataGrid).IsAssignableFrom(itemsControl.GetType()))
{
return typeof(DataGridRow);
}
// There is no safe way to get the item container type for an ItemsControl.
// First hard-code the types for the common ItemsControls.
//if (itemsControl.GetType().IsAssignableFrom(typeof(ListView)))
if (typeof(ListView).IsAssignableFrom(itemsControl.GetType()))
{
return typeof(ListViewItem);
}
//if (itemsControl.GetType().IsAssignableFrom(typeof(ListBox)))
else if (typeof(ListBox).IsAssignableFrom(itemsControl.GetType()))
{
return typeof(ListBoxItem);
}
//else if (itemsControl.GetType().IsAssignableFrom(typeof(TreeView)))
else if (typeof(TreeView).IsAssignableFrom(itemsControl.GetType()))
{
return typeof(TreeViewItem);
}
// Otherwise look for the control's ItemsPresenter, get it's child panel and the first
// child of that *should* be an item container.
//
// If the control currently has no items, we're out of luck.
if (itemsControl.Items.Count > 0)
{
var itemsPresenters = itemsControl.GetVisualDescendents<ItemsPresenter>();
foreach (var itemsPresenter in itemsPresenters)
{
if (VisualTreeHelper.GetChildrenCount(itemsPresenter) > 0)
{
var panel = VisualTreeHelper.GetChild(itemsPresenter, 0);
var itemContainer = VisualTreeHelper.GetChildrenCount(panel) > 0
? VisualTreeHelper.GetChild(panel, 0)
: null;
// Ensure that this actually *is* an item container by checking it with
// ItemContainerGenerator.
if (itemContainer != null &&
!(itemContainer is GroupItem) &&
itemsControl.ItemContainerGenerator.IndexFromContainer(itemContainer) != -1)
{
isItemContainer = true;
return itemContainer.GetType();
}
}
}
}
return null;
}
/// <summary>
/// Gets the Orientation which will be used for the drag drop action.
/// Normally it will be look up to find the correct orientaion of the inner ItemsPanel,
/// but sometimes it's necessary to force the oreintation, if the look up is wrong.
/// If so, the ItemsPanelOrientation value is taken.
/// </summary>
/// <param name="itemsControl">The ItemsControl for the look up.</param>
/// <returns>Orientation for the given ItemsControl.</returns>
public static Orientation GetItemsPanelOrientation(this ItemsControl itemsControl)
{
var itemsPanelOrientation = DragDrop.GetItemsPanelOrientation(itemsControl);
if (itemsPanelOrientation.HasValue)
{
return itemsPanelOrientation.Value;
}
if (itemsControl is TabControl)
{
//HitTestUtilities.HitTest4Type<TabPanel>(sender, elementPosition)
//var tabPanel = itemsControl.GetVisualDescendent<TabPanel>();
var tabControl = (TabControl)itemsControl;
return tabControl.TabStripPlacement == Dock.Left || tabControl.TabStripPlacement == Dock.Right ? Orientation.Vertical : Orientation.Horizontal;
}
var itemsPresenter = itemsControl.GetVisualDescendent<ItemsPresenter>() ?? itemsControl.GetVisualDescendent<ScrollContentPresenter>() as UIElement;
if (itemsPresenter != null && VisualTreeHelper.GetChildrenCount(itemsPresenter) > 0)
{
var itemsPanel = VisualTreeHelper.GetChild(itemsPresenter, 0);
var orientationProperty = itemsPanel.GetType().GetProperty("Orientation", typeof(Orientation));
if (orientationProperty != null)
{
return (Orientation)orientationProperty.GetValue(itemsPanel, null);
}
}
// Make a guess!
return Orientation.Vertical;
}
/// <summary>
/// Gets the FlowDirection which will be used for the drag drop action.
/// </summary>
/// <param name="itemsControl">The ItemsControl for the look up.</param>
/// <returns>FlowDirection for the given ItemsControl.</returns>
public static FlowDirection GetItemsPanelFlowDirection(this ItemsControl itemsControl)
{
var itemsPresenter = itemsControl.GetVisualDescendent<ItemsPresenter>() ?? itemsControl.GetVisualDescendent<ScrollContentPresenter>() as UIElement;
if (itemsPresenter != null && VisualTreeHelper.GetChildrenCount(itemsPresenter) > 0)
{
var itemsPanel = VisualTreeHelper.GetChild(itemsPresenter, 0);
var flowDirectionProperty = itemsPanel.GetType().GetProperty("FlowDirection", typeof(FlowDirection));
if (flowDirectionProperty != null)
{
return (FlowDirection)flowDirectionProperty.GetValue(itemsPanel, null);
}
}
// Make a guess!
return FlowDirection.LeftToRight;
}
/// <summary>
/// Sets the given object as selected item at the ItemsControl.
/// </summary>
/// <param name="itemsControl">The ItemsControl which contains the item.</param>
/// <param name="item">The object which should be selected.</param>
public static void SetSelectedItem(this ItemsControl itemsControl, object item)
{
if (itemsControl is MultiSelector)
{
((MultiSelector)itemsControl).SetCurrentValue(Selector.SelectedItemProperty, null);
((MultiSelector)itemsControl).SetCurrentValue(Selector.SelectedItemProperty, item);
}
else if (itemsControl is ListBox)
{
var selectionMode = ((ListBox)itemsControl).SelectionMode;
try
{
// change SelectionMode for UpdateAnchorAndActionItem
((ListBox)itemsControl).SetCurrentValue(ListBox.SelectionModeProperty, SelectionMode.Single);
((ListBox)itemsControl).SetCurrentValue(Selector.SelectedItemProperty, null);
((ListBox)itemsControl).SetCurrentValue(Selector.SelectedItemProperty, item);
}
finally
{
((ListBox)itemsControl).SetCurrentValue(ListBox.SelectionModeProperty, selectionMode);
}
}
else if (itemsControl is TreeViewItem)
{
// clear old selected item
var treeView = ItemsControl.ItemsControlFromItemContainer((TreeViewItem)itemsControl);
if (treeView != null)
{
var prevSelectedItem = treeView.GetValue(TreeView.SelectedItemProperty);
if (prevSelectedItem != null)
{
var prevSelectedTreeViewItem = treeView.ItemContainerGenerator.ContainerFromItem(prevSelectedItem) as TreeViewItem;
if (prevSelectedTreeViewItem != null)
{
prevSelectedTreeViewItem.SetCurrentValue(TreeViewItem.IsSelectedProperty, false);
}
}
}
// set new selected item
// TreeView.SelectedItemProperty is a read only property, so we must set the selection on the TreeViewItem itself
var treeViewItem = ((TreeViewItem)itemsControl).ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
if (treeViewItem != null)
{
treeViewItem.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
}
}
else if (itemsControl is TreeView)
{
// clear old selected item
var prevSelectedItem = ((TreeView)itemsControl).GetValue(TreeView.SelectedItemProperty);
if (prevSelectedItem != null)
{
var prevSelectedTreeViewItem = ((TreeView)itemsControl).ItemContainerGenerator.ContainerFromItem(prevSelectedItem) as TreeViewItem;
if (prevSelectedTreeViewItem != null)
{
prevSelectedTreeViewItem.SetCurrentValue(TreeViewItem.IsSelectedProperty, false);
}
}
// set new selected item
// TreeView.SelectedItemProperty is a read only property, so we must set the selection on the TreeViewItem itself
var treeViewItem = ((TreeView)itemsControl).ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
if (treeViewItem != null)
{
treeViewItem.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
}
}
else if (itemsControl is Selector)
{
((Selector)itemsControl).SetCurrentValue(Selector.SelectedItemProperty, null);
((Selector)itemsControl).SetCurrentValue(Selector.SelectedItemProperty, item);
}
}
/// <summary>
/// Clears the selected items.
/// </summary>
/// <param name="itemsControl">The items control.</param>
public static void ClearSelectedItems(this ItemsControl itemsControl)
{
if (itemsControl is MultiSelector)
{
if (((MultiSelector)itemsControl).CanSelectMultipleItems())
{
((MultiSelector)itemsControl).SelectedItems.Clear();
}
((MultiSelector)itemsControl).SetCurrentValue(Selector.SelectedItemProperty, null);
}
else if (itemsControl is ListBox)
{
if (((ListBox)itemsControl).CanSelectMultipleItems())
{
((ListBox)itemsControl).SelectedItems.Clear();
((ListBox)itemsControl).SetCurrentValue(Selector.SelectedItemProperty, null);
}
}
else if (itemsControl is TreeViewItem)
{
var treeView = ItemsControl.ItemsControlFromItemContainer((TreeViewItem)itemsControl);
treeView?.ClearSelectedItems();
}
else if (itemsControl is TreeView)
{
// clear old selected item
var prevSelectedItem = ((TreeView)itemsControl).GetValue(TreeView.SelectedItemProperty);
if (prevSelectedItem != null)
{
var prevSelectedTreeViewItem = ((TreeView)itemsControl).ItemContainerGenerator.ContainerFromItem(prevSelectedItem) as TreeViewItem;
if (prevSelectedTreeViewItem != null)
{
prevSelectedTreeViewItem.SetCurrentValue(TreeViewItem.IsSelectedProperty, false);
}
}
}
else if (itemsControl is Selector)
{
((Selector)itemsControl).SetCurrentValue(Selector.SelectedItemProperty, null);
}
}
public static object GetSelectedItem(this ItemsControl itemsControl)
{
if (itemsControl is MultiSelector)
{
return ((MultiSelector)itemsControl).SelectedItem;
}
else if (itemsControl is ListBox)
{
return ((ListBox)itemsControl).SelectedItem;
}
else if (itemsControl is TreeView)
{
return ((TreeView)itemsControl).GetValue(TreeView.SelectedItemProperty);
}
else if (itemsControl is Selector)
{
return ((Selector)itemsControl).SelectedItem;
}
return null;
}
public static IEnumerable GetSelectedItems(this ItemsControl itemsControl)
{
//if (itemsControl.GetType().IsAssignableFrom(typeof(MultiSelector)))
if (typeof(MultiSelector).IsAssignableFrom(itemsControl.GetType()))
{
return ((MultiSelector)itemsControl).SelectedItems;
}
else if (itemsControl is ListBox)
{
var listBox = (ListBox)itemsControl;
if (listBox.SelectionMode == SelectionMode.Single)
{
return Enumerable.Repeat(listBox.SelectedItem, 1);
}
else
{
return listBox.SelectedItems;
}
}
//else if (itemsControl.GetType().IsAssignableFrom(typeof(TreeView)))
else if (typeof(TreeView).IsAssignableFrom(itemsControl.GetType()))
{
return Enumerable.Repeat(((TreeView)itemsControl).SelectedItem, 1);
}
//else if (itemsControl.GetType().IsAssignableFrom(typeof(Selector)))
else if (typeof(Selector).IsAssignableFrom(itemsControl.GetType()))
{
return Enumerable.Repeat(((Selector)itemsControl).SelectedItem, 1);
}
else
{
return Enumerable.Empty<object>();
}
}
public static bool GetItemSelected(this ItemsControl itemsControl, object item)
{
if (itemsControl is MultiSelector)
{
return ((MultiSelector)itemsControl).SelectedItems.Contains(item);
}
else if (itemsControl is ListBox)
{
return ((ListBox)itemsControl).SelectedItems.Contains(item);
}
else if (itemsControl is TreeView)
{
return ((TreeView)itemsControl).SelectedItem == item;
}
else if (itemsControl is Selector)
{
return ((Selector)itemsControl).SelectedItem == item;
}
else
{
return false;
}
}
public static void SetItemSelected(this ItemsControl itemsControl, object item, bool itemSelected)
{
if (itemsControl is MultiSelector)
{
var multiSelector = (MultiSelector)itemsControl;
if (multiSelector.CanSelectMultipleItems())
{
if (itemSelected)
{
multiSelector.SelectedItems.Add(item);
}
else
{
multiSelector.SelectedItems.Remove(item);
}
}
else
{
multiSelector.SetCurrentValue(Selector.SelectedItemProperty, null);
if (itemSelected)
{
multiSelector.SetCurrentValue(Selector.SelectedItemProperty, item);
}
}
}
else if (itemsControl is ListBox)
{
var listBox = (ListBox)itemsControl;
if (listBox.SelectionMode != SelectionMode.Single)
{
if (itemSelected)
{
listBox.SelectedItems.Add(item);
}
else
{
listBox.SelectedItems.Remove(item);
}
}
else
{
listBox.SetCurrentValue(Selector.SelectedItemProperty, null);
if (itemSelected)
{
listBox.SetCurrentValue(Selector.SelectedItemProperty, item);
}
}
}
else
{
if (itemSelected)
{
itemsControl.SetSelectedItem(item);
}
else
{
itemsControl.SetSelectedItem(null);
}
}
}
private static UIElement GetClosest(ItemsControl itemsControl, IEnumerable<DependencyObject> items,
Point position, Orientation searchDirection)
{
//Console.WriteLine("GetClosest - {0}", itemsControl.ToString());
UIElement closest = null;
var closestDistance = double.MaxValue;
foreach (var i in items)
{
var uiElement = i as UIElement;
if (uiElement != null)
{
var p = uiElement.TransformToAncestor(itemsControl).Transform(new Point(0, 0));
var distance = double.MaxValue;
if (itemsControl is TreeView)
{
var xDiff = position.X - p.X;
var yDiff = position.Y - p.Y;
var hyp = Math.Sqrt(Math.Pow(xDiff, 2d) + Math.Pow(yDiff, 2d));
distance = Math.Abs(hyp);
}
else
{
var itemParent = ItemsControl.ItemsControlFromItemContainer(uiElement);
if (itemParent != null && itemParent != itemsControl)
{
searchDirection = itemParent.GetItemsPanelOrientation();
}
switch (searchDirection)
{
case Orientation.Horizontal:
distance = position.X <= p.X ? p.X - position.X : position.X - uiElement.RenderSize.Width - p.X;
break;
case Orientation.Vertical:
distance = position.Y <= p.Y ? p.Y - position.Y : position.Y - uiElement.RenderSize.Height - p.Y;
break;
}
}
if (distance < closestDistance)
{
closest = uiElement;
closestDistance = distance;
}
}
}
return closest;
}
}
}

View File

@@ -0,0 +1,31 @@
using System.Windows;
using System.Windows.Controls;
namespace GongSolutions.Wpf.DragDrop.Utilities
{
public static class RootElementFinder
{
public static UIElement FindRoot(DependencyObject visual)
{
var parentWindow = Window.GetWindow(visual);
var rootElement = parentWindow != null ? parentWindow.Content as UIElement : null;
if (rootElement == null)
{
if (Application.Current != null && Application.Current.MainWindow != null)
{
rootElement = Application.Current.MainWindow.Content as UIElement;
}
if (rootElement == null)
{
rootElement = visual.GetVisualAncestor<Page>() ?? visual.GetVisualAncestor<UserControl>() as UIElement;
}
}
// i don't want the fu... windows forms reference
// if (rootElement == null) {
// var elementHost = m_DragInfo.VisualSource.GetVisualAncestor<ElementHost>();
// rootElement = elementHost != null ? elementHost.Child : null;
// }
return rootElement;
}
}
}

View File

@@ -0,0 +1,36 @@
using System.Windows;
using System.Windows.Controls;
namespace GongSolutions.Wpf.DragDrop.Utilities
{
/// <summary>
/// Extension methods for TreeViewItem
/// </summary>
public static class TreeViewItemExtensions
{
/// <summary>
/// Try get the height of the header part for the given TreeViewItem.
/// If there is no PART_Header it will return Size.Empty.
/// </summary>
/// <param name="item">The TreeViewItem.</param>
public static Size GetHeaderSize(this TreeViewItem item)
{
if (item == null)
{
return Size.Empty;
}
var header = GetHeaderControl(item);
return header != null ? new Size(header.ActualWidth, header.ActualHeight) : item.RenderSize;
}
/// <summary>
/// Try get the header part of the given TreeViewItem.
/// If there is no PART_Header it will return null.
/// </summary>
/// <param name="item">The TreeViewItem.</param>
public static FrameworkElement GetHeaderControl(this TreeViewItem item)
{
return item?.Template?.FindName("PART_Header", item) as FrameworkElement;
}
}
}

View File

@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Collections;
namespace GongSolutions.Wpf.DragDrop.Utilities
{
public static class TypeUtilities
{
public static IEnumerable CreateDynamicallyTypedList(IEnumerable source)
{
var type = GetCommonBaseClass(source);
var listType = typeof(List<>).MakeGenericType(type);
var addMethod = listType.GetMethod("Add");
var list = listType.GetConstructor(Type.EmptyTypes).Invoke(null);
foreach (var o in source)
{
addMethod.Invoke(list, new[] { o });
}
return (IEnumerable)list;
}
public static Type GetCommonBaseClass(IEnumerable e)
{
var types = e.Cast<object>().Select(o => o.GetType()).ToArray<Type>();
return GetCommonBaseClass(types);
}
public static Type GetCommonBaseClass(Type[] types)
{
if (types.Length == 0)
{
return typeof(object);
}
var ret = types[0];
for (var i = 1; i < types.Length; ++i)
{
if (types[i].IsAssignableFrom(ret))
{
ret = types[i];
}
else
{
// This will always terminate when ret == typeof(object)
while (!ret.IsAssignableFrom(types[i]))
{
ret = ret.BaseType;
}
}
}
return ret;
}
/// <summary>
/// Gets the enumerable as list.
/// If enumerable is an ICollectionView then it returns the SourceCollection as list.
/// </summary>
/// <param name="enumerable">The enumerable.</param>
/// <returns>Returns a list.</returns>
public static IList TryGetList(this IEnumerable enumerable)
{
if (enumerable is ICollectionView)
{
return ((ICollectionView)enumerable).SourceCollection as IList;
}
else
{
var list = enumerable as IList;
return list ?? (enumerable != null ? enumerable.OfType<object>().ToList() : null);
}
}
}
}

View File

@@ -0,0 +1,169 @@
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Collections.Generic;
using System.Windows.Media.Media3D;
using JetBrains.Annotations;
namespace GongSolutions.Wpf.DragDrop.Utilities
{
public static class VisualTreeExtensions
{
/// <summary>
/// Gets the next ancestor element which is a drop target.
/// </summary>
/// <param name="element">The start element.</param>
/// <returns>The first element which is a drop target.</returns>
public static UIElement TryGetNextAncestorDropTargetElement(this UIElement element)
{
if (element == null)
{
return null;
}
var ancestor = element.GetVisualAncestor<UIElement>();
while (ancestor != null)
{
if (ancestor.IsDropTarget())
{
return ancestor;
}
ancestor = ancestor.GetVisualAncestor<UIElement>();
}
return null;
}
internal static DependencyObject FindVisualTreeRoot(this DependencyObject d)
{
var current = d;
var result = d;
while (current != null)
{
result = current;
if (current is Visual || current is Visual3D)
{
break;
}
else
{
// If we're in Logical Land then we must walk
// up the logical tree until we find a
// Visual/Visual3D to get us back to Visual Land.
current = LogicalTreeHelper.GetParent(current);
}
}
return result;
}
public static T GetVisualAncestor<T>(this DependencyObject d)
where T : class
{
var item = VisualTreeHelper.GetParent(d.FindVisualTreeRoot());
while (item != null)
{
var itemAsT = item as T;
if (itemAsT != null)
{
return itemAsT;
}
item = VisualTreeHelper.GetParent(item);
}
return null;
}
/// <summary>
/// find the visual ancestor item by type
/// </summary>
public static DependencyObject GetVisualAncestor(this DependencyObject d, Type itemSearchType, [NotNull] ItemsControl itemsControl, [NotNull] Type itemContainerSearchType)
{
if (itemsControl == null) throw new ArgumentNullException(nameof(itemsControl));
if (itemContainerSearchType == null) throw new ArgumentNullException(nameof(itemContainerSearchType));
var visualTreeRoot = d.FindVisualTreeRoot();
var currentVisual = VisualTreeHelper.GetParent(visualTreeRoot);
while (currentVisual != null && itemSearchType != null)
{
var currentVisualType = currentVisual.GetType();
if (currentVisualType == itemSearchType || currentVisualType.IsSubclassOf(itemSearchType))
{
if (currentVisual is TreeViewItem || itemsControl.ItemContainerGenerator.IndexFromContainer(currentVisual) != -1)
{
return currentVisual;
}
}
if (itemContainerSearchType.IsAssignableFrom(currentVisualType))
{
// ok, we found an ItemsControl (maybe an empty)
return null;
}
currentVisual = VisualTreeHelper.GetParent(currentVisual);
}
return null;
}
/// <summary>
/// find the visual ancestor by type and go through the visual tree until the given itemsControl will be found
/// </summary>
public static DependencyObject GetVisualAncestor(this DependencyObject d, Type itemSearchType, [NotNull] ItemsControl itemsControl)
{
if (itemsControl == null) throw new ArgumentNullException(nameof(itemsControl));
var visualTreeRoot = d.FindVisualTreeRoot();
var currentVisual = VisualTreeHelper.GetParent(visualTreeRoot);
DependencyObject lastFoundItemByType = null;
while (currentVisual != null && itemSearchType != null)
{
if (currentVisual == itemsControl)
{
return lastFoundItemByType;
}
var currentVisualType = currentVisual.GetType();
if ((currentVisualType == itemSearchType || currentVisualType.IsSubclassOf(itemSearchType))
&& (itemsControl.ItemContainerGenerator.IndexFromContainer(currentVisual) != -1))
{
lastFoundItemByType = currentVisual;
}
currentVisual = VisualTreeHelper.GetParent(currentVisual);
}
return lastFoundItemByType;
}
public static T GetVisualDescendent<T>(this DependencyObject d)
where T : DependencyObject
{
return d.GetVisualDescendents<T>().FirstOrDefault();
}
public static IEnumerable<T> GetVisualDescendents<T>(this DependencyObject d)
where T : DependencyObject
{
var childCount = VisualTreeHelper.GetChildrenCount(d);
for (var n = 0; n < childCount; n++)
{
var child = VisualTreeHelper.GetChild(d, n);
if (child is T)
{
yield return (T)child;
}
foreach (var match in GetVisualDescendents<T>(child))
{
yield return match;
}
}
yield break;
}
}
}