Files
aistudio-wpf-diagram/gong-wpf-dragdrop/src/GongSolutions.WPF.DragDrop/DropInfo.cs
2021-07-23 09:42:22 +08:00

453 lines
19 KiB
C#

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
}
}