From 53c9bd7836ad9e3515551fba22e2fcc15b3d949b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Montilla=20Kry=C5=A1tof?= Date: Tue, 7 May 2019 19:06:32 +0200 Subject: [PATCH 1/3] Scrolling Tabs in TabControl, Better horizontal scrolling --- .../Controls/Other/ScrollViewer.cs | 25 ++++-- .../Controls/TabControl/TabControl.cs | 54 +++++++++++- .../Themes/Styles/TabControl.xaml | 83 +++++++++++++++---- 3 files changed, 138 insertions(+), 24 deletions(-) diff --git a/src/Shared/HandyControl_Shared/Controls/Other/ScrollViewer.cs b/src/Shared/HandyControl_Shared/Controls/Other/ScrollViewer.cs index 8fc34154..39cb98da 100644 --- a/src/Shared/HandyControl_Shared/Controls/Other/ScrollViewer.cs +++ b/src/Shared/HandyControl_Shared/Controls/Other/ScrollViewer.cs @@ -17,6 +17,8 @@ namespace HandyControl.Controls private bool _isRunning; + private Storyboard _scrollingAnimation = new Storyboard(); + /// /// 滚动方向 /// @@ -28,7 +30,7 @@ namespace HandyControl.Controls /// public Orientation Orientation { - get => (Orientation) GetValue(OrientationProperty); + get => (Orientation)GetValue(OrientationProperty); set => SetValue(OrientationProperty, value); } @@ -43,7 +45,7 @@ namespace HandyControl.Controls /// public bool CanMouseWheel { - get => (bool) GetValue(CanMouseWheelProperty); + get => (bool)GetValue(CanMouseWheelProperty); set => SetValue(CanMouseWheelProperty, value); } @@ -101,7 +103,7 @@ namespace HandyControl.Controls } internal void ScrollToVerticalOffsetInternal(double offset, double milliseconds = 500) - { + { var animation = AnimationHelper.CreateAnimation(offset, milliseconds); animation.EasingFunction = new CubicEase { @@ -114,7 +116,11 @@ namespace HandyControl.Controls _isRunning = false; }; _isRunning = true; - BeginAnimation(CurrentVerticalOffsetProperty, animation); + + _scrollingAnimation.Children.Add(animation); + BeginStoryboard(_scrollingAnimation); + + //BeginAnimation(CurrentVerticalOffsetProperty, animation); } internal void ScrollToHorizontalOffsetInternal(double offset, double milliseconds = 500) @@ -131,7 +137,8 @@ namespace HandyControl.Controls _isRunning = false; }; _isRunning = true; - BeginAnimation(CurrentHorizontalOffsetProperty, animation); + + BeginAnimation(CurrentHorizontalOffsetProperty, animation, HandoffBehavior.Compose); } protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) => @@ -150,7 +157,7 @@ namespace HandyControl.Controls public static bool GetIsEnableInertia(DependencyObject element) { - return (bool) element.GetValue(IsEnableInertiaProperty); + return (bool)element.GetValue(IsEnableInertiaProperty); } /// @@ -184,7 +191,7 @@ namespace HandyControl.Controls public static bool GetIsPenetrating(DependencyObject element) { - return (bool) element.GetValue(IsPenetratingProperty); + return (bool)element.GetValue(IsPenetratingProperty); } /// @@ -207,7 +214,7 @@ namespace HandyControl.Controls private double CurrentVerticalOffset { // ReSharper disable once UnusedMember.Local - get => (double) GetValue(CurrentVerticalOffsetProperty); + get => (double)GetValue(CurrentVerticalOffsetProperty); set => SetValue(CurrentVerticalOffsetProperty, value); } @@ -230,7 +237,7 @@ namespace HandyControl.Controls /// public double CurrentHorizontalOffset { - get => (double) GetValue(CurrentHorizontalOffsetProperty); + get => (double)GetValue(CurrentHorizontalOffsetProperty); set => SetValue(CurrentHorizontalOffsetProperty, value); } } diff --git a/src/Shared/HandyControl_Shared/Controls/TabControl/TabControl.cs b/src/Shared/HandyControl_Shared/Controls/TabControl/TabControl.cs index 6967947d..15e347d9 100644 --- a/src/Shared/HandyControl_Shared/Controls/TabControl/TabControl.cs +++ b/src/Shared/HandyControl_Shared/Controls/TabControl/TabControl.cs @@ -10,16 +10,31 @@ namespace HandyControl.Controls { [TemplatePart(Name = OverflowButtonKey, Type = typeof(ContextMenuToggleButton))] [TemplatePart(Name = HeaderPanelKey, Type = typeof(TabPanel))] + [TemplatePart(Name = OverflowScrollviewer, Type = typeof(ScrollViewer))] + [TemplatePart(Name = OverflowButtonLeft, Type = typeof(Button))] + [TemplatePart(Name = OverflowButtonRight, Type = typeof(Button))] 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 OverflowButtonLeft = "PART_OverflowButtonLeft"; + + private const string OverflowButtonRight = "PART_OverflowButtonRight"; + private ContextMenuToggleButton _buttonOverflow; private TabPanel _headerPanel; + private ScrollViewer _scrollviewerOverflow; + + private Button _buttonOverflowLeft; + + private Button _buttonOverflowRight; + /// /// 是否为内部操作 /// @@ -115,6 +130,16 @@ namespace HandyControl.Controls set => SetValue(TabItemHeightProperty, value); } + //TODO: Implement this + public bool IsScrollable + { + get { return (bool)GetValue(IsScrollableProperty); } + set { SetValue(IsScrollableProperty, value); } + } + + public static readonly DependencyProperty IsScrollableProperty = + DependencyProperty.Register("IsScrollable", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox)); + /// /// 可见的标签数量 /// @@ -133,7 +158,7 @@ namespace HandyControl.Controls if (!IsEnableTabFill) { _itemShowCount = (int)(ActualWidth / TabItemWidth); - _buttonOverflow.Show(Items.Count > 0 && Items.Count >= _itemShowCount); + _buttonOverflow.Show(Items.Count > 0 && Items.Count >= _itemShowCount); } if (IsInternalAction) @@ -169,6 +194,33 @@ namespace HandyControl.Controls if (IsEnableTabFill) return; _buttonOverflow = Template.FindName(OverflowButtonKey, this) as ContextMenuToggleButton; + _scrollviewerOverflow = Template.FindName(OverflowScrollviewer, this) as ScrollViewer; + _buttonOverflowLeft = Template.FindName(OverflowButtonLeft, this) as Button; + _buttonOverflowRight = Template.FindName(OverflowButtonRight, this) as Button; + + _buttonOverflowLeft.Click += (s, e) => + { + var currentOffset = _scrollviewerOverflow.CurrentHorizontalOffset; + var modulo = (currentOffset % TabItemWidth); + var offset = currentOffset - (modulo == 0 ? TabItemWidth : modulo); + + if (offset >= 0) + { + _scrollviewerOverflow.ScrollToHorizontalOffsetInternal(offset); + } + }; + + _buttonOverflowRight.Click += (s, e) => + { + var currentOffset = _scrollviewerOverflow.CurrentHorizontalOffset; + var offset = currentOffset + (TabItemWidth - (currentOffset % TabItemWidth)); + + if(offset <= _scrollviewerOverflow.ScrollableWidth) + { + _scrollviewerOverflow.ScrollToHorizontalOffsetInternal(offset); + } + }; + if (_buttonOverflow != null) { _itemShowCount = (int)(ActualWidth / TabItemWidth); diff --git a/src/Shared/HandyControl_Shared/Themes/Styles/TabControl.xaml b/src/Shared/HandyControl_Shared/Themes/Styles/TabControl.xaml index 870ab401..583d6614 100644 --- a/src/Shared/HandyControl_Shared/Themes/Styles/TabControl.xaml +++ b/src/Shared/HandyControl_Shared/Themes/Styles/TabControl.xaml @@ -91,23 +91,78 @@ - - - - - - - - - - + + + + + + + + + + + + + + + public Orientation Orientation { - get => (Orientation)GetValue(OrientationProperty); + get => (Orientation) GetValue(OrientationProperty); set => SetValue(OrientationProperty, value); } @@ -45,7 +43,7 @@ namespace HandyControl.Controls /// public bool CanMouseWheel { - get => (bool)GetValue(CanMouseWheelProperty); + get => (bool) GetValue(CanMouseWheelProperty); set => SetValue(CanMouseWheelProperty, value); } @@ -103,7 +101,7 @@ namespace HandyControl.Controls } internal void ScrollToVerticalOffsetInternal(double offset, double milliseconds = 500) - { + { var animation = AnimationHelper.CreateAnimation(offset, milliseconds); animation.EasingFunction = new CubicEase { @@ -117,10 +115,7 @@ namespace HandyControl.Controls }; _isRunning = true; - _scrollingAnimation.Children.Add(animation); - BeginStoryboard(_scrollingAnimation); - - //BeginAnimation(CurrentVerticalOffsetProperty, animation); + BeginAnimation(CurrentVerticalOffsetProperty, animation, HandoffBehavior.Compose); } internal void ScrollToHorizontalOffsetInternal(double offset, double milliseconds = 500) @@ -165,7 +160,7 @@ namespace HandyControl.Controls /// public bool IsEnableInertia { - get => (bool)GetValue(IsEnableInertiaProperty); + get => (bool) GetValue(IsEnableInertiaProperty); set => SetValue(IsEnableInertiaProperty, value); } @@ -180,7 +175,7 @@ namespace HandyControl.Controls /// public bool IsPenetrating { - get => (bool)GetValue(IsPenetratingProperty); + get => (bool) GetValue(IsPenetratingProperty); set => SetValue(IsPenetratingProperty, value); } @@ -191,7 +186,7 @@ namespace HandyControl.Controls public static bool GetIsPenetrating(DependencyObject element) { - return (bool)element.GetValue(IsPenetratingProperty); + return (bool) element.GetValue(IsPenetratingProperty); } /// @@ -214,7 +209,7 @@ namespace HandyControl.Controls private double CurrentVerticalOffset { // ReSharper disable once UnusedMember.Local - get => (double)GetValue(CurrentVerticalOffsetProperty); + get => (double) GetValue(CurrentVerticalOffsetProperty); set => SetValue(CurrentVerticalOffsetProperty, value); } @@ -237,7 +232,7 @@ namespace HandyControl.Controls /// public double CurrentHorizontalOffset { - get => (double)GetValue(CurrentHorizontalOffsetProperty); + get => (double) GetValue(CurrentHorizontalOffsetProperty); set => SetValue(CurrentHorizontalOffsetProperty, value); } } diff --git a/src/Shared/HandyControl_Shared/Controls/TabControl/TabControl.cs b/src/Shared/HandyControl_Shared/Controls/TabControl/TabControl.cs index 15e347d9..aac5c5be 100644 --- a/src/Shared/HandyControl_Shared/Controls/TabControl/TabControl.cs +++ b/src/Shared/HandyControl_Shared/Controls/TabControl/TabControl.cs @@ -11,8 +11,8 @@ namespace HandyControl.Controls [TemplatePart(Name = OverflowButtonKey, Type = typeof(ContextMenuToggleButton))] [TemplatePart(Name = HeaderPanelKey, Type = typeof(TabPanel))] [TemplatePart(Name = OverflowScrollviewer, Type = typeof(ScrollViewer))] - [TemplatePart(Name = OverflowButtonLeft, Type = typeof(Button))] - [TemplatePart(Name = OverflowButtonRight, Type = typeof(Button))] + [TemplatePart(Name = ScrollButtonLeft, Type = typeof(Button))] + [TemplatePart(Name = ScrollButtonRight, Type = typeof(Button))] public class TabControl : System.Windows.Controls.TabControl { private const string OverflowButtonKey = "PART_OverflowButton"; @@ -21,9 +21,9 @@ namespace HandyControl.Controls private const string OverflowScrollviewer = "PART_OverflowScrollviewer"; - private const string OverflowButtonLeft = "PART_OverflowButtonLeft"; + private const string ScrollButtonLeft = "PART_ScrollButtonLeft"; - private const string OverflowButtonRight = "PART_OverflowButtonRight"; + private const string ScrollButtonRight = "PART_ScrollButtonRight"; private ContextMenuToggleButton _buttonOverflow; @@ -31,9 +31,9 @@ namespace HandyControl.Controls private ScrollViewer _scrollviewerOverflow; - private Button _buttonOverflowLeft; + private Button _buttonScrollLeft; - private Button _buttonOverflowRight; + private Button _buttonScrollRight; /// /// 是否为内部操作 @@ -130,16 +130,21 @@ namespace HandyControl.Controls set => SetValue(TabItemHeightProperty, value); } - //TODO: Implement this + /// + /// 是否可以滚动 + /// + public static readonly DependencyProperty IsScrollableProperty = DependencyProperty.Register( + "IsScrollable", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox)); + + /// + /// 是否可以滚动 + /// public bool IsScrollable { - get { return (bool)GetValue(IsScrollableProperty); } - set { SetValue(IsScrollableProperty, value); } + get => (bool) GetValue(IsScrollableProperty); + set => SetValue(IsScrollableProperty, value); } - public static readonly DependencyProperty IsScrollableProperty = - DependencyProperty.Register("IsScrollable", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox)); - /// /// 可见的标签数量 /// @@ -188,6 +193,20 @@ namespace HandyControl.Controls 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 = Template.FindName(HeaderPanelKey, this) as TabPanel; @@ -195,31 +214,11 @@ namespace HandyControl.Controls _buttonOverflow = Template.FindName(OverflowButtonKey, this) as ContextMenuToggleButton; _scrollviewerOverflow = Template.FindName(OverflowScrollviewer, this) as ScrollViewer; - _buttonOverflowLeft = Template.FindName(OverflowButtonLeft, this) as Button; - _buttonOverflowRight = Template.FindName(OverflowButtonRight, this) as Button; + _buttonScrollLeft = Template.FindName(ScrollButtonLeft, this) as Button; + _buttonScrollRight = Template.FindName(ScrollButtonRight, this) as Button; - _buttonOverflowLeft.Click += (s, e) => - { - var currentOffset = _scrollviewerOverflow.CurrentHorizontalOffset; - var modulo = (currentOffset % TabItemWidth); - var offset = currentOffset - (modulo == 0 ? TabItemWidth : modulo); - - if (offset >= 0) - { - _scrollviewerOverflow.ScrollToHorizontalOffsetInternal(offset); - } - }; - - _buttonOverflowRight.Click += (s, e) => - { - var currentOffset = _scrollviewerOverflow.CurrentHorizontalOffset; - var offset = currentOffset + (TabItemWidth - (currentOffset % TabItemWidth)); - - if(offset <= _scrollviewerOverflow.ScrollableWidth) - { - _scrollviewerOverflow.ScrollToHorizontalOffsetInternal(offset); - } - }; + if (_buttonScrollLeft != null) _buttonScrollLeft.Click += ButtonScrollLeft_Click; + if (_buttonScrollRight != null) _buttonScrollRight.Click += ButtonScrollRight_Click; if (_buttonOverflow != null) { @@ -231,48 +230,78 @@ namespace HandyControl.Controls Placement = PlacementMode.Bottom, PlacementTarget = _buttonOverflow }; - menu.Closed += (s, e) => _buttonOverflow.IsChecked = false; + menu.Closed += Menu_Closed; _buttonOverflow.Menu = menu; - _buttonOverflow.Click += (s, e) => + _buttonOverflow.Click += ButtonOverflow_Click; + } + } + + private void Menu_Closed(object sender, RoutedEventArgs e) + { + _buttonOverflow.IsChecked = false; + } + + private void ButtonScrollRight_Click(object sender, RoutedEventArgs e) + { + var currentOffset = _scrollviewerOverflow.CurrentHorizontalOffset; + var offset = currentOffset + (TabItemWidth - currentOffset % TabItemWidth); + + if (offset <= _scrollviewerOverflow.ScrollableWidth) + { + _scrollviewerOverflow.ScrollToHorizontalOffsetInternal(offset); + } + } + + private void ButtonScrollLeft_Click(object sender, RoutedEventArgs e) + { + var currentOffset = _scrollviewerOverflow.CurrentHorizontalOffset; + var modulo = currentOffset % TabItemWidth; + var offset = currentOffset - (modulo < 1e-6 ? TabItemWidth : modulo); + + if (offset >= 0) + { + _scrollviewerOverflow.ScrollToHorizontalOffsetInternal(offset); + } + } + + private void ButtonOverflow_Click(object sender, RoutedEventArgs e) + { + if (_buttonOverflow.IsChecked == true) + { + _buttonOverflow.Menu.Items.Clear(); + foreach (TabItem item in Items) { - if (_buttonOverflow.IsChecked == true) + var menuItem = new MenuItem { - _buttonOverflow.Menu.Items.Clear(); - foreach (TabItem item in Items) + Header = item.Header, + Width = TabItemWidth, + IsChecked = item.IsSelected, + IsCheckable = true + }; + menuItem.Click += delegate + { + _buttonOverflow.IsChecked = false; + var index = Items.IndexOf(item); + if (index >= _itemShowCount) { - var menuItem = new MenuItem + Items.Remove(item); + Items.Insert(0, item); + if (IsEnableAnimation) { - Header = item.Header, - Width = TabItemWidth, - IsChecked = item.IsSelected, - IsCheckable = true - }; - menuItem.Click += delegate + _headerPanel.ClearValue(TabPanel.FluidMoveDurationProperty); + } + else { - _buttonOverflow.IsChecked = false; - var index = Items.IndexOf(item); - if (index >= _itemShowCount) - { - Items.Remove(item); - Items.Insert(0, item); - if (IsEnableAnimation) - { - _headerPanel.ClearValue(TabPanel.FluidMoveDurationProperty); - } - else - { - _headerPanel.FluidMoveDuration = new Duration(TimeSpan.FromSeconds(0)); - } - _headerPanel.ForceUpdate = true; - _headerPanel.Measure(new Size(_headerPanel.DesiredSize.Width, ActualHeight)); - _headerPanel.ForceUpdate = false; - } - item.IsSelected = true; - }; - _buttonOverflow.Menu.Items.Add(menuItem); + _headerPanel.FluidMoveDuration = new Duration(TimeSpan.FromSeconds(0)); + } + _headerPanel.ForceUpdate = true; + _headerPanel.Measure(new Size(_headerPanel.DesiredSize.Width, ActualHeight)); + _headerPanel.ForceUpdate = false; } - } - }; + item.IsSelected = true; + }; + _buttonOverflow.Menu.Items.Add(menuItem); + } } } } diff --git a/src/Shared/HandyControl_Shared/Themes/Styles/TabControl.xaml b/src/Shared/HandyControl_Shared/Themes/Styles/TabControl.xaml index 583d6614..3214908e 100644 --- a/src/Shared/HandyControl_Shared/Themes/Styles/TabControl.xaml +++ b/src/Shared/HandyControl_Shared/Themes/Styles/TabControl.xaml @@ -71,6 +71,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +