using System; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using BPASmartClient.MilkWithTea.Control; using BPASmartClient.MilkWithTea.Data; using TabControl = BPASmartClient.MilkWithTea.Control.TabControl; namespace BPASmartClient.MilkWithTea.Control; public class TabItem : System.Windows.Controls.TabItem { /// /// 动画速度 /// private const int AnimationSpeed = 150; /// /// 选项卡是否处于拖动状态 /// private static bool ItemIsDragging; /// /// 选项卡是否等待被拖动 /// private bool _isWaiting; /// /// 拖动中的选项卡坐标 /// private Point _dragPoint; /// /// 鼠标按下时选项卡位置 /// private int _mouseDownIndex; /// /// 鼠标按下时选项卡横向偏移 /// private double _mouseDownOffsetX; /// /// 鼠标按下时的坐标 /// private Point _mouseDownPoint; /// /// 右侧可移动的最大值 /// private double _maxMoveRight; /// /// 左侧可移动的最大值 /// private double _maxMoveLeft; /// /// 选项卡宽度 /// public double ItemWidth { get; internal set; } /// /// 选项卡拖动等待距离(在鼠标移动了超过20个像素无关单位后,选项卡才开始被拖动) /// private const double WaitLength = 20; /// /// 选项卡是否处于拖动状态 /// private bool _isDragging; /// /// 选项卡是否已经被拖动 /// private bool _isDragged; /// /// 目标横向位移 /// internal double TargetOffsetX { get; set; } /// /// 当前编号 /// private int _currentIndex; /// /// 标签容器横向滚动距离 /// private double _scrollHorizontalOffset; private TabPanel _tabPanel; /// /// 标签容器 /// internal TabPanel TabPanel { get { if (_tabPanel == null && TabControlParent != null) { _tabPanel = TabControlParent.HeaderPanel; } return _tabPanel; } set => _tabPanel = value; } private TabControl TabControlParent => ItemsControl.ItemsControlFromItemContainer(this) as TabControl; /// /// 当前编号 /// internal int CurrentIndex { get => _currentIndex; set { if (_currentIndex == value || value < 0) return; var oldIndex = _currentIndex; _currentIndex = value; UpdateItemOffsetX(oldIndex); } } /// /// 是否显示关闭按钮 /// public static readonly DependencyProperty ShowCloseButtonProperty = TabControl.ShowCloseButtonProperty.AddOwner(typeof(TabItem)); /// /// 是否显示关闭按钮 /// public bool ShowCloseButton { get => (bool) GetValue(ShowCloseButtonProperty); set => SetValue(ShowCloseButtonProperty, ValueBoxes.BooleanBox(value)); } 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 static readonly DependencyProperty ShowContextMenuProperty = TabControl.ShowContextMenuProperty.AddOwner(typeof(TabItem), new FrameworkPropertyMetadata(OnShowContextMenuChanged)); private static void OnShowContextMenuChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var ctl = (TabItem) d; if (ctl.Menu != null) { var show = (bool) e.NewValue; ctl.Menu.IsEnabled = show; //ctl.Menu.Show(show); } } /// /// 是否显示上下文菜单 /// public bool ShowContextMenu { get => (bool) GetValue(ShowContextMenuProperty); set => SetValue(ShowContextMenuProperty, ValueBoxes.BooleanBox(value)); } 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 static readonly DependencyProperty MenuProperty = DependencyProperty.Register( "Menu", typeof(ContextMenu), typeof(TabItem), new PropertyMetadata(default(ContextMenu), OnMenuChanged)); private static void OnMenuChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var ctl = (TabItem) d; //ctl.OnMenuChanged(e.NewValue as ContextMenu); } //private void OnMenuChanged(ContextMenu menu) //{ // if (IsLoaded && menu != null) // { // var parent = TabControlParent; // if (parent == null) return; // var item = parent.ItemContainerGenerator.ItemFromContainer(this); // menu.DataContext = item; // menu.SetBinding(IsEnabledProperty, new Binding(ShowContextMenuProperty.Name) // { // Source = this // }); // menu.SetBinding(VisibilityProperty, new Binding(ShowContextMenuProperty.Name) // { // Source = this, // Converter = GetResourceInternal(ResourceToken.Boolean2VisibilityConverter) // }); // } //} public ContextMenu Menu { get => (ContextMenu) GetValue(MenuProperty); set => SetValue(MenuProperty, value); } /// /// 更新选项卡横向偏移 /// /// private void UpdateItemOffsetX(int oldIndex) { if (!_isDragging || CurrentIndex >= TabPanel.ItemDic.Count) { return; } var moveItem = TabPanel.ItemDic[CurrentIndex]; moveItem.CurrentIndex -= CurrentIndex - oldIndex; var offsetX = moveItem.TargetOffsetX; var resultX = offsetX + (oldIndex - CurrentIndex) * ItemWidth; TabPanel.ItemDic[CurrentIndex] = this; TabPanel.ItemDic[moveItem.CurrentIndex] = moveItem; moveItem.CreateAnimation(offsetX, resultX); } public TabItem() { //CommandBindings.Add(new CommandBinding(ControlCommands.Close, (s, e) => Close())); //CommandBindings.Add(new CommandBinding(ControlCommands.CloseAll, // (s, e) => { TabControlParent.CloseAllItems(); })); //CommandBindings.Add(new CommandBinding(ControlCommands.CloseOther, // (s, e) => { TabControlParent.CloseOtherItems(this); })); //Loaded += (s, e) => OnMenuChanged(Menu); } protected override void OnMouseRightButtonDown(MouseButtonEventArgs e) { base.OnMouseRightButtonDown(e); if (VisualTreeHelper.HitTest(this, e.GetPosition(this)) == null) return; IsSelected = true; Focus(); } protected override void OnHeaderChanged(object oldHeader, object newHeader) { base.OnHeaderChanged(oldHeader, newHeader); if (TabPanel != null) { TabPanel.ForceUpdate = true; InvalidateMeasure(); TabPanel.ForceUpdate = true; } } internal void Close() { var parent = TabControlParent; if (parent == null) return; var item = parent.ItemContainerGenerator.ItemFromContainer(this); var argsClosing = new CancelRoutedEventArgs(ClosingEvent, item); RaiseEvent(argsClosing); if (argsClosing.Cancel) return; TabPanel.SetValue(TabPanel.FluidMoveDurationPropertyKey, parent.IsAnimationEnabled ? new Duration(TimeSpan.FromMilliseconds(200)) : new Duration(TimeSpan.FromMilliseconds(1))); parent.IsInternalAction = true; RaiseEvent(new RoutedEventArgs(ClosedEvent, item)); var list = parent.GetActualList(); list?.Remove(item); } protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { base.OnMouseLeftButtonDown(e); if (VisualTreeHelper.HitTest(this, e.GetPosition(this)) == null) return; var parent = TabControlParent; if (parent == null) return; if (parent.IsDraggable && !ItemIsDragging && !_isDragging) { parent.UpdateScroll(); TabPanel.SetValue(TabPanel.FluidMoveDurationPropertyKey, new Duration(TimeSpan.FromSeconds(0))); _mouseDownOffsetX = RenderTransform.Value.OffsetX; _scrollHorizontalOffset = parent.GetHorizontalOffset(); var mx = TranslatePoint(new Point(), parent).X + _scrollHorizontalOffset; _mouseDownIndex = CalLocationIndex(mx); var subIndex = _mouseDownIndex - CalLocationIndex(_scrollHorizontalOffset); _maxMoveLeft = -subIndex * ItemWidth; _maxMoveRight = parent.ActualWidth - ActualWidth + _maxMoveLeft; _isDragging = true; ItemIsDragging = true; _isWaiting = true; _dragPoint = e.GetPosition(parent); _dragPoint = new Point(_dragPoint.X + _scrollHorizontalOffset, _dragPoint.Y); _mouseDownPoint = _dragPoint; CaptureMouse(); } } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (ItemIsDragging && _isDragging) { var parent = TabControlParent; if (parent == null) return; var subX = TranslatePoint(new Point(), parent).X + _scrollHorizontalOffset; CurrentIndex = CalLocationIndex(subX); var p = e.GetPosition(parent); p = new Point(p.X + _scrollHorizontalOffset, p.Y); var subLeft = p.X - _dragPoint.X; var totalLeft = p.X - _mouseDownPoint.X; if (Math.Abs(subLeft) <= WaitLength && _isWaiting) return; _isWaiting = false; _isDragged = true; var left = subLeft + RenderTransform.Value.OffsetX; if (totalLeft < _maxMoveLeft) { left = _maxMoveLeft + _mouseDownOffsetX; } else if (totalLeft > _maxMoveRight) { left = _maxMoveRight + _mouseDownOffsetX; } var t = new TranslateTransform(left, 0); RenderTransform = t; _dragPoint = p; } } protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) { base.OnMouseLeftButtonUp(e); ReleaseMouseCapture(); if (_isDragged) { var parent = TabControlParent; if (parent == null) return; var subX = TranslatePoint(new Point(), parent).X + _scrollHorizontalOffset; var index = CalLocationIndex(subX); var left = index * ItemWidth; var offsetX = RenderTransform.Value.OffsetX; CreateAnimation(offsetX, offsetX - subX + left, index); } _isDragging = false; ItemIsDragging = false; _isDragged = false; } protected override void OnMouseDown(MouseButtonEventArgs e) { if (e.ChangedButton == MouseButton.Middle && e.ButtonState == MouseButtonState.Pressed) { if (ShowCloseButton || ShowContextMenu) { Close(); } } } /// /// 创建动画 /// internal void CreateAnimation(double offsetX, double resultX, int index = -1) { var parent = TabControlParent; void AnimationCompleted() { RenderTransform = new TranslateTransform(resultX, 0); if (index == -1) return; var list = parent.GetActualList(); if (list == null) return; var item = parent.ItemContainerGenerator.ItemFromContainer(this); if (item == null) return; TabPanel.CanUpdate = false; parent.IsInternalAction = true; list.Remove(item); parent.IsInternalAction = true; list.Insert(index, item); _tabPanel.SetValue(TabPanel.FluidMoveDurationPropertyKey, new Duration(TimeSpan.FromMilliseconds(0))); TabPanel.CanUpdate = true; TabPanel.ForceUpdate = true; TabPanel.Measure(new Size(TabPanel.DesiredSize.Width, ActualHeight)); TabPanel.ForceUpdate = false; Focus(); IsSelected = true; if (!IsMouseCaptured) { parent.SetCurrentValue(Selector.SelectedIndexProperty, _currentIndex); } } TargetOffsetX = resultX; if (!parent.IsAnimationEnabled) { AnimationCompleted(); return; } var animation = CreateAnimation(resultX, AnimationSpeed); animation.FillBehavior = FillBehavior.Stop; animation.Completed += (s1, e1) => AnimationCompleted(); var f = new TranslateTransform(offsetX, 0); RenderTransform = f; f.BeginAnimation(TranslateTransform.XProperty, animation, HandoffBehavior.Compose); } /// /// 创建一个Double动画 /// /// /// /// public static DoubleAnimation CreateAnimation(double toValue, double milliseconds = 200) { return new(toValue, new Duration(TimeSpan.FromMilliseconds(milliseconds))) { EasingFunction = new PowerEase { EasingMode = EasingMode.EaseInOut } }; } /// /// 计算选项卡当前合适的位置编号 /// /// /// private int CalLocationIndex(double left) { if (_isWaiting) { return CurrentIndex; } var maxIndex = TabControlParent.Items.Count - 1; var div = (int) (left / ItemWidth); var rest = left % ItemWidth; var result = rest / ItemWidth > .5 ? div + 1 : div; return result > maxIndex ? maxIndex : result; } public static readonly RoutedEvent ClosingEvent = EventManager.RegisterRoutedEvent("Closing", RoutingStrategy.Bubble, typeof(EventHandler), typeof(TabItem)); public event EventHandler Closing { add => AddHandler(ClosingEvent, value); remove => RemoveHandler(ClosingEvent, value); } public static readonly RoutedEvent ClosedEvent = EventManager.RegisterRoutedEvent("Closed", RoutingStrategy.Bubble, typeof(EventHandler), typeof(TabItem)); public event EventHandler Closed { add => AddHandler(ClosedEvent, value); remove => RemoveHandler(ClosedEvent, value); } }