如何将WPF选项卡标题宽度自适应父控件宽度

37

在XAML中,是否有一种方法可以使选项卡标题跨越整个选项卡控件的宽度?

例如,我有三个标签页:红色、蓝色和绿色。如果我有一个宽度设置为自动的选项卡控件,那么选项卡标题将只填充选项卡内容上方的部分空间,但我希望它们填满所有空间。对于我的三个选项卡示例,红色应占据控件的第一个三分之一,蓝色应占据中心三分之一,绿色则占据最后三分之一。

我正在考虑在代码背景下实现这个想法,但我更希望以最简单的方式来完成。

12个回答

54

我使用了Jordan的代码示例,并进行了一些更改。这个版本适用于任意数量的选项卡:

namespace WpfApplication1.Converters
{
    public class TabSizeConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter,
            System.Globalization.CultureInfo culture)
        {
            TabControl tabControl = values[0] as TabControl;
            double width = tabControl.ActualWidth / tabControl.Items.Count;
            //Subtract 1, otherwise we could overflow to two rows.
            return (width <= 1) ? 0 : (width - 1);
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter,
            System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }
}

在XAML中使用相同的命名空间:

xmlns:local="clr-namespace:WpfApplication1.Converters"

并且这将使得所有的选项卡都使用它:

<Window.Resources>
    <local:TabSizeConverter x:Key="tabSizeConverter" />
    <Style TargetType="{x:Type TabItem}">
        <Setter Property="Width">
            <Setter.Value>
                <MultiBinding Converter="{StaticResource tabSizeConverter}">
                    <Binding RelativeSource="{RelativeSource Mode=FindAncestor,
            AncestorType={x:Type TabControl}}" />
                    <Binding RelativeSource="{RelativeSource Mode=FindAncestor,
            AncestorType={x:Type TabControl}}" Path="ActualWidth" />
                </MultiBinding>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

4
使用此功能时,标签页会在彼此之上堆叠,但如果我将 (width - 1) 改为 (width - 2.1),那么它就“正常了”,但似乎在标签项的两侧仍有一个像素空间。我做错了什么吗? - ChrisHDog
3
当选项卡项目数小于等于3时,我的结果与@ChrisHDog相同。当有四个或更多选项卡时,它运行良好。 我已将我的结果更改为:return (width <= 1) ? 0:(width - 1.34);,这似乎可以处理所有选项卡项目数。 - Mike L
@RyanVersaw:真是太棒了 :) - Priyank Thakkar
2
@RyanVersaw:为什么不实现IValueConverter而是IMultiValueConverter? - oli
2
之所以使用 IMultiValueConverter 而不是 IValueConverter 是因为如果没有第二个绑定到 ActualWidth,则绑定将在控件呈现之前绑定,导致转换器内的 ActualWidth 始终为零。添加第二个绑定到 ActualWidth 强制绑定每次父 TabControl 的宽度更改时重新评估。这有点笨拙,但它有效。 - XVar
显示剩余5条评论

23

似乎每个人都在使用转换器,但实际上可以在TabControl模板中使用Rows设置为1的UniformGrid来代替TabPanel,这并不复杂。当然,您需要重新定制它,但这并不困难。


它完美地运行,而且要简单得多(假设你喜欢搞模板...但你确实喜欢,对吧?) - Zoé Martin
3
可以举个例子,方便那些喜欢复制粘贴的人 :) - TravisWhidden
1
示例用法:https://dev59.com/cIfca4cB1Zd3GeqPoNU2 - TravisWhidden
哇,这比转换器方法简单得多,而且更可靠,谢谢! - Fabian
更简单,甚至不需要与C#交互。完美的解决方案。 - Jan Skála

7

我是一个老派的人,喜欢将这种功能封装到控件本身的代码中。我的派生控件如下所示:

    public class CustomTabControl :TabControl
{
    protected override void OnRenderSizeChanged(System.Windows.SizeChangedInfo sizeInfo)
    {
        foreach (TabItem item in this.Items)
        {
            double newW = (this.ActualWidth / Items.Count) - 1;
            if (newW < 0) newW = 0;

            item.Width = newW;
        }            
    }       
}

我的XAML看起来像

</infrastructure:CustomTabControl>
     <TabItem />
     <TabItem />
</infrustracture:CustomControl>

有人能解释一下为什么大家都更喜欢样式控制而不是派生吗?


6
我可以使用类似以下的转换器来实现这个功能:
namespace WpfApplication1.Converters
{
    public class SizeConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter,
            System.Globalization.CultureInfo culture)
        {
            double width = Double.Parse(value.ToString());
            //Subtract 1, otherwise we could overflow to two rows.
            return .25 * width - 1;
        }

        public object ConvertBack(object value, Type targetType, object parameter,
            System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException();
        }

        #endregion
    }
}

然后将命名空间添加到我的XAML中:

xmlns:local="clr-namespace:WpfApplication1.Converters"

然后将所有的TabItems都使用该转换器:

<Window.Resources>
        <local:SizeConverter x:Key="sizeConverter" />
        <Style TargetType="{x:Type TabItem}">
            <Setter Property="Width" Value="{Binding ElementName=x_Grid, Path=ActualWidth, Converter={StaticResource sizeConverter}}" />
        </Style>
    </Window.Resources>

x_Grid是父元素的x:Name,我希望选项卡的大小为其四分之一。


非常抱歉代码下面有大量的空白。我不知道为什么会这样。 - Jordan H.
此外,您可以使转换器尽可能复杂。您可以获取TabControl,查看其项目并确定其数量,然后使用该数量来计算宽度。 - Jordan H.
1
Jordan:谢谢,老兄,这个很好用 :) 顺便说一下,“Object Value”以double形式进入Convert函数,所以你可以将其强制转换为“double height = (double)value;” +1 BTW :) - Binary Worrier

4
这是最快速的解决方案:
<TabControl.Template>
    <ControlTemplate TargetType="TabControl">
        <DockPanel>
            <UniformGrid IsItemsHost="True" Rows="1" DockPanel.Dock="Top"></UniformGrid>
            <ContentPresenter ContentSource="SelectedContent"></ContentPresenter>
        </DockPanel>
    </ControlTemplate>
</TabControl.Template>

我在使用这个过程中遇到了一些意外的行为。更改包含这个模板化TabControl的祖先TabControl会导致模板化TabControl的选项卡也发生变化。有人知道为什么会发生这种情况吗? - undefined

2

通过将宽度绑定到父选项卡控件的ActualWidth,可以实现如下所示。

我已经将其包装在一个样式中,以应用于所有选项卡页面。

<Grid>
      <Grid.Resources>
        <Style TargetType="TabItem">
            <Setter Property="Width" Value="{Binding    
                     Path=ActualWidth,    
                     RelativeSource={RelativeSource    
                    Mode=FindAncestor,    
                    AncestorType={x:Type TabControl}}}"/>
        </Style>
    </Grid.Resources>

<TabControl>
    <TabItem Header="Page3"/>
    <TabItem Header="Page2"/>
    <TabItem Header="Page3"/>            
</TabControl> 
</Grid>

这只会使每个TabItem占据整个宽度。所需的是它们需要共享宽度。 - Sergey Aldoukhov

1

我按照Charlie的建议,走上了重新模板化的路线。这里是一个简单的TabControl实现,它将可用空间平均分配给其TabItem,使用UniformGrid:

控件的XAML

<TabControl x:Class="YourNamespace.Views.BigTabsTabControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:YourNamespace.Views"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
            Padding="2" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
            BorderThickness="1" Foreground="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}">

    <TabControl.Resources>
        <SolidColorBrush x:Key="TabItem.Selected.Background" Color="#FFFFFF"/>
        <SolidColorBrush x:Key="TabItem.Selected.Border" Color="#ACACAC"/>
    </TabControl.Resources>

    <TabControl.Style>
        <Style TargetType="{x:Type TabControl}">
            <Setter Property="Background" Value="{StaticResource TabItem.Selected.Background}"/>
            <Setter Property="BorderBrush" Value="{StaticResource TabItem.Selected.Border}"/>
        </Style>
    </TabControl.Style>

    <TabControl.Template>
        <ControlTemplate TargetType="{x:Type TabControl}">
            <Grid x:Name="templateRoot" ClipToBounds="true" SnapsToDevicePixels="true" KeyboardNavigation.TabNavigation="Local">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition x:Name="ColumnDefinition0"/>
                    <ColumnDefinition x:Name="ColumnDefinition1" Width="0"/>
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition x:Name="RowDefinition0" Height="Auto"/>
                    <RowDefinition x:Name="RowDefinition1" Height="*"/>
                </Grid.RowDefinitions>

                <UniformGrid x:Name="headerPanel" Background="Transparent" Grid.Column="0" IsItemsHost="true" Grid.Row="0" KeyboardNavigation.TabIndex="1" Panel.ZIndex="1" />
                <Border x:Name="contentPanel" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" 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>
            </Grid>
            <ControlTemplate.Triggers>
                <Trigger Property="TabStripPlacement" Value="Bottom">
                    <Setter Property="Grid.Row" TargetName="headerPanel" Value="1"/>
                    <Setter Property="Grid.Row" TargetName="contentPanel" Value="0"/>
                    <Setter Property="Height" TargetName="RowDefinition0" Value="*"/>
                    <Setter Property="Height" TargetName="RowDefinition1" Value="Auto"/>
                    <Setter Property="Margin" TargetName="headerPanel" Value="2,0,2,2"/>
                </Trigger>
                <Trigger Property="TabStripPlacement" Value="Left">
                    <Setter Property="Grid.Row" TargetName="headerPanel" Value="0"/>
                    <Setter Property="Grid.Row" TargetName="contentPanel" Value="0"/>
                    <Setter Property="Grid.Column" TargetName="headerPanel" Value="0"/>
                    <Setter Property="Grid.Column" TargetName="contentPanel" Value="1"/>
                    <Setter Property="Width" TargetName="ColumnDefinition0" Value="Auto"/>
                    <Setter Property="Width" TargetName="ColumnDefinition1" Value="*"/>
                    <Setter Property="Height" TargetName="RowDefinition0" Value="*"/>
                    <Setter Property="Height" TargetName="RowDefinition1" Value="0"/>
                    <Setter Property="Margin" TargetName="headerPanel" Value="2,2,0,2"/>
                </Trigger>
                <Trigger Property="TabStripPlacement" Value="Right">
                    <Setter Property="Grid.Row" TargetName="headerPanel" Value="0"/>
                    <Setter Property="Grid.Row" TargetName="contentPanel" Value="0"/>
                    <Setter Property="Grid.Column" TargetName="headerPanel" Value="1"/>
                    <Setter Property="Grid.Column" TargetName="contentPanel" Value="0"/>
                    <Setter Property="Width" TargetName="ColumnDefinition0" Value="*"/>
                    <Setter Property="Width" TargetName="ColumnDefinition1" Value="Auto"/>
                    <Setter Property="Height" TargetName="RowDefinition0" Value="*"/>
                    <Setter Property="Height" TargetName="RowDefinition1" Value="0"/>
                    <Setter Property="Margin" TargetName="headerPanel" Value="0,2,2,2"/>
                </Trigger>
                <Trigger Property="IsEnabled" Value="false">
                    <Setter Property="TextElement.Foreground" TargetName="templateRoot" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </TabControl.Template>
</TabControl>

控件的代码后台
using System.Windows.Controls;
using System.Windows.Controls.Primitives;

namespace YourNamespace.Views
{
  /// <summary>
  /// A TabControl with large tabs. 
  /// </summary>
  public partial class BigTabsTabControl : TabControl
  {
    public BigTabsTabControl()
    {
      InitializeComponent();
    }

