WPF 灵活的 TabControl Header
WPF Flexible TabControl Header
我想要一个 TabControl
和多个 TabItems
。这些 TabItems
每个都有一个 header 文本。这些文本的长度可能相差很大(例如 5 个字符长和 15 个字符长)。
我希望 TabControl
仅在一行中对齐 header。
所有选项卡 header 应该使用相同的宽度,当有足够的 space 可用时,我希望它们使用所有可用的 space,最多 MaxWidth
,所有项目都一样。
因此,如果我想对 7 个项目使用 100 的 vMaxWidth`,选项卡 header 的最大宽度应为 700。如果还有更多 space 可用,则应忽略它。
如果可用的 space 较少,我希望 space 在项目之间平均分配。如果文本被截断,我想使用 TextWrapping
。
我现在已经尝试了多种方法来解决这个问题,这是我目前的设置:
<Style x:Key="Style-TabControl-Main" TargetType="{x:Type TabControl}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid KeyboardNavigation.TabNavigation="Local">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Border BorderThickness="0,0,0,1" Margin="13,0,0,0" BorderBrush="{StaticResource Brush-White}">
<StackPanel Panel.ZIndex="1" x:Name="HeaderPanel" IsItemsHost="True" KeyboardNavigation.TabIndex="1" Background="Transparent"
Orientation="Horizontal"/>
</Border>
<Border x:Name="Border"
Grid.Row="1" Grid.ColumnSpan="2"
KeyboardNavigation.TabNavigation="Local"
KeyboardNavigation.DirectionalNavigation="Contained"
KeyboardNavigation.TabIndex="2"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}">
<ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
和 TabItem
Style
<Style x:Key="Style-TabItem-Main" TargetType="{x:Type TabItem}">
<Setter Property="Height" Value="31"/>
<Setter Property="Width" Value="180" />
<Setter Property="Foreground" Value="{DynamicResource Brush-BrightRegular-Foreground}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border x:Name="Border" Cursor="Hand"
Margin="2,0,0,0"
BorderThickness="1,1,1,0"
CornerRadius="4,4,0,0"
BorderBrush="{DynamicResource Brush-BrightRegular-Background}"
Background="{DynamicResource Brush-White}">
<ContentPresenter x:Name="Content" VerticalAlignment="Center" HorizontalAlignment="Stretch" ContentSource="Header" RecognizesAccessKey="True"
TextBlock.TextAlignment="Center" TextBlock.FontSize="16" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="{DynamicResource Brush-White}"/>
<Setter TargetName="Border" Property="Background" Value="{DynamicResource Brush-DefaultDark-Background}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
我正在使用 StackPanel
而不是 TabPanel
来摆脱 "stacking",当您调整默认值 TabControl
时会出现这种情况。但是,我无法满足其余的要求。我尝试将 MaxWidth
(而不是固定宽度)应用到 TabItem
headers,但这当然不起作用,因为该项目会缩小到所需的最小尺寸。
步骤 1(第一次尝试): 将 header 放在一行中,并给每个 header 相同的宽度。
这可以通过使用 UniformGrid
而不是标准 TabPanel 并将其行数锁定为 1 来实现。这是 TabControl
样式的 stripped-down 版本:
<Style x:Key="Style-TabControl-Main" TargetType="{x:Type TabControl}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border>
<UniformGrid x:Name="HeaderPanel" IsItemsHost="True"
Rows="1" />
</Border>
<Border x:Name="Border" Grid.Row="1"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}">
<ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
第 2 步: 将 header 限制为 MaxWidth
并应用文本换行。
MaxWidth
可以在 TabItem
样式中设置,连同 HeaderTemplate
包装文本(您仍然可以在此处使用您的自定义 ControlTemplate
来设置样式TabItem 部分):
<Style x:Key="Style-TabItem-Main" TargetType="{x:Type TabItem}">
<Setter Property="MaxWidth" Value="100" />
<!--https://social.msdn.microsoft.com/forums/vstudio/en-US/df4f7fc3-f0ec-4ed1-a022-a32650e49cb3/how-to-wrap-header-text-in-tabcontrol-->
<Setter Property="HeaderTemplate" >
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding}" TextWrapping="Wrap" />
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
...
</Setter>
</Style>
故障排除:现在,如果您在步骤 2 中应用 MaxWidth
,您可能需要 left-align UniformGrid
当 TabControl 变得太宽时..
<UniformGrid x:Name="HeaderPanel" IsItemsHost="True"
Rows="1" HorizontalAlignment="Left" />
..但是您不希望在尚未达到 MaxWidth 时出现这种情况,并且项目应延伸到 TabControl 的整个宽度(又名 步骤 1 ).所以我们需要一种方法来根据是否达到项目的最大宽度(如果已设置)来切换 HorizontalAlignment
。
第 1 步(重访):让我们尝试制作我们自己的 UniformGrid:
public class UniformTabPanel : UniformGrid
{
public UniformTabPanel()
{
this.IsItemsHost = true;
this.Rows = 1;
//Default, so not really needed..
this.HorizontalAlignment = HorizontalAlignment.Stretch;
}
protected override Size MeasureOverride(Size constraint)
{
var totalMaxWidth = this.Children.OfType<TabItem>().Sum(tab => tab.MaxWidth);
if (!double.IsInfinity(totalMaxWidth))
{
this.HorizontalAlignment = (constraint.Width > totalMaxWidth)
? HorizontalAlignment.Left
: HorizontalAlignment.Stretch;
}
return base.MeasureOverride(constraint);
}
}
现在,我们可以替换 TabControl
样式中的 UniformGrid
这个新面板:
...
<Border>
<mycontrols:UniformTabPanel x:Name="HeaderPanel" />
</Border>
...
...TabControl 应该按预期运行。
Sphinxx 的答案是正确的,但是我需要将以下代码添加到 UniformTabPanel,以使其按我想要的方式工作(当有足够的 space 可用时,将 headers 的大小调整为最大宽度)
我将以下代码添加到 UniformTabPanel,它现在可以满足我的需要:
protected override Size MeasureOverride(Size constraint)
{
var children = this.Children.OfType<TabItem>();
var totalMaxWidth = children.Sum(tab => tab.MaxWidth);
if (!double.IsInfinity(totalMaxWidth))
{
this.HorizontalAlignment = (constraint.Width > totalMaxWidth)
? HorizontalAlignment.Left
: HorizontalAlignment.Stretch;
foreach (var child in children)
{
child.Width = this.HorizontalAlignment == System.Windows.HorizontalAlignment.Left
? child.MaxWidth
: Double.NaN;
}
}
return base.MeasureOverride(constraint);
}
我想要一个 TabControl
和多个 TabItems
。这些 TabItems
每个都有一个 header 文本。这些文本的长度可能相差很大(例如 5 个字符长和 15 个字符长)。
我希望 TabControl
仅在一行中对齐 header。
所有选项卡 header 应该使用相同的宽度,当有足够的 space 可用时,我希望它们使用所有可用的 space,最多 MaxWidth
,所有项目都一样。
因此,如果我想对 7 个项目使用 100 的 vMaxWidth`,选项卡 header 的最大宽度应为 700。如果还有更多 space 可用,则应忽略它。
如果可用的 space 较少,我希望 space 在项目之间平均分配。如果文本被截断,我想使用 TextWrapping
。
我现在已经尝试了多种方法来解决这个问题,这是我目前的设置:
<Style x:Key="Style-TabControl-Main" TargetType="{x:Type TabControl}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid KeyboardNavigation.TabNavigation="Local">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Border BorderThickness="0,0,0,1" Margin="13,0,0,0" BorderBrush="{StaticResource Brush-White}">
<StackPanel Panel.ZIndex="1" x:Name="HeaderPanel" IsItemsHost="True" KeyboardNavigation.TabIndex="1" Background="Transparent"
Orientation="Horizontal"/>
</Border>
<Border x:Name="Border"
Grid.Row="1" Grid.ColumnSpan="2"
KeyboardNavigation.TabNavigation="Local"
KeyboardNavigation.DirectionalNavigation="Contained"
KeyboardNavigation.TabIndex="2"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}">
<ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
和 TabItem
Style
<Style x:Key="Style-TabItem-Main" TargetType="{x:Type TabItem}">
<Setter Property="Height" Value="31"/>
<Setter Property="Width" Value="180" />
<Setter Property="Foreground" Value="{DynamicResource Brush-BrightRegular-Foreground}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border x:Name="Border" Cursor="Hand"
Margin="2,0,0,0"
BorderThickness="1,1,1,0"
CornerRadius="4,4,0,0"
BorderBrush="{DynamicResource Brush-BrightRegular-Background}"
Background="{DynamicResource Brush-White}">
<ContentPresenter x:Name="Content" VerticalAlignment="Center" HorizontalAlignment="Stretch" ContentSource="Header" RecognizesAccessKey="True"
TextBlock.TextAlignment="Center" TextBlock.FontSize="16" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="{DynamicResource Brush-White}"/>
<Setter TargetName="Border" Property="Background" Value="{DynamicResource Brush-DefaultDark-Background}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
我正在使用 StackPanel
而不是 TabPanel
来摆脱 "stacking",当您调整默认值 TabControl
时会出现这种情况。但是,我无法满足其余的要求。我尝试将 MaxWidth
(而不是固定宽度)应用到 TabItem
headers,但这当然不起作用,因为该项目会缩小到所需的最小尺寸。
步骤 1(第一次尝试): 将 header 放在一行中,并给每个 header 相同的宽度。
这可以通过使用 UniformGrid
而不是标准 TabPanel 并将其行数锁定为 1 来实现。这是 TabControl
样式的 stripped-down 版本:
<Style x:Key="Style-TabControl-Main" TargetType="{x:Type TabControl}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border>
<UniformGrid x:Name="HeaderPanel" IsItemsHost="True"
Rows="1" />
</Border>
<Border x:Name="Border" Grid.Row="1"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}">
<ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
第 2 步: 将 header 限制为 MaxWidth
并应用文本换行。
MaxWidth
可以在 TabItem
样式中设置,连同 HeaderTemplate
包装文本(您仍然可以在此处使用您的自定义 ControlTemplate
来设置样式TabItem 部分):
<Style x:Key="Style-TabItem-Main" TargetType="{x:Type TabItem}">
<Setter Property="MaxWidth" Value="100" />
<!--https://social.msdn.microsoft.com/forums/vstudio/en-US/df4f7fc3-f0ec-4ed1-a022-a32650e49cb3/how-to-wrap-header-text-in-tabcontrol-->
<Setter Property="HeaderTemplate" >
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding}" TextWrapping="Wrap" />
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
...
</Setter>
</Style>
故障排除:现在,如果您在步骤 2 中应用 MaxWidth
,您可能需要 left-align UniformGrid
当 TabControl 变得太宽时..
<UniformGrid x:Name="HeaderPanel" IsItemsHost="True"
Rows="1" HorizontalAlignment="Left" />
..但是您不希望在尚未达到 MaxWidth 时出现这种情况,并且项目应延伸到 TabControl 的整个宽度(又名 步骤 1 ).所以我们需要一种方法来根据是否达到项目的最大宽度(如果已设置)来切换 HorizontalAlignment
。
第 1 步(重访):让我们尝试制作我们自己的 UniformGrid:
public class UniformTabPanel : UniformGrid
{
public UniformTabPanel()
{
this.IsItemsHost = true;
this.Rows = 1;
//Default, so not really needed..
this.HorizontalAlignment = HorizontalAlignment.Stretch;
}
protected override Size MeasureOverride(Size constraint)
{
var totalMaxWidth = this.Children.OfType<TabItem>().Sum(tab => tab.MaxWidth);
if (!double.IsInfinity(totalMaxWidth))
{
this.HorizontalAlignment = (constraint.Width > totalMaxWidth)
? HorizontalAlignment.Left
: HorizontalAlignment.Stretch;
}
return base.MeasureOverride(constraint);
}
}
现在,我们可以替换 TabControl
样式中的 UniformGrid
这个新面板:
...
<Border>
<mycontrols:UniformTabPanel x:Name="HeaderPanel" />
</Border>
...
...TabControl 应该按预期运行。
Sphinxx 的答案是正确的,但是我需要将以下代码添加到 UniformTabPanel,以使其按我想要的方式工作(当有足够的 space 可用时,将 headers 的大小调整为最大宽度)
我将以下代码添加到 UniformTabPanel,它现在可以满足我的需要:
protected override Size MeasureOverride(Size constraint)
{
var children = this.Children.OfType<TabItem>();
var totalMaxWidth = children.Sum(tab => tab.MaxWidth);
if (!double.IsInfinity(totalMaxWidth))
{
this.HorizontalAlignment = (constraint.Width > totalMaxWidth)
? HorizontalAlignment.Left
: HorizontalAlignment.Stretch;
foreach (var child in children)
{
child.Width = this.HorizontalAlignment == System.Windows.HorizontalAlignment.Left
? child.MaxWidth
: Double.NaN;
}
}
return base.MeasureOverride(constraint);
}