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 AIStudio.Wpf.ADiagram.Controls { [TemplatePart(Name = OverflowButtonKey, Type = typeof(ContextMenuToggleButton))] [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(System.Windows.Controls.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 System.Windows.Controls.Border _headerBorder; /// /// 是否为内部操作 /// internal bool IsInternalAction; /// /// 是否启用动画 /// public static readonly DependencyProperty IsAnimationEnabledProperty = DependencyProperty.Register( "IsAnimationEnabled", typeof(bool), typeof(TabControl), new PropertyMetadata(false)); /// /// 是否启用动画 /// public bool IsAnimationEnabled { get => (bool)GetValue(IsAnimationEnabledProperty); set => SetValue(IsAnimationEnabledProperty, value); } /// /// 是否可以拖动 /// public static readonly DependencyProperty IsDraggableProperty = DependencyProperty.Register( "IsDraggable", typeof(bool), typeof(TabControl), new PropertyMetadata(false)); /// /// 是否可以拖动 /// public bool IsDraggable { get => (bool)GetValue(IsDraggableProperty); set => SetValue(IsDraggableProperty, value); } /// /// 是否显示关闭按钮 /// public static readonly DependencyProperty ShowCloseButtonProperty = DependencyProperty.RegisterAttached( "ShowCloseButton", typeof(bool), typeof(TabControl), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits)); public static void SetShowCloseButton(DependencyObject element, bool value) => element.SetValue(ShowCloseButtonProperty, value); public static bool GetShowCloseButton(DependencyObject element) => (bool) element.GetValue(ShowCloseButtonProperty); /// /// 是否显示关闭按钮 /// public bool ShowCloseButton { get => (bool)GetValue(ShowCloseButtonProperty); set => SetValue(ShowCloseButtonProperty, value); } /// /// 是否显示上下文菜单 /// public static readonly DependencyProperty ShowContextMenuProperty = DependencyProperty.RegisterAttached( "ShowContextMenu", typeof(bool), typeof(TabControl), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.Inherits)); public static void SetShowContextMenu(DependencyObject element, bool value) => element.SetValue(ShowContextMenuProperty, value); public static bool GetShowContextMenu(DependencyObject element) => (bool) element.GetValue(ShowContextMenuProperty); /// /// 是否显示上下文菜单 /// public bool ShowContextMenu { get => (bool)GetValue(ShowContextMenuProperty); set => SetValue(ShowContextMenuProperty, value); } /// /// 是否将标签填充 /// public static readonly DependencyProperty IsTabFillEnabledProperty = DependencyProperty.Register( "IsTabFillEnabled", typeof(bool), typeof(TabControl), new PropertyMetadata(false)); /// /// 是否将标签填充 /// public bool IsTabFillEnabled { get => (bool)GetValue(IsTabFillEnabledProperty); set => SetValue(IsTabFillEnabledProperty, 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(false)); /// /// 是否可以滚动 /// public bool IsScrollable { get => (bool) GetValue(IsScrollableProperty); set => SetValue(IsScrollableProperty, value); } /// /// 是否显示溢出按钮 /// public static readonly DependencyProperty ShowOverflowButtonProperty = DependencyProperty.Register( "ShowOverflowButton", typeof(bool), typeof(TabControl), new PropertyMetadata(true)); /// /// 是否显示溢出按钮 /// public bool ShowOverflowButton { get => (bool) GetValue(ShowOverflowButtonProperty); set => SetValue(ShowOverflowButtonProperty, value); } /// /// 是否显示滚动按钮 /// public static readonly DependencyProperty ShowScrollButtonProperty = DependencyProperty.Register( "ShowScrollButton", typeof(bool), typeof(TabControl), new PropertyMetadata(false)); /// /// 是否显示滚动按钮 /// public bool ShowScrollButton { get => (bool) GetValue(ShowScrollButtonProperty); set => SetValue(ShowScrollButtonProperty, 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 (IsAnimationEnabled) { HeaderPanel.SetCurrentValue(TabPanel.FluidMoveDurationProperty, new Duration(TimeSpan.FromMilliseconds(200))); } else { HeaderPanel.FluidMoveDuration = new Duration(TimeSpan.FromSeconds(0)); } if (e.Action == NotifyCollectionChangedAction.Add) { for (var i = 0; i < Items.Count; i++) { if (!(ItemContainerGenerator.ContainerFromIndex(i) is 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 System.Windows.Controls.Border; if (_buttonScrollLeft != null) _buttonScrollLeft.Click += ButtonScrollLeft_Click; if (_buttonScrollRight != null) _buttonScrollRight.Click += ButtonScrollRight_Click; if (_buttonOverflow != null) { var menu = new System.Windows.Controls.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); if (_buttonOverflow != null) { _buttonOverflow.Visibility = (ShowOverflowButton && Items.Count > 0 && Items.Count >= _itemShowCount) ? Visibility.Visible : Visibility.Collapsed; } } } private void Menu_Closed(object sender, RoutedEventArgs e) => _buttonOverflow.IsChecked = false; private void ButtonScrollRight_Click(object sender, RoutedEventArgs e) => _scrollViewerOverflow.ScrollToHorizontalOffsetInternal(Math.Min( _scrollViewerOverflow.CurrentHorizontalOffset + TabItemWidth, _scrollViewerOverflow.ScrollableWidth)); private void ButtonScrollLeft_Click(object sender, RoutedEventArgs e) => _scrollViewerOverflow.ScrollToHorizontalOffsetInternal(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 TabItem item)) continue; var menuItem = new System.Windows.Controls.MenuItem { HeaderStringFormat = ItemStringFormat, HeaderTemplate = ItemTemplate, HeaderTemplateSelector = ItemTemplateSelector, Header = item.Header, Width = TabItemWidth, IsChecked = item.IsSelected, IsCheckable = true }; 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); if (IsAnimationEnabled) { HeaderPanel.SetCurrentValue(TabPanel.FluidMoveDurationProperty, new Duration(TimeSpan.FromMilliseconds(200))); } else { HeaderPanel.FluidMoveDuration = new Duration(TimeSpan.FromSeconds(0)); } HeaderPanel.ForceUpdate = true; HeaderPanel.Measure(new Size(HeaderPanel.DesiredSize.Width, ActualHeight)); HeaderPanel.ForceUpdate = false; SetCurrentValue(SelectedIndexProperty, 0); } 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 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(); } }