    public override void OnApplyTemplate()
    {
      base.OnApplyTemplate();

      if (this.Template != null)
      {
        UniformGrid X = this.Template.FindName("headerPanel", this) as UniformGrid;
        if (X != null) X.Columns = this.Items.Count;
      }
    }
  }
}

就这样,您现在可以向此控件添加TabItem,它们将自动调整宽度。这些TabItem无需指定Grid.Column,即使在设计时也可以正常工作。


你为什么使用边距来调整选项卡的大小,而不是将RowDefinition0的高度设置为静态数字而不是“自动”?这没有意义,是一种倒退的做法。 - Anthony Stivers
我没有使用边距来调整选项卡的大小。由于设置了UniformGrid.Columns(这就是UniformGrid的作用),选项卡会自动调整大小。另外请注意,我们不能将RowDefinition0的高度设置为静态数字,因为用户可以使用任何高度的标题。最后,如果您对答案中的某些内容不理解,向提问者询问总是友善和体贴的做法,而不是直接点踩。 - dotNET
据我所知,相反的情况是正确的。选项卡内容的高度是静态的(300),而选项卡本身的高度填充其余可用空间。通过将RowDefinition0的Height属性设置为40,并删除UniformGrid上的边距,我能够获得所需的效果。为什么UniformGrid底部有300个边距?那有什么作用?也许这是我将选项卡放入DockPanel和LastChildFill中的一些奇怪交互,但最终进行这些更改会产生所需的效果,这使我相信它应该是这样的。 - Anthony Stivers
@AnthonyStivers:感谢您指出这个问题。XAML实际上有两个问题。首先,Margin设置不应该存在;不知道它是怎么通过的。其次,Grid.Row应该是0,而不是1。除了这两个问题,其他的都是正确的,对我来说完全正常工作。 - dotNET
补充一下这个答案,欢迎使用@dotNET编辑此信息。您需要将此自定义的TabControl放置在解决方案中的单独项目中。如果不这样做,当您尝试向TabControl添加多个选项卡时,可能会遇到一个神秘的错误“属性'content'只能设置一次”。至少在我的情况下,我遇到了这个错误,并通过将自定义的TabControl放置在解决方案中的单独项目中来解决它。该项目仍然可以编译并以所需的效果运行。这只是一个设计时错误。但仍然很烦人。 - Anthony Stivers
显示剩余2条评论

