终端一体化运控平台
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

427 lines
15 KiB

  1. using BPASmartClient.MilkWithTea.Data;
  2. using System;
  3. using System.Collections;
  4. using System.Collections.Specialized;
  5. using System.Windows;
  6. using System.Windows.Controls;
  7. using System.Windows.Controls.Primitives;
  8. using System.Windows.Input;
  9. namespace BPASmartClient.MilkWithTea.Control;
  10. [TemplatePart(Name = HeaderPanelKey, Type = typeof(TabPanel))]
  11. [TemplatePart(Name = OverflowScrollviewer, Type = typeof(ScrollViewer))]
  12. [TemplatePart(Name = ScrollButtonLeft, Type = typeof(ButtonBase))]
  13. [TemplatePart(Name = ScrollButtonRight, Type = typeof(ButtonBase))]
  14. [TemplatePart(Name = HeaderBorder, Type = typeof(Border))]
  15. public class TabControl : System.Windows.Controls.TabControl
  16. {
  17. private const string OverflowButtonKey = "PART_OverflowButton";
  18. private const string HeaderPanelKey = "PART_HeaderPanel";
  19. private const string OverflowScrollviewer = "PART_OverflowScrollviewer";
  20. private const string ScrollButtonLeft = "PART_ScrollButtonLeft";
  21. private const string ScrollButtonRight = "PART_ScrollButtonRight";
  22. private const string HeaderBorder = "PART_HeaderBorder";
  23. private ContextMenuToggleButton _buttonOverflow;
  24. internal TabPanel HeaderPanel { get; private set; }
  25. private ScrollViewer _scrollViewerOverflow;
  26. private ButtonBase _buttonScrollLeft;
  27. private ButtonBase _buttonScrollRight;
  28. private Border _headerBorder;
  29. /// <summary>
  30. /// 是否为内部操作
  31. /// </summary>
  32. internal bool IsInternalAction;
  33. /// <summary>
  34. /// 是否启用动画
  35. /// </summary>
  36. public static readonly DependencyProperty IsAnimationEnabledProperty = DependencyProperty.Register(
  37. "IsAnimationEnabled", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox));
  38. /// <summary>
  39. /// 是否启用动画
  40. /// </summary>
  41. public bool IsAnimationEnabled
  42. {
  43. get => (bool) GetValue(IsAnimationEnabledProperty);
  44. set => SetValue(IsAnimationEnabledProperty, ValueBoxes.BooleanBox(value));
  45. }
  46. /// <summary>
  47. /// 是否可以拖动
  48. /// </summary>
  49. public static readonly DependencyProperty IsDraggableProperty = DependencyProperty.Register(
  50. "IsDraggable", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox));
  51. /// <summary>
  52. /// 是否可以拖动
  53. /// </summary>
  54. public bool IsDraggable
  55. {
  56. get => (bool) GetValue(IsDraggableProperty);
  57. set => SetValue(IsDraggableProperty, ValueBoxes.BooleanBox(value));
  58. }
  59. /// <summary>
  60. /// 是否显示关闭按钮
  61. /// </summary>
  62. public static readonly DependencyProperty ShowCloseButtonProperty = DependencyProperty.RegisterAttached(
  63. "ShowCloseButton", typeof(bool), typeof(TabControl), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.Inherits));
  64. public static void SetShowCloseButton(DependencyObject element, bool value)
  65. => element.SetValue(ShowCloseButtonProperty, ValueBoxes.BooleanBox(value));
  66. public static bool GetShowCloseButton(DependencyObject element)
  67. => (bool) element.GetValue(ShowCloseButtonProperty);
  68. /// <summary>
  69. /// 是否显示关闭按钮
  70. /// </summary>
  71. public bool ShowCloseButton
  72. {
  73. get => (bool) GetValue(ShowCloseButtonProperty);
  74. set => SetValue(ShowCloseButtonProperty, ValueBoxes.BooleanBox(value));
  75. }
  76. /// <summary>
  77. /// 是否显示上下文菜单
  78. /// </summary>
  79. public static readonly DependencyProperty ShowContextMenuProperty = DependencyProperty.RegisterAttached(
  80. "ShowContextMenu", typeof(bool), typeof(TabControl), new FrameworkPropertyMetadata(ValueBoxes.TrueBox, FrameworkPropertyMetadataOptions.Inherits));
  81. public static void SetShowContextMenu(DependencyObject element, bool value)
  82. => element.SetValue(ShowContextMenuProperty, ValueBoxes.BooleanBox(value));
  83. public static bool GetShowContextMenu(DependencyObject element)
  84. => (bool) element.GetValue(ShowContextMenuProperty);
  85. /// <summary>
  86. /// 是否显示上下文菜单
  87. /// </summary>
  88. public bool ShowContextMenu
  89. {
  90. get => (bool) GetValue(ShowContextMenuProperty);
  91. set => SetValue(ShowContextMenuProperty, ValueBoxes.BooleanBox(value));
  92. }
  93. /// <summary>
  94. /// 是否将标签填充
  95. /// </summary>
  96. public static readonly DependencyProperty IsTabFillEnabledProperty = DependencyProperty.Register(
  97. "IsTabFillEnabled", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox));
  98. /// <summary>
  99. /// 是否将标签填充
  100. /// </summary>
  101. public bool IsTabFillEnabled
  102. {
  103. get => (bool) GetValue(IsTabFillEnabledProperty);
  104. set => SetValue(IsTabFillEnabledProperty, ValueBoxes.BooleanBox(value));
  105. }
  106. /// <summary>
  107. /// 标签宽度
  108. /// </summary>
  109. public static readonly DependencyProperty TabItemWidthProperty = DependencyProperty.Register(
  110. "TabItemWidth", typeof(double), typeof(TabControl), new PropertyMetadata(200.0));
  111. /// <summary>
  112. /// 标签宽度
  113. /// </summary>
  114. public double TabItemWidth
  115. {
  116. get => (double) GetValue(TabItemWidthProperty);
  117. set => SetValue(TabItemWidthProperty, value);
  118. }
  119. /// <summary>
  120. /// 标签高度
  121. /// </summary>
  122. public static readonly DependencyProperty TabItemHeightProperty = DependencyProperty.Register(
  123. "TabItemHeight", typeof(double), typeof(TabControl), new PropertyMetadata(30.0));
  124. /// <summary>
  125. /// 标签高度
  126. /// </summary>
  127. public double TabItemHeight
  128. {
  129. get => (double) GetValue(TabItemHeightProperty);
  130. set => SetValue(TabItemHeightProperty, value);
  131. }
  132. /// <summary>
  133. /// 是否可以滚动
  134. /// </summary>
  135. public static readonly DependencyProperty IsScrollableProperty = DependencyProperty.Register(
  136. "IsScrollable", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox));
  137. /// <summary>
  138. /// 是否可以滚动
  139. /// </summary>
  140. public bool IsScrollable
  141. {
  142. get => (bool) GetValue(IsScrollableProperty);
  143. set => SetValue(IsScrollableProperty, ValueBoxes.BooleanBox(value));
  144. }
  145. /// <summary>
  146. /// 是否显示溢出按钮
  147. /// </summary>
  148. public static readonly DependencyProperty ShowOverflowButtonProperty = DependencyProperty.Register(
  149. "ShowOverflowButton", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.TrueBox));
  150. /// <summary>
  151. /// 是否显示溢出按钮
  152. /// </summary>
  153. public bool ShowOverflowButton
  154. {
  155. get => (bool) GetValue(ShowOverflowButtonProperty);
  156. set => SetValue(ShowOverflowButtonProperty, ValueBoxes.BooleanBox(value));
  157. }
  158. /// <summary>
  159. /// 是否显示滚动按钮
  160. /// </summary>
  161. public static readonly DependencyProperty ShowScrollButtonProperty = DependencyProperty.Register(
  162. "ShowScrollButton", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox));
  163. /// <summary>
  164. /// 是否显示滚动按钮
  165. /// </summary>
  166. public bool ShowScrollButton
  167. {
  168. get => (bool) GetValue(ShowScrollButtonProperty);
  169. set => SetValue(ShowScrollButtonProperty, ValueBoxes.BooleanBox(value));
  170. }
  171. /// <summary>
  172. /// 可见的标签数量
  173. /// </summary>
  174. private int _itemShowCount;
  175. protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
  176. {
  177. base.OnItemsChanged(e);
  178. if (HeaderPanel == null)
  179. {
  180. IsInternalAction = false;
  181. return;
  182. }
  183. UpdateOverflowButton();
  184. if (IsInternalAction)
  185. {
  186. IsInternalAction = false;
  187. return;
  188. }
  189. if (e.Action == NotifyCollectionChangedAction.Add)
  190. {
  191. for (var i = 0; i < Items.Count; i++)
  192. {
  193. if (ItemContainerGenerator.ContainerFromIndex(i) is not TabItem item) return;
  194. item.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
  195. item.TabPanel = HeaderPanel;
  196. }
  197. }
  198. _headerBorder?.InvalidateMeasure();
  199. IsInternalAction = false;
  200. }
  201. public override void OnApplyTemplate()
  202. {
  203. if (_buttonOverflow != null)
  204. {
  205. if (_buttonOverflow.Menu != null)
  206. {
  207. _buttonOverflow.Menu.Closed -= Menu_Closed;
  208. _buttonOverflow.Menu = null;
  209. }
  210. _buttonOverflow.Click -= ButtonOverflow_Click;
  211. }
  212. if (_buttonScrollLeft != null) _buttonScrollLeft.Click -= ButtonScrollLeft_Click;
  213. if (_buttonScrollRight != null) _buttonScrollRight.Click -= ButtonScrollRight_Click;
  214. base.OnApplyTemplate();
  215. HeaderPanel = GetTemplateChild(HeaderPanelKey) as TabPanel;
  216. if (IsTabFillEnabled) return;
  217. _buttonOverflow = GetTemplateChild(OverflowButtonKey) as ContextMenuToggleButton;
  218. _scrollViewerOverflow = GetTemplateChild(OverflowScrollviewer) as ScrollViewer;
  219. _buttonScrollLeft = GetTemplateChild(ScrollButtonLeft) as ButtonBase;
  220. _buttonScrollRight = GetTemplateChild(ScrollButtonRight) as ButtonBase;
  221. _headerBorder = GetTemplateChild(HeaderBorder) as Border;
  222. if (_buttonScrollLeft != null) _buttonScrollLeft.Click += ButtonScrollLeft_Click;
  223. if (_buttonScrollRight != null) _buttonScrollRight.Click += ButtonScrollRight_Click;
  224. if (_buttonOverflow != null)
  225. {
  226. var menu = new ContextMenu
  227. {
  228. Placement = PlacementMode.Bottom,
  229. PlacementTarget = _buttonOverflow
  230. };
  231. menu.Closed += Menu_Closed;
  232. _buttonOverflow.Menu = menu;
  233. _buttonOverflow.Click += ButtonOverflow_Click;
  234. }
  235. }
  236. protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
  237. {
  238. base.OnRenderSizeChanged(sizeInfo);
  239. UpdateOverflowButton();
  240. }
  241. private void UpdateOverflowButton()
  242. {
  243. if (!IsTabFillEnabled)
  244. {
  245. _itemShowCount = (int) (ActualWidth / TabItemWidth);
  246. //_buttonOverflow?.Show(ShowOverflowButton && Items.Count > 0 && Items.Count >= _itemShowCount);
  247. }
  248. }
  249. private void Menu_Closed(object sender, RoutedEventArgs e) => _buttonOverflow.IsChecked = false;
  250. private void ButtonScrollRight_Click(object sender, RoutedEventArgs e) =>
  251. _scrollViewerOverflow.ScrollToHorizontalOffsetWithAnimation(Math.Min(
  252. _scrollViewerOverflow.CurrentHorizontalOffset + TabItemWidth, _scrollViewerOverflow.ScrollableWidth));
  253. private void ButtonScrollLeft_Click(object sender, RoutedEventArgs e) =>
  254. _scrollViewerOverflow.ScrollToHorizontalOffsetWithAnimation(Math.Max(
  255. _scrollViewerOverflow.CurrentHorizontalOffset - TabItemWidth, 0));
  256. private void ButtonOverflow_Click(object sender, RoutedEventArgs e)
  257. {
  258. if (_buttonOverflow.IsChecked == true)
  259. {
  260. _buttonOverflow.Menu.Items.Clear();
  261. for (var i = 0; i < Items.Count; i++)
  262. {
  263. if (ItemContainerGenerator.ContainerFromIndex(i) is not TabItem item) continue;
  264. var menuItem = new MenuItem
  265. {
  266. HeaderStringFormat = ItemStringFormat,
  267. HeaderTemplate = ItemTemplate,
  268. HeaderTemplateSelector = ItemTemplateSelector,
  269. Header = item.Header,
  270. Width = TabItemWidth,
  271. IsChecked = item.IsSelected,
  272. IsCheckable = true,
  273. IsEnabled = item.IsEnabled
  274. };
  275. menuItem.Click += delegate
  276. {
  277. _buttonOverflow.IsChecked = false;
  278. var list = GetActualList();
  279. if (list == null) return;
  280. var actualItem = ItemContainerGenerator.ItemFromContainer(item);
  281. if (actualItem == null) return;
  282. var index = list.IndexOf(actualItem);
  283. if (index >= _itemShowCount)
  284. {
  285. list.Remove(actualItem);
  286. list.Insert(0, actualItem);
  287. HeaderPanel.SetValue(TabPanel.FluidMoveDurationPropertyKey,
  288. IsAnimationEnabled
  289. ? new Duration(TimeSpan.FromMilliseconds(200))
  290. : new Duration(TimeSpan.FromMilliseconds(0)));
  291. HeaderPanel.ForceUpdate = true;
  292. HeaderPanel.Measure(new Size(HeaderPanel.DesiredSize.Width, ActualHeight));
  293. HeaderPanel.ForceUpdate = false;
  294. SetCurrentValue(SelectedIndexProperty, ValueBoxes.Int0Box);
  295. }
  296. item.IsSelected = true;
  297. };
  298. _buttonOverflow.Menu.Items.Add(menuItem);
  299. }
  300. }
  301. }
  302. internal double GetHorizontalOffset() => _scrollViewerOverflow?.CurrentHorizontalOffset ?? 0;
  303. internal void UpdateScroll() => _scrollViewerOverflow?.RaiseEvent(new MouseWheelEventArgs(Mouse.PrimaryDevice, Environment.TickCount, 0)
  304. {
  305. RoutedEvent = MouseWheelEvent
  306. });
  307. internal void CloseAllItems() => CloseOtherItems(null);
  308. internal void CloseOtherItems(TabItem currentItem)
  309. {
  310. var actualItem = currentItem != null ? ItemContainerGenerator.ItemFromContainer(currentItem) : null;
  311. var list = GetActualList();
  312. if (list == null) return;
  313. IsInternalAction = true;
  314. for (var i = 0; i < Items.Count; i++)
  315. {
  316. var item = list[i];
  317. if (!Equals(item, actualItem) && item != null)
  318. {
  319. var argsClosing = new CancelRoutedEventArgs(TabItem.ClosingEvent, item);
  320. if (ItemContainerGenerator.ContainerFromItem(item) is not TabItem tabItem) continue;
  321. tabItem.RaiseEvent(argsClosing);
  322. if (argsClosing.Cancel) return;
  323. tabItem.RaiseEvent(new RoutedEventArgs(TabItem.ClosedEvent, item));
  324. list.Remove(item);
  325. i--;
  326. }
  327. }
  328. SetCurrentValue(SelectedIndexProperty, Items.Count == 0 ? -1 : 0);
  329. }
  330. internal IList GetActualList()
  331. {
  332. IList list;
  333. if (ItemsSource != null)
  334. {
  335. list = ItemsSource as IList;
  336. }
  337. else
  338. {
  339. list = Items;
  340. }
  341. return list;
  342. }
  343. protected override bool IsItemItsOwnContainerOverride(object item) => item is TabItem;
  344. protected override DependencyObject GetContainerForItemOverride() => new TabItem();
  345. }