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 Dragging Data Object private static DataObject? GetDraggingData(DependencyObject obj) { return (DataObject?)obj.GetValue(DraggingDataProperty); } private static void SetDraggingData(DependencyObject obj, DataObject? value) { obj.SetValue(DraggingDataProperty, value); } private static readonly DependencyProperty DraggingDataProperty = DependencyProperty.RegisterAttached( "DraggingData", typeof(DataObject), typeof(ItemsDraggable), new PropertyMetadata(null)); #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'."); } // if (itemsControl.ItemsSource is IList == false) { // throw new InvalidOperationException("The attached control must have an 'ItemsSource' implements 'IList'."); // } itemsControl.AllowDrop = false; itemsControl.PreviewMouseLeftButtonDown -= ItemsDraggable_PreviewMouseLeftButtonDown; 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.PreviewMouseLeftButtonDown += ItemsDraggable_PreviewMouseLeftButtonDown; itemsControl.MouseMove += ItemsDraggable_MouseMove; itemsControl.DragEnter += ItemsDraggable_DragEnter; itemsControl.DragOver += ItemsDraggable_DragOver; itemsControl.DragLeave += ItemsDraggable_DragLeave; itemsControl.Drop += ItemsDraggable_Drop; } } #endregion #region Drag Drop Event private static void ItemsDraggable_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs args) { if (sender is ItemsControl control == false) { return; } if (control.ItemsSource is IList == false) { return; } if (GetElement(control, args.GetPosition(control)) 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); SetDraggingData(control, data); } static private void ItemsDraggable_MouseMove(object sender, MouseEventArgs args) { if (args.LeftButton != MouseButtonState.Pressed) { return; } if (sender is ItemsControl source == false) { return; } if (GetDraggingData(source) is DataObject data == false) { return; } SetDraggingData(source, null); DragDrop.DoDragDrop(source, 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 control == false) { return; } // 获取 放置目标 if (GetElement(control, args.GetPosition(control)) is Control targetControl == false) { return; } if (GetParent(targetControl) is ItemsControl targetItemsControl == false) { return; } object? targetData = targetControl.DataContext; IList? targetCollection = targetItemsControl.ItemsSource as IList; if (targetCollection == null) { return; } // 获取 拖拽源 object? sourceData = args.Data.GetData(DraggableDataFormat) as object; IList? sourceCollection = args.Data.GetData(DraggableCollectionFormat) as IList; if (sourceData == null || sourceCollection == null) { return; } // 执行 拖拽影响 DoDragDropEffects(control, args.Effects, sourceCollection, sourceData, targetCollection, targetData); } #endregion #region Drag Drop Effects public delegate void DoEffectsCallbackHandler(object sender, DoEffectsCallbackArgs args); public class DoEffectsCallbackArgs { public IList SourceCollection { get; } public object SourceData { get; } public IList TargetCollection { get; } public object? TargetData { get; } public DoEffectsCallbackArgs(IList sourceCollection, object sourceData, IList targetCollection, object? targetData) { SourceCollection = sourceCollection; SourceData = sourceData; TargetCollection = targetCollection; TargetData = targetData; } } static private void DoDragDropEffects(ItemsControl control, 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: { GetDoMoveEffect(control)?.Invoke(control, new DoEffectsCallbackArgs(sourceCollection, sourceData, targetCollection, targetData)); break; } case DragDropEffects.Link: { break; } default: { break; } } } #region Do Move Effect Callback public static DoEffectsCallbackHandler GetDoMoveEffect(DependencyObject obj) { return (DoEffectsCallbackHandler)obj.GetValue(DoMoveEffectProperty); } public static void SetDoMoveEffect(DependencyObject obj, DoEffectsCallbackHandler value) { obj.SetValue(DoMoveEffectProperty, value); } public static readonly DependencyProperty DoMoveEffectProperty = DependencyProperty.RegisterAttached( "DoMoveEffect", typeof(DoEffectsCallbackHandler), typeof(ItemsDraggable), new PropertyMetadata(null)); #endregion #endregion #region Helper 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; } #endregion }