1
这里有一个仅使用模板的无痛解决方案:
<Window x:Class="Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:EffectLibrary="clr-namespace:EffectLibrary;assembly=EffectLibrary"
        mc:Ignorable="d"
        Title="Window1" Height="300" Width="300">
    <TabControl Style="{DynamicResource TabControlStyle}" ItemContainerStyle="{DynamicResource TabItemStyle}" BorderBrush="{DynamicResource Pallete.Primary}" Foreground="{DynamicResource Pallete.Primary}" Background="Transparent" Margin="0" d:LayoutOverrides="Height">
        <TabControl.Resources>
            <Style x:Key="TabControlStyle" TargetType="{x:Type TabControl}">
                <Setter Property="Padding" Value="0"/>
                <Setter Property="HorizontalContentAlignment" Value="Center"/>
                <Setter Property="VerticalContentAlignment" Value="Center"/>
                <Setter Property="Background" Value="Transparent"/>
                <Setter Property="BorderBrush" Value="#093A5F"/>
                <Setter Property="BorderThickness" Value="1"/>
                <Setter Property="Foreground" Value="#001423"/>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type TabControl}">
                            <Border x:Name="Bg" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
                                <Grid x:Name="templateRoot" ClipToBounds="true" SnapsToDevicePixels="true" KeyboardNavigation.TabNavigation="Local">
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition x:Name="ColumnDefinition0"/>
                                        <ColumnDefinition x:Name="ColumnDefinition1" Width="0"/>
                                    </Grid.ColumnDefinitions>
                                    <Grid.RowDefinitions>
                                        <RowDefinition x:Name="RowDefinition0" Height="Auto"/>
                                        <RowDefinition x:Name="RowDefinition1" Height="*"/>
                                    </Grid.RowDefinitions>
                                    <UniformGrid x:Name="headerPanel" IsItemsHost="True" Margin="0">
                                        <UniformGrid.Style>
                                            <Style TargetType="{x:Type UniformGrid}">
                                                <Setter Property="Rows" Value="1"/>
                                                <Style.Triggers>
                                                    <DataTrigger Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource TemplatedParent}}" Value="Right">
                                                        <Setter Property="Columns" Value="1"/>
                                                        <Setter Property="Rows" Value="0"/>
                                                    </DataTrigger>
                                                    <DataTrigger Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource TemplatedParent}}" Value="Left">
                                                        <Setter Property="Columns" Value="1"/>
                                                        <Setter Property="Rows" Value="0"/>
                                                    </DataTrigger>
                                                </Style.Triggers>
                                            </Style>
                                        </UniformGrid.Style>
                                    </UniformGrid>
                                    <Border x:Name="contentPanel" Grid.Column="0" KeyboardNavigation.DirectionalNavigation="Contained" Grid.Row="1" KeyboardNavigation.TabIndex="2" KeyboardNavigation.TabNavigation="Local" BorderThickness="0,1,0,0" BorderBrush="{TemplateBinding BorderBrush}">
                                        <ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                                    </Border>
                                </Grid>
                            </Border>
                            <ControlTemplate.Triggers>
                                <Trigger Property="TabStripPlacement" Value="Bottom">
                                    <Setter Property="Grid.Row" TargetName="headerPanel" Value="1"/>
                                    <Setter Property="Grid.Row" TargetName="contentPanel" Value="0"/>
                                    <Setter Property="Height" TargetName="RowDefinition0" Value="*"/>
                                    <Setter Property="Height" TargetName="RowDefinition1" Value="Auto"/>
                                </Trigger>
                                <Trigger Property="TabStripPlacement" Value="Left">
                                    <Setter Property="Grid.Row" TargetName="headerPanel" Value="0"/>
                                    <Setter Property="Grid.Row" TargetName="contentPanel" Value="0"/>
                                    <Setter Property="Grid.Column" TargetName="headerPanel" Value="0"/>
                                    <Setter Property="Grid.Column" TargetName="contentPanel" Value="1"/>
                                    <Setter Property="Width" TargetName="ColumnDefinition0" Value="Auto"/>
                                    <Setter Property="Width" TargetName="ColumnDefinition1" Value="*"/>
                                    <Setter Property="Height" TargetName="RowDefinition0" Value="*"/>
                                    <Setter Property="Height" TargetName="RowDefinition1" Value="0"/>
                                </Trigger>
                                <Trigger Property="TabStripPlacement" Value="Right">
                                    <Setter Property="Grid.Row" TargetName="headerPanel" Value="0"/>
                                    <Setter Property="Grid.Row" TargetName="contentPanel" Value="0"/>
                                    <Setter Property="Grid.Column" TargetName="headerPanel" Value="1"/>
                                    <Setter Property="Grid.Column" TargetName="contentPanel" Value="0"/>
                                    <Setter Property="Width" TargetName="ColumnDefinition0" Value="*"/>
                                    <Setter Property="Width" TargetName="ColumnDefinition1" Value="Auto"/>
                                    <Setter Property="Height" TargetName="RowDefinition0" Value="*"/>
                                    <Setter Property="Height" TargetName="RowDefinition1" Value="0"/>
                                </Trigger>
                                <Trigger Property="IsEnabled" Value="false">
                                    <Setter Property="Effect" TargetName="templateRoot">
                                        <Setter.Value>
                                            <EffectLibrary:DesaturateEffect DesaturationFactor=".25"/>
                                        </Setter.Value>
                                    </Setter>
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
            <Style x:Key="TabItemStyle" TargetType="{x:Type TabItem}">
                <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
                <Setter Property="Foreground" Value="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}}"/>
                <Setter Property="Background" Value="{Binding Background, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}}"/>
                <Setter Property="BorderBrush" Value="{Binding BorderBrush, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}}"/>
                <Setter Property="Margin" Value="0"/>
                <Setter Property="Padding" Value="0,5"/>
                <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                <Setter Property="VerticalContentAlignment" Value="Stretch"/>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type TabItem}">
                            <Grid x:Name="templateRoot" SnapsToDevicePixels="true"  Background="{TemplateBinding Background}">
                                <Border x:Name="mainBorder" BorderBrush="{TemplateBinding BorderBrush}">
                                    <Border x:Name="highlightBorder"/>
                                </Border>
                                <ContentPresenter x:Name="contentPresenter" ContentSource="Header" Focusable="False" HorizontalAlignment="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
                            </Grid>
                            <ControlTemplate.Triggers>
                                <Trigger Property="IsEnabled" Value="false">
                                    <Setter Property="Effect" TargetName="templateRoot">
                                        <Setter.Value>
                                            <EffectLibrary:DesaturateEffect DesaturationFactor=".25"/>
                                        </Setter.Value>
                                    </Setter>
                                </Trigger>
                                <DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="true">
                                    <Setter TargetName="highlightBorder" Property="Background" Value="#0B79CE"/>
                                </DataTrigger>
                                <DataTrigger Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Top">
                                    <Setter TargetName="mainBorder" Property="BorderThickness" Value="0,0,1,0"/>
                                    <Setter TargetName="highlightBorder" Property="Height" Value="2"/>
                                    <Setter TargetName="highlightBorder" Property="VerticalAlignment" Value="Bottom"/>
                                </DataTrigger>
                                <DataTrigger Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Bottom">
                                    <Setter TargetName="mainBorder" Property="BorderThickness" Value="0,0,1,0"/>
                                    <Setter TargetName="highlightBorder" Property="Height" Value="2"/>
                                    <Setter TargetName="highlightBorder" Property="VerticalAlignment" Value="Top"/>
                                </DataTrigger>
                                <DataTrigger Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Left">
                                    <Setter TargetName="mainBorder" Property="BorderThickness" Value="0,0,0,1"/>
                                    <Setter TargetName="highlightBorder" Property="Width" Value="2"/>
                                    <Setter TargetName="highlightBorder" Property="HorizontalAlignment" Value="Right"/>
                                </DataTrigger>
                                <DataTrigger Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Right">
                                    <Setter TargetName="mainBorder" Property="BorderThickness" Value="0,0,0,1"/>
                                    <Setter TargetName="highlightBorder" Property="Width" Value="2"/>
                                    <Setter TargetName="highlightBorder" Property="HorizontalAlignment" Value="Left"/>
                                </DataTrigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </TabControl.Resources>
        <TabItem Header="Years">
            <ListBox Background="{DynamicResource Pallete.Primary.Brightest}" Foreground="{DynamicResource Pallete.Primary}">
                <TextBlock Text="2015"/>
                <TextBlock Text="2016"/>
                <TextBlock Text="2017"/>
            </ListBox>
        </TabItem>
        <TabItem Header="Tables">
            <ListBox  Background="{DynamicResource Pallete.Primary.Brightest}" Foreground="{DynamicResource Pallete.Primary}">
                <TextBlock Text="Table1..."/>
                <TextBlock Text="Table2..."/>
                <TextBlock Text="Table3..."/>
            </ListBox>
        </TabItem>
    </TabControl>
