ItemsDraggable.cs 11 KB

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