using System.Collections; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; namespace Waaagh.Behaviors { sealed public class ItemsDraggable { #region Drag Drop Data Format String static readonly string DraggableDataFormat = "DraggableData"; static readonly string DraggableCollectionFormat = "DraggableCollection"; #endregion #region Enabled static public bool GetEnabled(DependencyObject obj) { return (bool)obj.GetValue(EnabledProperty); } static public void SetEnabled(DependencyObject obj, bool value) { obj.SetValue(EnabledProperty, value); } static public readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(ItemsDraggable), new PropertyMetadata(false, OnEnabledChanged)); static private void OnEnabledChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) { if (sender is ItemsControl itemsControl == false) { throw new InvalidOperationException("The attached control is not an 'ItemsControl'."); } itemsControl.AllowDrop = false; itemsControl.MouseMove -= ItemsDraggable_MouseMove; itemsControl.DragEnter -= ItemsDraggable_DragEnter; itemsControl.DragOver -= ItemsDraggable_DragOver; itemsControl.DragLeave -= ItemsDraggable_DragLeave; itemsControl.Drop -= ItemsDraggable_Drop; if (GetEnabled(itemsControl) == true) { itemsControl.AllowDrop = true; itemsControl.MouseMove += ItemsDraggable_MouseMove; itemsControl.DragEnter += ItemsDraggable_DragEnter; itemsControl.DragOver += ItemsDraggable_DragOver; itemsControl.DragLeave += ItemsDraggable_DragLeave; itemsControl.Drop += ItemsDraggable_Drop; } } public static Func GetQueryTargetCollection(DependencyObject obj) { return (Func)obj.GetValue(QueryTargetCollectionProperty); } public static void SetQueryTargetCollection(DependencyObject obj, Func value) { obj.SetValue(QueryTargetCollectionProperty, value); } public static readonly DependencyProperty QueryTargetCollectionProperty = DependencyProperty.RegisterAttached("QueryTargetCollection", typeof(Func), typeof(ItemsDraggable), new PropertyMetadata(null)); #endregion #region Drag Drop Event static private void ItemsDraggable_MouseMove(object sender, MouseEventArgs args) { if (args.LeftButton != MouseButtonState.Pressed) { return; } if (sender is ItemsControl source == false) { return; } if (source.ItemsSource is IList == false) { return; } if (GetElement(source, args.GetPosition(source)) is Control sourceControl == false) { return; } if (GetParent(sourceControl) is ItemsControl sourceItemsControl == false) { return; } DataObject data = new DataObject(); data.SetData(DraggableDataFormat, sourceControl.DataContext); data.SetData(DraggableCollectionFormat, sourceItemsControl.ItemsSource); DragDrop.DoDragDrop(sourceItemsControl, data, DragDropEffects.Move); } static private void ItemsDraggable_DragEnter(object sender, DragEventArgs args) { } static private void ItemsDraggable_DragOver(object sender, DragEventArgs args) { } static private void ItemsDraggable_DragLeave(object sender, DragEventArgs args) { } static private void ItemsDraggable_Drop(object sender, DragEventArgs args) { if (args.Effects == DragDropEffects.None) { return; } if (sender is ItemsControl target == false) { return; } // 获取 放置目标 if (GetElement(target, args.GetPosition(target)) is Control targetControl == false) { return; } if (GetParent(targetControl) is ItemsControl targetItemsControl == false) { return; } if (targetItemsControl.ItemsSource is IList targetCollection == false) { return; } // 获取 拖拽源 object? sourceData = args.Data.GetData(DraggableDataFormat) as object; IList? sourceCollection = args.Data.GetData(DraggableCollectionFormat) as IList; if (sourceData == null || sourceCollection == null) { return; } // 判断 集合元素类型 object? targetData = targetControl.DataContext; if (CheckCollectionElementType(sourceCollection, targetCollection) == false) { IList? collection = GetQueryTargetCollection(targetItemsControl)?.Invoke(targetControl.DataContext, sourceData.GetType()); if (collection == null || CheckCollectionElementType(sourceCollection, collection) == false) { return; } targetCollection = collection; targetData = null; // 置 targetData 为 null, 尝试将 sourceData 添加到 targetCollection 末尾 } // 执行 拖拽影响 DoDragDropEffects(args.Effects, sourceCollection, sourceData, targetCollection, targetData); } #endregion #region Drag Drop Effects static private void DoDragDropEffects(DragDropEffects effects, IList sourceCollection, object sourceData, IList targetCollection, object? targetData) { if (sourceCollection == null || sourceData == null || targetCollection == null) { return; } if (object.ReferenceEquals(sourceData, targetData)) { return; } switch (effects) { case DragDropEffects.Scroll: { break; } case DragDropEffects.All: { break; } case DragDropEffects.Copy: { break; } case DragDropEffects.Move: { DoMoveEffect(sourceCollection, sourceData, targetCollection, targetData); break; } case DragDropEffects.Link: { break; } default: { break; } } } static private void DoMoveEffect(IList sourceCollection, object sourceData, IList targetCollection, object? targetData) { int oldIndex = sourceCollection.IndexOf(sourceData); int newIndex = targetCollection.IndexOf(targetData); if (newIndex == -1) { newIndex = targetCollection.Count - 1; } if (object.ReferenceEquals(sourceCollection, targetCollection)) { if (oldIndex == newIndex) { return; } // 同个集合, 移除元素后下标可能变化 if (oldIndex < newIndex) { newIndex -= 1; } } sourceCollection.RemoveAt(oldIndex); targetCollection.Insert(newIndex + 1, sourceData); } #endregion #region Helper static private bool GetDragDropEventSource(ref ItemsControl parent, out Control child, Func getPosition) { if (parent != null) { ItemContainerGenerator generator = parent.ItemContainerGenerator; for (int i = generator.Items.Count - 1; i >= 0; --i) { if (generator.ContainerFromIndex(i) is Control control == false) { continue; } Point position = getPosition(control); if (position.X < 0 || position.X > control.ActualWidth || position.Y < 0 || position.Y > control.ActualHeight) { continue; } if (control is ContentControl) { child = control; return true; } if (control is ItemsControl itemsControl) { ItemsControl prevParent = parent; parent = itemsControl; if (GetDragDropEventSource(ref parent, out child, getPosition) == false) { child = parent; parent = prevParent; } return true; } } } child = default!; return false; } static private Control? GetElement(ItemsControl source, Point point) { if (GetItemContainerType(source) is Type type == false) { return null; } DependencyObject? dp = source.InputHitTest(point) as DependencyObject; while (dp != null) { if (dp.GetType().Equals(type)) { break; } dp = VisualTreeHelper.GetParent(dp); } return dp as Control; } static private ItemsControl? GetParent(Control control) { for (DependencyObject dp = VisualTreeHelper.GetParent(control); dp != null; dp = VisualTreeHelper.GetParent(dp)) { if (dp is ItemsControl parent && parent.ItemContainerGenerator.ContainerFromItem(control.DataContext) != null) { return parent; } } return null; } static private Type? GetItemContainerType(ItemsControl source) { if (source == null) { return null; } if (source is ListView) { return typeof(ListViewItem); } else if (source is ListBox) { return typeof(ListBoxItem); } else if (source is TreeView || source is TreeViewItem) { return typeof(TreeViewItem); } // else if (source is GridView) { // return typeof(GridViewColumn); // } if (source.Items.Count > 0) { return source.ItemContainerGenerator.ContainerFromIndex(0)?.GetType(); } return null; } static private bool CheckCollectionElementType(IList collection1, IList collection2) { return collection1.GetType().GetGenericArguments()[0] == collection2.GetType().GetGenericArguments()[0]; } #endregion } }