</Window>

希望我已经包含了所有的颜色,并且它对你有用。啊...糟糕!我的去饱和度效果!如果你想要,可以从我的WPF启动项目中获取该效果(将效果放入触发器中比重新着色更容易,高光也一样)。 是的,这是很多代码,但我只是简单地改变了ItemsContainer以使其看起来更好,并用UniformGrid替换了标准Header控件,并根据TabStripPlacement设置了行或列为1。现在我可以折叠这段代码,或者将其隐藏在某个地方。 :)

我发现了我的效果的一个不好的一面。当我在TreeView中突出显示交替项目时,这些项目变得模糊。因此,我制作了一个专用的背景边框,它里面什么都没有,就在ItemPresenter后面,这样它就不会受到这种效果的影响。 - zORg Alex

1

我通过创建一个特殊的转换器来解决这个问题:

    public class TabItemWidthAdjustmentConverter : IValueConverter
    {
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Double lTabControlWidth = value is Double ? (Double)value : 50; // 50 just to see something, in case of error
        Int32 lTabsCount = (parameter != null && parameter is String) ? Int32.Parse((String)parameter) : 1;
        return lTabControlWidth / lTabsCount;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
    }

我计算了TabControl中Tag元素中一个选项卡项目的值,以避免为每个选项卡单独计算。以下是示例代码(请注意,在我的情况下,我需要一个水平滚动视图器,因为我有多个选项卡项目,并且具有最小宽度):

