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);
}
}