using System; using System.Collections; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using HandyControl.Interactivity; using HandyControl.Tools; namespace HandyControl.Controls { 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; /// /// 标签容器 /// internal TabPanel TabPanel { get; set; } /// /// 当前编号 /// internal int CurrentIndex { get => _currentIndex; set { if (_currentIndex == value || value < 0) return; var oldIndex = _currentIndex; _currentIndex = value; UpdateItemOffsetX(oldIndex); } } /// /// 更新选项卡横向偏移 /// /// private void UpdateItemOffsetX(int oldIndex) { if (!_isDragging) 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, Close)); CommandBindings.Add(new CommandBinding(ControlCommands.CloseAll, (s, e) => { TabControlParent.IsInternalAction = true; TabControlParent.Items.Clear(); })); CommandBindings.Add(new CommandBinding(ControlCommands.CloseOther, (s, e) => { TabControlParent.IsInternalAction = true; var enumerator = ((IEnumerable)TabControlParent.Items).GetEnumerator(); while (enumerator.MoveNext()) { var item = enumerator.Current; if (!Equals(item) && item != null) { TabControlParent.Items.Remove(item); enumerator = ((IEnumerable)TabControlParent.Items).GetEnumerator(); } } })); } private TabControl _tabControlParent; private TabControl TabControlParent => _tabControlParent ?? (_tabControlParent = ItemsControl.ItemsControlFromItemContainer(this) as TabControl); protected override void OnMouseRightButtonDown(MouseButtonEventArgs e) { base.OnMouseRightButtonDown(e); IsSelected = true; Focus(); } /// /// 关闭 /// private void Close(object sender, RoutedEventArgs e) { if (TabControlParent.IsEnableAnimation) { TabPanel.ClearValue(TabPanel.FluidMoveDurationProperty); } else { TabPanel.FluidMoveDuration = new Duration(TimeSpan.FromSeconds(0)); } TabControlParent.IsInternalAction = true; TabControlParent.Items.Remove(this); } protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { base.OnMouseLeftButtonDown(e); if (TabControlParent.IsDraggable && !ItemIsDragging && !_isDragging) { TabPanel.FluidMoveDuration = new Duration(TimeSpan.FromSeconds(0)); _mouseDownOffsetX = RenderTransform.Value.OffsetX; var mx = TranslatePoint(new Point(), TabControlParent).X; _mouseDownIndex = CalLocationIndex(mx); _maxMoveLeft = -_mouseDownIndex * ItemWidth; _maxMoveRight = TabControlParent.ActualWidth - ActualWidth + _maxMoveLeft; _isDragging = true; ItemIsDragging = true; _isWaiting = true; _dragPoint = e.GetPosition(TabControlParent); _mouseDownPoint = _dragPoint; CaptureMouse(); } } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (ItemIsDragging && _isDragging) { var subX = TranslatePoint(new Point(), TabControlParent).X; CurrentIndex = CalLocationIndex(subX); var p = e.GetPosition(TabControlParent); 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 subX = TranslatePoint(new Point(), TabControlParent).X; 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; } /// /// 创建动画 /// internal void CreateAnimation(double offsetX, double resultX, int index = -1) { void AnimationCompleted() { RenderTransform = new TranslateTransform(resultX, 0); if (index == -1) return; var parent = TabControlParent; TabPanel.CanUpdate = false; parent.IsInternalAction = true; parent.Items.Remove(this); parent.IsInternalAction = true; parent.Items.Insert(index, this); TabPanel.CanUpdate = true; TabPanel.ForceUpdate = true; TabPanel.Measure(new Size(TabPanel.DesiredSize.Width, ActualHeight)); TabPanel.ForceUpdate = false; Focus(); IsSelected = true; } TargetOffsetX = resultX; if (!TabControlParent.IsEnableAnimation) { AnimationCompleted(); return; } var animation = AnimationHelper.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); } /// /// 计算选项卡当前合适的位置编号 /// /// /// private int CalLocationIndex(double left) { 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; } } }