diff --git a/src/MainDemo.Wpf/Tabs.xaml b/src/MainDemo.Wpf/Tabs.xaml
index b575c717ae..c09a30d49b 100644
--- a/src/MainDemo.Wpf/Tabs.xaml
+++ b/src/MainDemo.Wpf/Tabs.xaml
@@ -756,8 +756,9 @@
+ Margin="0,0,0,16"
+ BorderBrush="{DynamicResource MaterialDesign.Brush.Primary}"
+ BorderThickness="1">
@@ -837,5 +838,266 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/MaterialDesignThemes.Wpf/Behaviors/Internal/TabControlHeaderScrollBehavior.cs b/src/MaterialDesignThemes.Wpf/Behaviors/Internal/TabControlHeaderScrollBehavior.cs
index 6605714ac4..7ad1d90b4f 100644
--- a/src/MaterialDesignThemes.Wpf/Behaviors/Internal/TabControlHeaderScrollBehavior.cs
+++ b/src/MaterialDesignThemes.Wpf/Behaviors/Internal/TabControlHeaderScrollBehavior.cs
@@ -1,4 +1,4 @@
-using System.Diagnostics;
+using System.ComponentModel;
using System.Windows.Media.Animation;
using Microsoft.Xaml.Behaviors;
@@ -13,6 +13,9 @@ public class TabControlHeaderScrollBehavior : Behavior
public static void SetCustomHorizontalOffset(DependencyObject obj, double value) => obj.SetValue(CustomHorizontalOffsetProperty, value);
private static void CustomHorizontalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
+ if (DesignerProperties.GetIsInDesignMode(d))
+ return;
+
var scrollViewer = (ScrollViewer)d;
scrollViewer.ScrollToHorizontalOffset((double)e.NewValue);
}
@@ -36,21 +39,64 @@ public TabControl TabControl
private static void OnTabControlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
+ if (DesignerProperties.GetIsInDesignMode(d))
+ return;
+
var behavior = (TabControlHeaderScrollBehavior)d;
if (e.OldValue is TabControl oldTabControl)
{
oldTabControl.SelectionChanged -= behavior.OnTabChanged;
oldTabControl.SizeChanged -= behavior.OnTabControlSizeChanged;
oldTabControl.PreviewKeyDown -= behavior.OnTabControlPreviewKeyDown;
+ oldTabControl.ItemContainerGenerator.ItemsChanged -= behavior.OnTabsChanged;
}
if (e.NewValue is TabControl newTabControl)
{
newTabControl.SelectionChanged += behavior.OnTabChanged;
newTabControl.SizeChanged += behavior.OnTabControlSizeChanged;
newTabControl.PreviewKeyDown += behavior.OnTabControlPreviewKeyDown;
+ newTabControl.ItemContainerGenerator.ItemsChanged += behavior.OnTabsChanged;
}
}
+ public double AdditionalHeaderPanelContentWidth
+ {
+ get => (double)GetValue(AdditionalHeaderPanelContentWidthProperty);
+ set => SetValue(AdditionalHeaderPanelContentWidthProperty, value);
+ }
+
+ public static readonly DependencyProperty AdditionalHeaderPanelContentWidthProperty =
+ DependencyProperty.Register(nameof(AdditionalHeaderPanelContentWidth), typeof(double),
+ typeof(TabControlHeaderScrollBehavior), new PropertyMetadata(0d, AdditionalHeaderPanelContentWidthChanged));
+
+ private static void AdditionalHeaderPanelContentWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (DesignerProperties.GetIsInDesignMode(d))
+ return;
+
+ var behavior = (TabControlHeaderScrollBehavior)d;
+ behavior.AddPaddingToScrollableContentIfWiderThanViewPort();
+ }
+
+ public double NavigationPanelLeftWidth
+ {
+ get => (double)GetValue(NavigationPanelLeftWidthProperty);
+ set => SetValue(NavigationPanelLeftWidthProperty, value);
+ }
+
+ public static readonly DependencyProperty NavigationPanelLeftWidthProperty =
+ DependencyProperty.Register(nameof(NavigationPanelLeftWidth), typeof(double),
+ typeof(TabControlHeaderScrollBehavior), new PropertyMetadata(0d, NavigationPanelLeftWidthChanged));
+
+ private static void NavigationPanelLeftWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (DesignerProperties.GetIsInDesignMode(d))
+ return;
+
+ var behavior = (TabControlHeaderScrollBehavior)d;
+ behavior.AddPaddingToScrollableContentIfWiderThanViewPort();
+ }
+
public FrameworkElement ScrollableContent
{
get => (FrameworkElement)GetValue(ScrollableContentProperty);
@@ -63,13 +109,123 @@ public FrameworkElement ScrollableContent
private static void OnScrollableContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
+ if (DesignerProperties.GetIsInDesignMode(d))
+ return;
+
var behavior = (TabControlHeaderScrollBehavior)d;
behavior.AddPaddingToScrollableContentIfWiderThanViewPort();
}
+ public ICommand NextTabCommand { get; }
+ public ICommand PreviousTabCommand { get; }
+
private double? _desiredScrollStart;
private bool _isAnimatingScroll;
+ public TabControlHeaderScrollBehavior()
+ {
+ if (DesignerProperties.GetIsInDesignMode(this))
+ {
+ NextTabCommand = null!;
+ PreviousTabCommand = null!;
+ return;
+ }
+
+ NextTabCommand = new SimpleICommandImplementation(_ =>
+ {
+ if (TabControl is { } tabControl)
+ {
+ NavigationPanelBehavior behavior = TabAssist.GetNavigationPanelBehavior(tabControl);
+ if (behavior == NavigationPanelBehavior.Scroll)
+ {
+ _desiredScrollStart = AssociatedObject.ContentHorizontalOffset;
+ double newOffset = Math.Min(AssociatedObject.ContentHorizontalOffset + AssociatedObject.ActualWidth, AssociatedObject.ExtentWidth - AssociatedObject.ActualWidth - AssociatedObject.Margin.Left - AssociatedObject.Margin.Right);
+ AssociatedObject.ScrollToHorizontalOffset(newOffset);
+ }
+ else if (behavior == NavigationPanelBehavior.Select && TryGetNextTabIndex(tabControl, out int nextIndex))
+ {
+ tabControl.SelectedIndex = nextIndex;
+ }
+ }
+ }, CanNextTabCommandExecute);
+ PreviousTabCommand = new SimpleICommandImplementation(_ =>
+ {
+ if (TabControl is { } tabControl)
+ {
+ NavigationPanelBehavior behavior = TabAssist.GetNavigationPanelBehavior(tabControl);
+ if (behavior == NavigationPanelBehavior.Scroll)
+ {
+ _desiredScrollStart = AssociatedObject.ContentHorizontalOffset;
+ AssociatedObject.ScrollToHorizontalOffset(AssociatedObject.ContentHorizontalOffset - AssociatedObject.ActualWidth);
+ }
+ else if (behavior == NavigationPanelBehavior.Select && TryGetPreviousTabIndex(tabControl, out int previousIndex))
+ {
+ tabControl.SelectedIndex = previousIndex;
+ }
+ }
+ }, CanPreviousTabCommandExecute);
+
+ bool CanNextTabCommandExecute(object? _)
+ {
+ if (TabControl is not { } tabControl)
+ return false;
+
+ NavigationPanelBehavior behavior = TabAssist.GetNavigationPanelBehavior(tabControl);
+ return behavior switch
+ {
+ NavigationPanelBehavior.Scroll => AssociatedObject.ContentHorizontalOffset < AssociatedObject.ExtentWidth - AssociatedObject.ActualWidth - AssociatedObject.Margin.Left - AssociatedObject.Margin.Right,
+ NavigationPanelBehavior.Select => TryGetNextTabIndex(tabControl, out int _),
+ _ => false
+ };
+ }
+
+ bool CanPreviousTabCommandExecute(object? _)
+ {
+ if (TabControl is not { } tabControl)
+ return false;
+
+ NavigationPanelBehavior behavior = TabAssist.GetNavigationPanelBehavior(tabControl);
+ return behavior switch
+ {
+ NavigationPanelBehavior.Scroll => AssociatedObject.ContentHorizontalOffset > 0,
+ NavigationPanelBehavior.Select => TryGetPreviousTabIndex(tabControl, out int _),
+ _ => false
+ };
+ }
+
+ static bool TryGetNextTabIndex(TabControl tabControl, out int nextTabIndex)
+ {
+ nextTabIndex = -1;
+ var nextTabs = GetEnabledTabItemIndices(tabControl, index => index > tabControl.SelectedIndex);
+ if (nextTabs.Count > 0)
+ {
+ nextTabIndex = nextTabs.First();
+ return true;
+ }
+ return false;
+ }
+
+ static bool TryGetPreviousTabIndex(TabControl tabControl, out int previousTabIndex)
+ {
+ previousTabIndex = -1;
+ var previousTabs = GetEnabledTabItemIndices(tabControl, index => index < tabControl.SelectedIndex);
+ if (previousTabs.Count > 0)
+ {
+ previousTabIndex = previousTabs.Last();
+ return true;
+ }
+ return false;
+ }
+
+ static List GetEnabledTabItemIndices(TabControl tabControl, Predicate predicate) => [.. tabControl
+ .Items
+ .Cast