Scrolling Tabs in TabControl, Better horizontal scrolling

This commit is contained in:
Montilla Kryštof committed 2019-05-07 19:06:32 +02:00
1 parent 8f9fe6df49
commit 53c9bd7836
3 files changed
+138 -24

No files matched your search

@@ -17,6 +17,8 @@ namespace HandyControl.Controls
private bool _isRunning;
private Storyboard _scrollingAnimation = new Storyboard();
/// <summary>
/// 滚动方向
/// </summary>
@@ -28,7 +30,7 @@ namespace HandyControl.Controls
/// </summary>
public Orientation Orientation
{
get => (Orientation) GetValue(OrientationProperty);
get => (Orientation)GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
@@ -43,7 +45,7 @@ namespace HandyControl.Controls
/// </summary>
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);
}
/// <summary>
@@ -184,7 +191,7 @@ namespace HandyControl.Controls
public static bool GetIsPenetrating(DependencyObject element)
{
return (bool) element.GetValue(IsPenetratingProperty);
return (bool)element.GetValue(IsPenetratingProperty);
}
/// <summary>
@@ -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
/// </summary>
public double CurrentHorizontalOffset
{
get => (double) GetValue(CurrentHorizontalOffsetProperty);
get => (double)GetValue(CurrentHorizontalOffsetProperty);
set => SetValue(CurrentHorizontalOffsetProperty, value);
}
}
@@ -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;
/// <summary>
/// 是否为内部操作
/// </summary>
@@ -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));
/// <summary>
/// 可见的标签数量
/// </summary>
@@ -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);
@@ -91,23 +91,78 @@
<RowDefinition x:Name="RowDefinition0" Height="Auto"/>
<RowDefinition x:Name="RowDefinition1" Height="*"/>
</Grid.RowDefinitions>
<Border Background="{TemplateBinding Background}" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource BorderBrush}">
<controls:TabPanel MinHeight="{TemplateBinding TabItemHeight}" TabItemHeight="{TemplateBinding TabItemHeight}" TabItemWidth="{TemplateBinding TabItemWidth}" IsEnableTabFill="{TemplateBinding IsEnableTabFill}" Margin="0,0,-1,-1" x:Name="PART_HeaderPanel" HorizontalAlignment="Left" Background="{DynamicResource SecondaryTextBrush}" IsItemsHost="true" KeyboardNavigation.TabIndex="1" Panel.ZIndex="1">
<interactivity:Interaction.Behaviors>
<interactivity:FluidMoveBehavior AppliesTo="Children" Duration="{Binding FluidMoveDuration,ElementName=PART_HeaderPanel}">
<interactivity:FluidMoveBehavior.EaseX>
<CubicEase EasingMode="EaseOut"/>
</interactivity:FluidMoveBehavior.EaseX>
</interactivity:FluidMoveBehavior>
</interactivity:Interaction.Behaviors>
</controls:TabPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Visibility="{Binding Visibility,ElementName=PART_OverflowButton}" Margin="0,-1,0,0" Grid.Row="0" Grid.Column="0"
BorderThickness="0,0,1,0" BorderBrush="{DynamicResource BorderBrush}"
Background="{DynamicResource RegionBrush}" HorizontalAlignment="Left"
Panel.ZIndex="2">
<Border.CacheMode>
<BitmapCache SnapsToDevicePixels="True"/>
</Border.CacheMode>
<Border.Effect>
<DropShadowEffect Direction="-45" BlurRadius="25"
ShadowDepth="8" Opacity="0.5"
Color="{DynamicResource EffectShadowColor}"/>
</Border.Effect>
<Button Style="{DynamicResource ButtonIcon}"
controls:IconElement.Geometry="{StaticResource LeftGeometry}"
Grid.Column="0"
Foreground="{DynamicResource PrimaryTextBrush}"
x:Name="PART_OverflowButtonLeft"
/>
</Border>
<controls:ScrollViewer HorizontalScrollBarVisibility="Hidden"
CanMouseWheel="True" Orientation="Horizontal"
IsEnableInertia="True"
Grid.Column="1"
IsDeferredScrollingEnabled="True"
x:Name="PART_OverflowScrollviewer">
<Border Background="{TemplateBinding Background}" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource BorderBrush}" >
<controls:TabPanel MinHeight="{TemplateBinding TabItemHeight}" TabItemHeight="{TemplateBinding TabItemHeight}" TabItemWidth="{TemplateBinding TabItemWidth}" IsEnableTabFill="{TemplateBinding IsEnableTabFill}" Margin="0,0,-1,-1" x:Name="PART_HeaderPanel" HorizontalAlignment="Left" Background="{DynamicResource SecondaryTextBrush}" IsItemsHost="true" KeyboardNavigation.TabIndex="1" Panel.ZIndex="1">
<interactivity:Interaction.Behaviors>
<interactivity:FluidMoveBehavior AppliesTo="Children" Duration="{Binding FluidMoveDuration,ElementName=PART_HeaderPanel}">
<interactivity:FluidMoveBehavior.EaseX>
<CubicEase EasingMode="EaseOut"/>
</interactivity:FluidMoveBehavior.EaseX>
</interactivity:FluidMoveBehavior>
</interactivity:Interaction.Behaviors>
</controls:TabPanel>
</Border>
</controls:ScrollViewer>
</Grid>
<Border Visibility="{Binding Visibility,ElementName=PART_OverflowButton}"
Margin="0,-1,0,0" Grid.Row="0" Grid.Column="0" BorderThickness="1,0,0,1" BorderBrush="{DynamicResource BorderBrush}"
Background="{DynamicResource RegionBrush}" HorizontalAlignment="Right">
<Border.Effect>
<DropShadowEffect Direction="-135" BlurRadius="25"
ShadowDepth="8" Opacity="0.5"
Color="{DynamicResource EffectShadowColor}"/>
</Border.Effect>
<Border.CacheMode>
<BitmapCache SnapsToDevicePixels="True"/>
</Border.CacheMode>
<StackPanel Orientation="Horizontal">
<Button Style="{DynamicResource ButtonIcon}"
controls:IconElement.Geometry="{StaticResource RightGeometry}"
Foreground="{DynamicResource PrimaryTextBrush}"
x:Name="PART_OverflowButtonRight"/>
<controls:ContextMenuToggleButton x:Name="PART_OverflowButton" Visibility="Collapsed" Style="{StaticResource ToggleButtonIconTransparent}" controls:IconSwitchElement.Geometry="{StaticResource DownGeometry}" controls:IconSwitchElement.GeometrySelected="{StaticResource UpGeometry}"/>
</StackPanel>
</Border>
<Border x:Name="contentPanel" Background="{DynamicResource SecondaryRegionBrush}" Grid.Column="0" KeyboardNavigation.DirectionalNavigation="Contained" Grid.Row="1" KeyboardNavigation.TabIndex="2" KeyboardNavigation.TabNavigation="Local">
<Border x:Name="contentPanel" Background="{DynamicResource SecondaryRegionBrush}"
Grid.Column="0" KeyboardNavigation.DirectionalNavigation="Contained" Grid.Row="1" KeyboardNavigation.TabIndex="2" KeyboardNavigation.TabNavigation="Local"
>
<ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
<Border Visibility="{Binding Visibility,ElementName=PART_OverflowButton}" Margin="0,-1,0,0" Grid.Row="0" Grid.Column="0" BorderThickness="1,0,0,1" BorderBrush="{DynamicResource BorderBrush}" Background="{DynamicResource RegionBrush}" HorizontalAlignment="Right">
<controls:ContextMenuToggleButton x:Name="PART_OverflowButton" Visibility="Collapsed" Style="{StaticResource ToggleButtonIconTransparent}" controls:IconSwitchElement.Geometry="{StaticResource DownGeometry}" controls:IconSwitchElement.GeometrySelected="{StaticResource UpGeometry}"/>
</Border>
</Grid>
</Border>
</ControlTemplate>