<TabControl Name="tabControl" VerticalAlignment="Stretch" SelectionChanged="TabControl_SelectionChanged" 
                    Tag="{Binding RelativeSource={RelativeSource Self}, Path=ActualWidth, Converter={StaticResource tabItemWidthAdjustmentConverter}, ConverterParameter=15}"><!-- Here 15 because I have 15 tabs -->
            <TabControl.Template>
                <ControlTemplate TargetType="TabControl">
                    <StackPanel>
                        <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
                            <TabPanel x:Name="HeaderPanel"
                                      Panel.ZIndex="1"
                                      KeyboardNavigation.TabIndex="1"
                                      IsItemsHost="True"/>
                        </ScrollViewer>
                        <ContentPresenter x:Name="PART_SelectedContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                          Margin="{TemplateBinding Padding}"
                                          ContentSource="SelectedContent"/>
                    </StackPanel>
                </ControlTemplate>
            </TabControl.Template>
            <TabItem Header="Tab1" MinWidth="115" VerticalAlignment="Stretch" Width="{Binding ElementName=tabControl, Path=Tag}">
                <ContentControl ContentTemplate="{StaticResource My_TemplateTab1}">
                    <ContentPresenter />
                </ContentControl>
            </TabItem>
            <TabItem Header="Tab2" MinWidth="115" Height="50" Width="{Binding ElementName=tabControl, Path=Tag}">
                <ContentControl ContentTemplate="{StaticResource My_TemplateTab2}">
                    <ContentPresenter />
                </ContentControl>
            </TabItem>
            <!-- Here another 13 tabs which I skipped -->
            </TabControl>

我可以说,在我的情况下,这个工作得非常好 :) 希望有人会觉得它有用!
附注:在我的情况下,我不需要/想要任何样式。

你的解决方案是唯一有点用的!非常感谢。我遇到的唯一问题是在初始化时TabControl的ActualWidth为零。当我在Loaded事件处理程序中检索ActualWidth时,它为450。你知道为什么/如何解决吗? - Lumocra
1
请阅读此处的 WPF 窗口事件说明(特别是 Initialized 和 Loaded): http://blogs.msdn.com/b/mikehillberg/archive/2006/09/19/loadedvsinitialized.aspx - XMight
你的观点是,发生的事情是合乎逻辑的?至少这是我从博客文章中理解到的(顺便感谢您)。由于已经过去了几天,我不知道为什么它对我来说是个问题。现在它运行良好,代码在Loaded事件中。谢谢! :-) - Lumocra
是的,这是合乎逻辑的。这意味着在您的代码中,在实际值之前,您以某种方式访问了ActualWidth。很高兴现在它能正常工作! - XMight

0
我正在使用以下解决方案: 在主窗口中,我使用了一个窗口调整大小事件和TabControl初始化事件来设置每个选项卡的宽度。数字“5”对应于我的选项卡数量。
    private void tabchanger_Initialized(object sender, EventArgs e)
    {
        foreach (TabItem item in tabchanger.Items)
        {
            double newW = (tabchanger.ActualWidth / 5) - 1;
            if (newW < 0) newW = 0;

            item.Width = newW;
        }

    }

    private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        foreach (TabItem item in tabchanger.Items)
        {
            double newW = (tabchanger.ActualWidth / 5) - 1;
            if (newW < 0) newW = 0;

            item.Width = newW;
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接