终端一体化运控平台
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

510 lines
15 KiB

  1. using System;
  2. using System.Windows;
  3. using System.Windows.Controls;
  4. using System.Windows.Controls.Primitives;
  5. using System.Windows.Data;
  6. using System.Windows.Input;
  7. using System.Windows.Media;
  8. using System.Windows.Media.Animation;
  9. using BPASmartClient.MilkWithTea.Control;
  10. using BPASmartClient.MilkWithTea.Data;
  11. using TabControl = BPASmartClient.MilkWithTea.Control.TabControl;
  12. namespace BPASmartClient.MilkWithTea.Control;
  13. public class TabItem : System.Windows.Controls.TabItem
  14. {
  15. /// <summary>
  16. /// 动画速度
  17. /// </summary>
  18. private const int AnimationSpeed = 150;
  19. /// <summary>
  20. /// 选项卡是否处于拖动状态
  21. /// </summary>
  22. private static bool ItemIsDragging;
  23. /// <summary>
  24. /// 选项卡是否等待被拖动
  25. /// </summary>
  26. private bool _isWaiting;
  27. /// <summary>
  28. /// 拖动中的选项卡坐标
  29. /// </summary>
  30. private Point _dragPoint;
  31. /// <summary>
  32. /// 鼠标按下时选项卡位置
  33. /// </summary>
  34. private int _mouseDownIndex;
  35. /// <summary>
  36. /// 鼠标按下时选项卡横向偏移
  37. /// </summary>
  38. private double _mouseDownOffsetX;
  39. /// <summary>
  40. /// 鼠标按下时的坐标
  41. /// </summary>
  42. private Point _mouseDownPoint;
  43. /// <summary>
  44. /// 右侧可移动的最大值
  45. /// </summary>
  46. private double _maxMoveRight;
  47. /// <summary>
  48. /// 左侧可移动的最大值
  49. /// </summary>
  50. private double _maxMoveLeft;
  51. /// <summary>
  52. /// 选项卡宽度
  53. /// </summary>
  54. public double ItemWidth { get; internal set; }
  55. /// <summary>
  56. /// 选项卡拖动等待距离(在鼠标移动了超过20个像素无关单位后,选项卡才开始被拖动)
  57. /// </summary>
  58. private const double WaitLength = 20;
  59. /// <summary>
  60. /// 选项卡是否处于拖动状态
  61. /// </summary>
  62. private bool _isDragging;
  63. /// <summary>
  64. /// 选项卡是否已经被拖动
  65. /// </summary>
  66. private bool _isDragged;
  67. /// <summary>
  68. /// 目标横向位移
  69. /// </summary>
  70. internal double TargetOffsetX { get; set; }
  71. /// <summary>
  72. /// 当前编号
  73. /// </summary>
  74. private int _currentIndex;
  75. /// <summary>
  76. /// 标签容器横向滚动距离
  77. /// </summary>
  78. private double _scrollHorizontalOffset;
  79. private TabPanel _tabPanel;
  80. /// <summary>
  81. /// 标签容器
  82. /// </summary>
  83. internal TabPanel TabPanel
  84. {
  85. get
  86. {
  87. if (_tabPanel == null && TabControlParent != null)
  88. {
  89. _tabPanel = TabControlParent.HeaderPanel;
  90. }
  91. return _tabPanel;
  92. }
  93. set => _tabPanel = value;
  94. }
  95. private TabControl TabControlParent => ItemsControl.ItemsControlFromItemContainer(this) as TabControl;
  96. /// <summary>
  97. /// 当前编号
  98. /// </summary>
  99. internal int CurrentIndex
  100. {
  101. get => _currentIndex;
  102. set
  103. {
  104. if (_currentIndex == value || value < 0) return;
  105. var oldIndex = _currentIndex;
  106. _currentIndex = value;
  107. UpdateItemOffsetX(oldIndex);
  108. }
  109. }
  110. /// <summary>
  111. /// 是否显示关闭按钮
  112. /// </summary>
  113. public static readonly DependencyProperty ShowCloseButtonProperty =
  114. TabControl.ShowCloseButtonProperty.AddOwner(typeof(TabItem));
  115. /// <summary>
  116. /// 是否显示关闭按钮
  117. /// </summary>
  118. public bool ShowCloseButton
  119. {
  120. get => (bool) GetValue(ShowCloseButtonProperty);
  121. set => SetValue(ShowCloseButtonProperty, ValueBoxes.BooleanBox(value));
  122. }
  123. public static void SetShowCloseButton(DependencyObject element, bool value)
  124. => element.SetValue(ShowCloseButtonProperty, ValueBoxes.BooleanBox(value));
  125. public static bool GetShowCloseButton(DependencyObject element)
  126. => (bool) element.GetValue(ShowCloseButtonProperty);
  127. /// <summary>
  128. /// 是否显示上下文菜单
  129. /// </summary>
  130. public static readonly DependencyProperty ShowContextMenuProperty =
  131. TabControl.ShowContextMenuProperty.AddOwner(typeof(TabItem), new FrameworkPropertyMetadata(OnShowContextMenuChanged));
  132. private static void OnShowContextMenuChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  133. {
  134. var ctl = (TabItem) d;
  135. if (ctl.Menu != null)
  136. {
  137. var show = (bool) e.NewValue;
  138. ctl.Menu.IsEnabled = show;
  139. //ctl.Menu.Show(show);
  140. }
  141. }
  142. /// <summary>
  143. /// 是否显示上下文菜单
  144. /// </summary>
  145. public bool ShowContextMenu
  146. {
  147. get => (bool) GetValue(ShowContextMenuProperty);
  148. set => SetValue(ShowContextMenuProperty, ValueBoxes.BooleanBox(value));
  149. }
  150. public static void SetShowContextMenu(DependencyObject element, bool value)
  151. => element.SetValue(ShowContextMenuProperty, ValueBoxes.BooleanBox(value));
  152. public static bool GetShowContextMenu(DependencyObject element)
  153. => (bool) element.GetValue(ShowContextMenuProperty);
  154. public static readonly DependencyProperty MenuProperty = DependencyProperty.Register(
  155. "Menu", typeof(ContextMenu), typeof(TabItem), new PropertyMetadata(default(ContextMenu), OnMenuChanged));
  156. private static void OnMenuChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  157. {
  158. var ctl = (TabItem) d;
  159. //ctl.OnMenuChanged(e.NewValue as ContextMenu);
  160. }
  161. //private void OnMenuChanged(ContextMenu menu)
  162. //{
  163. // if (IsLoaded && menu != null)
  164. // {
  165. // var parent = TabControlParent;
  166. // if (parent == null) return;
  167. // var item = parent.ItemContainerGenerator.ItemFromContainer(this);
  168. // menu.DataContext = item;
  169. // menu.SetBinding(IsEnabledProperty, new Binding(ShowContextMenuProperty.Name)
  170. // {
  171. // Source = this
  172. // });
  173. // menu.SetBinding(VisibilityProperty, new Binding(ShowContextMenuProperty.Name)
  174. // {
  175. // Source = this,
  176. // Converter = GetResourceInternal<IValueConverter>(ResourceToken.Boolean2VisibilityConverter)
  177. // });
  178. // }
  179. //}
  180. public ContextMenu Menu
  181. {
  182. get => (ContextMenu) GetValue(MenuProperty);
  183. set => SetValue(MenuProperty, value);
  184. }
  185. /// <summary>
  186. /// 更新选项卡横向偏移
  187. /// </summary>
  188. /// <param name="oldIndex"></param>
  189. private void UpdateItemOffsetX(int oldIndex)
  190. {
  191. if (!_isDragging || CurrentIndex >= TabPanel.ItemDic.Count)
  192. {
  193. return;
  194. }
  195. var moveItem = TabPanel.ItemDic[CurrentIndex];
  196. moveItem.CurrentIndex -= CurrentIndex - oldIndex;
  197. var offsetX = moveItem.TargetOffsetX;
  198. var resultX = offsetX + (oldIndex - CurrentIndex) * ItemWidth;
  199. TabPanel.ItemDic[CurrentIndex] = this;
  200. TabPanel.ItemDic[moveItem.CurrentIndex] = moveItem;
  201. moveItem.CreateAnimation(offsetX, resultX);
  202. }
  203. public TabItem()
  204. {
  205. //CommandBindings.Add(new CommandBinding(ControlCommands.Close, (s, e) => Close()));
  206. //CommandBindings.Add(new CommandBinding(ControlCommands.CloseAll,
  207. // (s, e) => { TabControlParent.CloseAllItems(); }));
  208. //CommandBindings.Add(new CommandBinding(ControlCommands.CloseOther,
  209. // (s, e) => { TabControlParent.CloseOtherItems(this); }));
  210. //Loaded += (s, e) => OnMenuChanged(Menu);
  211. }
  212. protected override void OnMouseRightButtonDown(MouseButtonEventArgs e)
  213. {
  214. base.OnMouseRightButtonDown(e);
  215. if (VisualTreeHelper.HitTest(this, e.GetPosition(this)) == null) return;
  216. IsSelected = true;
  217. Focus();
  218. }
  219. protected override void OnHeaderChanged(object oldHeader, object newHeader)
  220. {
  221. base.OnHeaderChanged(oldHeader, newHeader);
  222. if (TabPanel != null)
  223. {
  224. TabPanel.ForceUpdate = true;
  225. InvalidateMeasure();
  226. TabPanel.ForceUpdate = true;
  227. }
  228. }
  229. internal void Close()
  230. {
  231. var parent = TabControlParent;
  232. if (parent == null) return;
  233. var item = parent.ItemContainerGenerator.ItemFromContainer(this);
  234. var argsClosing = new CancelRoutedEventArgs(ClosingEvent, item);
  235. RaiseEvent(argsClosing);
  236. if (argsClosing.Cancel) return;
  237. TabPanel.SetValue(TabPanel.FluidMoveDurationPropertyKey, parent.IsAnimationEnabled
  238. ? new Duration(TimeSpan.FromMilliseconds(200))
  239. : new Duration(TimeSpan.FromMilliseconds(1)));
  240. parent.IsInternalAction = true;
  241. RaiseEvent(new RoutedEventArgs(ClosedEvent, item));
  242. var list = parent.GetActualList();
  243. list?.Remove(item);
  244. }
  245. protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
  246. {
  247. base.OnMouseLeftButtonDown(e);
  248. if (VisualTreeHelper.HitTest(this, e.GetPosition(this)) == null) return;
  249. var parent = TabControlParent;
  250. if (parent == null) return;
  251. if (parent.IsDraggable && !ItemIsDragging && !_isDragging)
  252. {
  253. parent.UpdateScroll();
  254. TabPanel.SetValue(TabPanel.FluidMoveDurationPropertyKey, new Duration(TimeSpan.FromSeconds(0)));
  255. _mouseDownOffsetX = RenderTransform.Value.OffsetX;
  256. _scrollHorizontalOffset = parent.GetHorizontalOffset();
  257. var mx = TranslatePoint(new Point(), parent).X + _scrollHorizontalOffset;
  258. _mouseDownIndex = CalLocationIndex(mx);
  259. var subIndex = _mouseDownIndex - CalLocationIndex(_scrollHorizontalOffset);
  260. _maxMoveLeft = -subIndex * ItemWidth;
  261. _maxMoveRight = parent.ActualWidth - ActualWidth + _maxMoveLeft;
  262. _isDragging = true;
  263. ItemIsDragging = true;
  264. _isWaiting = true;
  265. _dragPoint = e.GetPosition(parent);
  266. _dragPoint = new Point(_dragPoint.X + _scrollHorizontalOffset, _dragPoint.Y);
  267. _mouseDownPoint = _dragPoint;
  268. CaptureMouse();
  269. }
  270. }
  271. protected override void OnMouseMove(MouseEventArgs e)
  272. {
  273. base.OnMouseMove(e);
  274. if (ItemIsDragging && _isDragging)
  275. {
  276. var parent = TabControlParent;
  277. if (parent == null) return;
  278. var subX = TranslatePoint(new Point(), parent).X + _scrollHorizontalOffset;
  279. CurrentIndex = CalLocationIndex(subX);
  280. var p = e.GetPosition(parent);
  281. p = new Point(p.X + _scrollHorizontalOffset, p.Y);
  282. var subLeft = p.X - _dragPoint.X;
  283. var totalLeft = p.X - _mouseDownPoint.X;
  284. if (Math.Abs(subLeft) <= WaitLength && _isWaiting) return;
  285. _isWaiting = false;
  286. _isDragged = true;
  287. var left = subLeft + RenderTransform.Value.OffsetX;
  288. if (totalLeft < _maxMoveLeft)
  289. {
  290. left = _maxMoveLeft + _mouseDownOffsetX;
  291. }
  292. else if (totalLeft > _maxMoveRight)
  293. {
  294. left = _maxMoveRight + _mouseDownOffsetX;
  295. }
  296. var t = new TranslateTransform(left, 0);
  297. RenderTransform = t;
  298. _dragPoint = p;
  299. }
  300. }
  301. protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
  302. {
  303. base.OnMouseLeftButtonUp(e);
  304. ReleaseMouseCapture();
  305. if (_isDragged)
  306. {
  307. var parent = TabControlParent;
  308. if (parent == null) return;
  309. var subX = TranslatePoint(new Point(), parent).X + _scrollHorizontalOffset;
  310. var index = CalLocationIndex(subX);
  311. var left = index * ItemWidth;
  312. var offsetX = RenderTransform.Value.OffsetX;
  313. CreateAnimation(offsetX, offsetX - subX + left, index);
  314. }
  315. _isDragging = false;
  316. ItemIsDragging = false;
  317. _isDragged = false;
  318. }
  319. protected override void OnMouseDown(MouseButtonEventArgs e)
  320. {
  321. if (e.ChangedButton == MouseButton.Middle && e.ButtonState == MouseButtonState.Pressed)
  322. {
  323. if (ShowCloseButton || ShowContextMenu)
  324. {
  325. Close();
  326. }
  327. }
  328. }
  329. /// <summary>
  330. /// 创建动画
  331. /// </summary>
  332. internal void CreateAnimation(double offsetX, double resultX, int index = -1)
  333. {
  334. var parent = TabControlParent;
  335. void AnimationCompleted()
  336. {
  337. RenderTransform = new TranslateTransform(resultX, 0);
  338. if (index == -1) return;
  339. var list = parent.GetActualList();
  340. if (list == null) return;
  341. var item = parent.ItemContainerGenerator.ItemFromContainer(this);
  342. if (item == null) return;
  343. TabPanel.CanUpdate = false;
  344. parent.IsInternalAction = true;
  345. list.Remove(item);
  346. parent.IsInternalAction = true;
  347. list.Insert(index, item);
  348. _tabPanel.SetValue(TabPanel.FluidMoveDurationPropertyKey, new Duration(TimeSpan.FromMilliseconds(0)));
  349. TabPanel.CanUpdate = true;
  350. TabPanel.ForceUpdate = true;
  351. TabPanel.Measure(new Size(TabPanel.DesiredSize.Width, ActualHeight));
  352. TabPanel.ForceUpdate = false;
  353. Focus();
  354. IsSelected = true;
  355. if (!IsMouseCaptured)
  356. {
  357. parent.SetCurrentValue(Selector.SelectedIndexProperty, _currentIndex);
  358. }
  359. }
  360. TargetOffsetX = resultX;
  361. if (!parent.IsAnimationEnabled)
  362. {
  363. AnimationCompleted();
  364. return;
  365. }
  366. var animation = CreateAnimation(resultX, AnimationSpeed);
  367. animation.FillBehavior = FillBehavior.Stop;
  368. animation.Completed += (s1, e1) => AnimationCompleted();
  369. var f = new TranslateTransform(offsetX, 0);
  370. RenderTransform = f;
  371. f.BeginAnimation(TranslateTransform.XProperty, animation, HandoffBehavior.Compose);
  372. }
  373. /// <summary>
  374. /// 创建一个Double动画
  375. /// </summary>
  376. /// <param name="toValue"></param>
  377. /// <param name="milliseconds"></param>
  378. /// <returns></returns>
  379. public static DoubleAnimation CreateAnimation(double toValue, double milliseconds = 200)
  380. {
  381. return new(toValue, new Duration(TimeSpan.FromMilliseconds(milliseconds)))
  382. {
  383. EasingFunction = new PowerEase { EasingMode = EasingMode.EaseInOut }
  384. };
  385. }
  386. /// <summary>
  387. /// 计算选项卡当前合适的位置编号
  388. /// </summary>
  389. /// <param name="left"></param>
  390. /// <returns></returns>
  391. private int CalLocationIndex(double left)
  392. {
  393. if (_isWaiting)
  394. {
  395. return CurrentIndex;
  396. }
  397. var maxIndex = TabControlParent.Items.Count - 1;
  398. var div = (int) (left / ItemWidth);
  399. var rest = left % ItemWidth;
  400. var result = rest / ItemWidth > .5 ? div + 1 : div;
  401. return result > maxIndex ? maxIndex : result;
  402. }
  403. public static readonly RoutedEvent ClosingEvent = EventManager.RegisterRoutedEvent("Closing", RoutingStrategy.Bubble, typeof(EventHandler), typeof(TabItem));
  404. public event EventHandler Closing
  405. {
  406. add => AddHandler(ClosingEvent, value);
  407. remove => RemoveHandler(ClosingEvent, value);
  408. }
  409. public static readonly RoutedEvent ClosedEvent = EventManager.RegisterRoutedEvent("Closed", RoutingStrategy.Bubble, typeof(EventHandler), typeof(TabItem));
  410. public event EventHandler Closed
  411. {
  412. add => AddHandler(ClosedEvent, value);
  413. remove => RemoveHandler(ClosedEvent, value);
  414. }
  415. }