using BPASmartClient.MilkWithTea.Data; using System; using System.Collections; using System.Collections.Specialized; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; namespace BPASmartClient.MilkWithTea.Control; [TemplatePart(Name = HeaderPanelKey, Type = typeof(TabPanel))] [TemplatePart(Name = OverflowScrollviewer, Type = typeof(ScrollViewer))] [TemplatePart(Name = ScrollButtonLeft, Type = typeof(ButtonBase))] [TemplatePart(Name = ScrollButtonRight, Type = typeof(ButtonBase))] [TemplatePart(Name = HeaderBorder, Type = typeof(Border))] public class TabControl : System.Windows.Controls.TabControl { private const string OverflowButtonKey = "PART_OverflowButton"; private const string HeaderPanelKey = "PART_HeaderPanel"; private const string OverflowScrollviewer = "PART_OverflowScrollviewer"; private const string ScrollButtonLeft = "PART_ScrollButtonLeft"; private const string ScrollButtonRight = "PART_ScrollButtonRight"; private const string HeaderBorder = "PART_HeaderBorder"; private ContextMenuToggleButton _buttonOverflow; internal TabPanel HeaderPanel { get; private set; } private ScrollViewer _scrollViewerOverflow; private ButtonBase _buttonScrollLeft; private ButtonBase _buttonScrollRight; private Border _headerBorder; /// /// 是否为内部操作 /// internal bool IsInternalAction; /// /// 是否启用动画 /// public static readonly DependencyProperty IsAnimationEnabledProperty = DependencyProperty.Register( "IsAnimationEnabled", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox)); /// /// 是否启用动画 /// public bool IsAnimationEnabled { get => (bool) GetValue(IsAnimationEnabledProperty); set => SetValue(IsAnimationEnabledProperty, ValueBoxes.BooleanBox(value)); } /// /// 是否可以拖动 /// public static readonly DependencyProperty IsDraggableProperty = DependencyProperty.Register( "IsDraggable", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox)); /// /// 是否可以拖动 /// public bool IsDraggable { get => (bool) GetValue(IsDraggableProperty); set => SetValue(IsDraggableProperty, ValueBoxes.BooleanBox(value)); } /// /// 是否显示关闭按钮 /// public static readonly DependencyProperty ShowCloseButtonProperty = DependencyProperty.RegisterAttached( "ShowCloseButton", typeof(bool), typeof(TabControl), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.Inherits)); public static void SetShowCloseButton(DependencyObject element, bool value) => element.SetValue(ShowCloseButtonProperty, ValueBoxes.BooleanBox(value)); public static bool GetShowCloseButton(DependencyObject element) => (bool) element.GetValue(ShowCloseButtonProperty); /// /// 是否显示关闭按钮 /// public bool ShowCloseButton { get => (bool) GetValue(ShowCloseButtonProperty); set => SetValue(ShowCloseButtonProperty, ValueBoxes.BooleanBox(value)); } /// /// 是否显示上下文菜单 /// public static readonly DependencyProperty ShowContextMenuProperty = DependencyProperty.RegisterAttached( "ShowContextMenu", typeof(bool), typeof(TabControl), new FrameworkPropertyMetadata(ValueBoxes.TrueBox, FrameworkPropertyMetadataOptions.Inherits)); public static void SetShowContextMenu(DependencyObject element, bool value) => element.SetValue(ShowContextMenuProperty, ValueBoxes.BooleanBox(value)); public static bool GetShowContextMenu(DependencyObject element) => (bool) element.GetValue(ShowContextMenuProperty); /// /// 是否显示上下文菜单 /// public bool ShowContextMenu { get => (bool) GetValue(ShowContextMenuProperty); set => SetValue(ShowContextMenuProperty, ValueBoxes.BooleanBox(value)); } /// /// 是否将标签填充 /// public static readonly DependencyProperty IsTabFillEnabledProperty = DependencyProperty.Register( "IsTabFillEnabled", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox)); /// /// 是否将标签填充 /// public bool IsTabFillEnabled { get => (bool) GetValue(IsTabFillEnabledProperty); set => SetValue(IsTabFillEnabledProperty, ValueBoxes.BooleanBox(value)); } /// /// 标签宽度 /// public static readonly DependencyProperty TabItemWidthProperty = DependencyProperty.Register( "TabItemWidth", typeof(double), typeof(TabControl), new PropertyMetadata(200.0)); /// /// 标签宽度 /// public double TabItemWidth { get => (double) GetValue(TabItemWidthProperty); set => SetValue(TabItemWidthProperty, value); } /// /// 标签高度 /// public static readonly DependencyProperty TabItemHeightProperty = DependencyProperty.Register( "TabItemHeight", typeof(double), typeof(TabControl), new PropertyMetadata(30.0)); /// /// 标签高度 /// public double TabItemHeight { get => (double) GetValue(TabItemHeightProperty); set => SetValue(TabItemHeightProperty, value); } /// /// 是否可以滚动 /// public static readonly DependencyProperty IsScrollableProperty = DependencyProperty.Register( "IsScrollable", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox)); /// /// 是否可以滚动 /// public bool IsScrollable { get => (bool) GetValue(IsScrollableProperty); set => SetValue(IsScrollableProperty, ValueBoxes.BooleanBox(value)); } /// /// 是否显示溢出按钮 /// public static readonly DependencyProperty ShowOverflowButtonProperty = DependencyProperty.Register( "ShowOverflowButton", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.TrueBox)); /// /// 是否显示溢出按钮 /// public bool ShowOverflowButton { get => (bool) GetValue(ShowOverflowButtonProperty); set => SetValue(ShowOverflowButtonProperty, ValueBoxes.BooleanBox(value)); } /// /// 是否显示滚动按钮 /// public static readonly DependencyProperty ShowScrollButtonProperty = DependencyProperty.Register( "ShowScrollButton", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox)); /// /// 是否显示滚动按钮 /// public bool ShowScrollButton { get => (bool) GetValue(ShowScrollButtonProperty); set => SetValue(ShowScrollButtonProperty, ValueBoxes.BooleanBox(value)); } /// /// 可见的标签数量 /// private int _itemShowCount; protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) { base.OnItemsChanged(e); if (HeaderPanel == null) { IsInternalAction = false; return; } UpdateOverflowButton(); if (IsInternalAction) { IsInternalAction = false; return; } if (e.Action == NotifyCollectionChangedAction.Add) { for (var i = 0; i < Items.Count; i++) { if (ItemContainerGenerator.ContainerFromIndex(i) is not TabItem item) return; item.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); item.TabPanel = HeaderPanel; } } _headerBorder?.InvalidateMeasure(); IsInternalAction = false; } public override void OnApplyTemplate() { if (_buttonOverflow != null) { if (_buttonOverflow.Menu != null) { _buttonOverflow.Menu.Closed -= Menu_Closed; _buttonOverflow.Menu = null; } _buttonOverflow.Click -= ButtonOverflow_Click; } if (_buttonScrollLeft != null) _buttonScrollLeft.Click -= ButtonScrollLeft_Click; if (_buttonScrollRight != null) _buttonScrollRight.Click -= ButtonScrollRight_Click; base.OnApplyTemplate(); HeaderPanel = GetTemplateChild(HeaderPanelKey) as TabPanel; if (IsTabFillEnabled) return; _buttonOverflow = GetTemplateChild(OverflowButtonKey) as ContextMenuToggleButton; _scrollViewerOverflow = GetTemplateChild(OverflowScrollviewer) as ScrollViewer; _buttonScrollLeft = GetTemplateChild(ScrollButtonLeft) as ButtonBase; _buttonScrollRight = GetTemplateChild(ScrollButtonRight) as ButtonBase; _headerBorder = GetTemplateChild(HeaderBorder) as Border; if (_buttonScrollLeft != null) _buttonScrollLeft.Click += ButtonScrollLeft_Click; if (_buttonScrollRight != null) _buttonScrollRight.Click += ButtonScrollRight_Click; if (_buttonOverflow != null) { var menu = new ContextMenu { Placement = PlacementMode.Bottom, PlacementTarget = _buttonOverflow }; menu.Closed += Menu_Closed; _buttonOverflow.Menu = menu; _buttonOverflow.Click += ButtonOverflow_Click; } } protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) { base.OnRenderSizeChanged(sizeInfo); UpdateOverflowButton(); } private void UpdateOverflowButton() { if (!IsTabFillEnabled) { _itemShowCount = (int) (ActualWidth / TabItemWidth); //_buttonOverflow?.Show(ShowOverflowButton && Items.Count > 0 && Items.Count >= _itemShowCount); } } private void Menu_Closed(object sender, RoutedEventArgs e) => _buttonOverflow.IsChecked = false; private void ButtonScrollRight_Click(object sender, RoutedEventArgs e) => _scrollViewerOverflow.ScrollToHorizontalOffsetWithAnimation(Math.Min( _scrollViewerOverflow.CurrentHorizontalOffset + TabItemWidth, _scrollViewerOverflow.ScrollableWidth)); private void ButtonScrollLeft_Click(object sender, RoutedEventArgs e) => _scrollViewerOverflow.ScrollToHorizontalOffsetWithAnimation(Math.Max( _scrollViewerOverflow.CurrentHorizontalOffset - TabItemWidth, 0)); private void ButtonOverflow_Click(object sender, RoutedEventArgs e) { if (_buttonOverflow.IsChecked == true) { _buttonOverflow.Menu.Items.Clear(); for (var i = 0; i < Items.Count; i++) { if (ItemContainerGenerator.ContainerFromIndex(i) is not TabItem item) continue; var menuItem = new MenuItem { HeaderStringFormat = ItemStringFormat, HeaderTemplate = ItemTemplate, HeaderTemplateSelector = ItemTemplateSelector, Header = item.Header, Width = TabItemWidth, IsChecked = item.IsSelected, IsCheckable = true, IsEnabled = item.IsEnabled }; menuItem.Click += delegate { _buttonOverflow.IsChecked = false; var list = GetActualList(); if (list == null) return; var actualItem = ItemContainerGenerator.ItemFromContainer(item); if (actualItem == null) return; var index = list.IndexOf(actualItem); if (index >= _itemShowCount) { list.Remove(actualItem); list.Insert(0, actualItem); HeaderPanel.SetValue(TabPanel.FluidMoveDurationPropertyKey, IsAnimationEnabled ? new Duration(TimeSpan.FromMilliseconds(200)) : new Duration(TimeSpan.FromMilliseconds(0))); HeaderPanel.ForceUpdate = true; HeaderPanel.Measure(new Size(HeaderPanel.DesiredSize.Width, ActualHeight)); HeaderPanel.ForceUpdate = false; SetCurrentValue(SelectedIndexProperty, ValueBoxes.Int0Box); } item.IsSelected = true; }; _buttonOverflow.Menu.Items.Add(menuItem); } } } internal double GetHorizontalOffset() => _scrollViewerOverflow?.CurrentHorizontalOffset ?? 0; internal void UpdateScroll() => _scrollViewerOverflow?.RaiseEvent(new MouseWheelEventArgs(Mouse.PrimaryDevice, Environment.TickCount, 0) { RoutedEvent = MouseWheelEvent }); internal void CloseAllItems() => CloseOtherItems(null); internal void CloseOtherItems(TabItem currentItem) { var actualItem = currentItem != null ? ItemContainerGenerator.ItemFromContainer(currentItem) : null; var list = GetActualList(); if (list == null) return; IsInternalAction = true; for (var i = 0; i < Items.Count; i++) { var item = list[i]; if (!Equals(item, actualItem) && item != null) { var argsClosing = new CancelRoutedEventArgs(TabItem.ClosingEvent, item); if (ItemContainerGenerator.ContainerFromItem(item) is not TabItem tabItem) continue; tabItem.RaiseEvent(argsClosing); if (argsClosing.Cancel) return; tabItem.RaiseEvent(new RoutedEventArgs(TabItem.ClosedEvent, item)); list.Remove(item); i--; } } SetCurrentValue(SelectedIndexProperty, Items.Count == 0 ? -1 : 0); } internal IList GetActualList() { IList list; if (ItemsSource != null) { list = ItemsSource as IList; } else { list = Items; } return list; } protected override bool IsItemItsOwnContainerOverride(object item) => item is TabItem; protected override DependencyObject GetContainerForItemOverride() => new TabItem(); }