ItemsDraggable.cs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. using System.Collections;
  2. using System.Windows;
  3. using System.Windows.Controls;
  4. using System.Windows.Input;
  5. namespace Waaagh.Behaviors {
  6. sealed public class ItemsDraggable {
  7. #region Drag Drop Data Format String
  8. static readonly string DraggableDataFormat = "DraggableData";
  9. static readonly string DraggableCollectionFormat = "DraggableCollection";
  10. #endregion
  11. #region Enabled
  12. static public bool GetEnabled(DependencyObject obj) {
  13. return (bool)obj.GetValue(EnabledProperty);
  14. }
  15. static public void SetEnabled(DependencyObject obj, bool value) {
  16. obj.SetValue(EnabledProperty, value);
  17. }
  18. static public readonly DependencyProperty EnabledProperty =
  19. DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(ItemsDraggable),
  20. new PropertyMetadata(false, OnEnabledChanged));
  21. static private void OnEnabledChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) {
  22. if (sender is ItemsControl itemsControl == false) {
  23. throw new InvalidOperationException("The attached control is not an 'ItemsControl'.");
  24. }
  25. itemsControl.AllowDrop = false;
  26. itemsControl.MouseMove -= ItemsDraggable_MouseMove;
  27. itemsControl.DragEnter -= ItemsDraggable_DragEnter;
  28. itemsControl.DragOver -= ItemsDraggable_DragOver;
  29. itemsControl.DragLeave -= ItemsDraggable_DragLeave;
  30. itemsControl.Drop -= ItemsDraggable_Drop;
  31. if (GetEnabled(itemsControl) == true) {
  32. itemsControl.AllowDrop = true;
  33. itemsControl.MouseMove += ItemsDraggable_MouseMove;
  34. itemsControl.DragEnter += ItemsDraggable_DragEnter;
  35. itemsControl.DragOver += ItemsDraggable_DragOver;
  36. itemsControl.DragLeave += ItemsDraggable_DragLeave;
  37. itemsControl.Drop += ItemsDraggable_Drop;
  38. }
  39. }
  40. public static Func<object, Type, IList?> GetQueryTargetCollection(DependencyObject obj) {
  41. return (Func<object, Type, IList?>)obj.GetValue(QueryTargetCollectionProperty);
  42. }
  43. public static void SetQueryTargetCollection(DependencyObject obj, Func<object, Type, IList?> value) {
  44. obj.SetValue(QueryTargetCollectionProperty, value);
  45. }
  46. public static readonly DependencyProperty QueryTargetCollectionProperty =
  47. DependencyProperty.RegisterAttached("QueryTargetCollection", typeof(Func<object, Type, IList?>), typeof(ItemsDraggable), new PropertyMetadata(null));
  48. #endregion
  49. #region Drag Drop Event
  50. static private void ItemsDraggable_MouseMove(object sender, MouseEventArgs args) {
  51. if (args.LeftButton != MouseButtonState.Pressed) {
  52. return;
  53. }
  54. if (sender is ItemsControl sourceItemsControl == false) {
  55. return;
  56. }
  57. if (sourceItemsControl.ItemsSource is IList == false) {
  58. return;
  59. }
  60. if (GetDragDropEventSource(ref sourceItemsControl, out Control sourceControl, args.GetPosition) == false) {
  61. return;
  62. }
  63. DataObject data = new DataObject();
  64. data.SetData(DraggableDataFormat, sourceControl.DataContext);
  65. data.SetData(DraggableCollectionFormat, sourceItemsControl.ItemsSource);
  66. DragDrop.DoDragDrop(sourceItemsControl, data, DragDropEffects.Move);
  67. }
  68. static private void ItemsDraggable_DragEnter(object sender, DragEventArgs args) {
  69. }
  70. static private void ItemsDraggable_DragOver(object sender, DragEventArgs args) {
  71. }
  72. static private void ItemsDraggable_DragLeave(object sender, DragEventArgs args) {
  73. }
  74. static private void ItemsDraggable_Drop(object sender, DragEventArgs args) {
  75. if (args.Effects == DragDropEffects.None) {
  76. return;
  77. }
  78. if (sender is ItemsControl targetItemsControl == false) {
  79. return;
  80. }
  81. // 获取 拖拽源
  82. object? sourceData = args.Data.GetData(DraggableDataFormat) as object;
  83. IList? sourceCollection = args.Data.GetData(DraggableCollectionFormat) as IList;
  84. if (sourceData == null || sourceCollection == null) {
  85. return;
  86. }
  87. // 获取 放置目标
  88. if (GetDragDropEventSource(ref targetItemsControl, out Control targetControl, args.GetPosition) == false) {
  89. return;
  90. }
  91. if (targetItemsControl.ItemsSource is IList targetCollection == false) {
  92. return;
  93. }
  94. // 判断 集合元素类型
  95. object? targetData = targetControl.DataContext;
  96. if (CheckCollectionElementType(sourceCollection, targetCollection) == false) {
  97. IList? collection = GetQueryTargetCollection(targetItemsControl)?.Invoke(targetControl.DataContext, sourceData.GetType());
  98. if (collection == null || CheckCollectionElementType(sourceCollection, collection) == false) {
  99. return;
  100. }
  101. targetCollection = collection;
  102. targetData = null; // 置 targetData 为 null, 尝试将 sourceData 添加到 targetCollection 末尾
  103. }
  104. // 执行 拖拽影响
  105. DoDragDropEffects(args.Effects, sourceCollection, sourceData, targetCollection, targetData);
  106. }
  107. #endregion
  108. #region Drag Drop Effects
  109. static private void DoDragDropEffects(DragDropEffects effects, IList sourceCollection, object sourceData, IList targetCollection, object? targetData) {
  110. if (sourceCollection == null || sourceData == null || targetCollection == null) {
  111. return;
  112. }
  113. if (object.ReferenceEquals(sourceData, targetData)) {
  114. return;
  115. }
  116. switch (effects) {
  117. case DragDropEffects.Scroll: {
  118. break;
  119. }
  120. case DragDropEffects.All: {
  121. break;
  122. }
  123. case DragDropEffects.Copy: {
  124. break;
  125. }
  126. case DragDropEffects.Move: {
  127. DoMoveEffect(sourceCollection, sourceData, targetCollection, targetData);
  128. break;
  129. }
  130. case DragDropEffects.Link: {
  131. break;
  132. }
  133. default: {
  134. break;
  135. }
  136. }
  137. }
  138. static private void DoMoveEffect(IList sourceCollection, object sourceData, IList targetCollection, object? targetData) {
  139. int oldIndex = sourceCollection.IndexOf(sourceData);
  140. int newIndex = targetCollection.IndexOf(targetData);
  141. if (newIndex == -1) {
  142. newIndex = targetCollection.Count - 1;
  143. }
  144. if (object.ReferenceEquals(sourceCollection, targetCollection)) {
  145. if (oldIndex == newIndex) {
  146. return;
  147. }
  148. // 同个集合, 移除元素后下标可能变化
  149. if (oldIndex < newIndex) {
  150. newIndex -= 1;
  151. }
  152. }
  153. sourceCollection.RemoveAt(oldIndex);
  154. targetCollection.Insert(newIndex + 1, sourceData);
  155. }
  156. #endregion
  157. #region Helper
  158. static private bool GetDragDropEventSource(ref ItemsControl parent, out Control child, Func<IInputElement, Point> getPosition) {
  159. if (parent != null) {
  160. ItemContainerGenerator generator = parent.ItemContainerGenerator;
  161. for (int i = generator.Items.Count - 1; i >= 0; --i) {
  162. if (generator.ContainerFromIndex(i) is Control control == false) {
  163. continue;
  164. }
  165. Point position = getPosition(control);
  166. if (position.X < 0 || position.X > control.ActualWidth ||
  167. position.Y < 0 || position.Y > control.ActualHeight) {
  168. continue;
  169. }
  170. if (control is ContentControl) {
  171. child = control;
  172. return true;
  173. }
  174. if (control is ItemsControl itemsControl) {
  175. ItemsControl prevParent = parent;
  176. parent = itemsControl;
  177. if (GetDragDropEventSource(ref parent, out child, getPosition) == false) {
  178. child = parent;
  179. parent = prevParent;
  180. }
  181. return true;
  182. }
  183. }
  184. }
  185. child = default!;
  186. return false;
  187. }
  188. static private bool CheckCollectionElementType(IList collection1, IList collection2) {
  189. return collection1.GetType().GetGenericArguments()[0] == collection2.GetType().GetGenericArguments()[0];
  190. }
  191. #endregion
  192. }
  193. }