如何将 WPF 选项卡项标题拉伸到父控件宽度

Posted

技术标签:

【中文标题】如何将 WPF 选项卡项标题拉伸到父控件宽度【英文标题】:How to Stretch WPF Tab Item Headers to Parent Control Width 【发布时间】:2010-10-16 21:21:04 【问题描述】:

在 XAML 中是否有一种方法可以使选项卡项标题延伸到选项卡控件的宽度?

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

我知道如何在我现在正在处理的代码中执行此操作,但我有兴趣以最简单的方式执行此操作。

【问题讨论】:

【参考方案1】:

我以乔丹为例,对其进行了一些修改。此版本适用于任意数量的标签:

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>

【讨论】:

我会将转换器的返回值设置为返回宽度 当使用这个 tabitems 堆叠在一起时,但是如果我将 (width - 1) 更改为 (width - 2.1) 那么它“工作”,但我似乎有一个像素空间在 tabitems 的两侧。我做错了什么? 当标签项的数量 return (width <= 1) ? 0 : (width - 1.34);,这似乎可以处理所有数量的选项卡项。 @RyanVersaw:为什么不实现 IValueConverter 而不是 IMultiValueConverter? 它是IMultiValueConverter 而不是IValueConverter 的原因是,如果没有第二次绑定到ActualWidth,绑定将在控件呈现之前绑定,导致ActualWidth 在内部始终为零转换器。将第二个绑定添加到 ActualWidth 会强制绑定在每次父 TabControl 的宽度发生变化时重新评估。这有点麻烦,但确实有效。【参考方案2】:

似乎每个人都在使用转换器路线,但它实际上就像在 TabControl 模板中使用 Rows 设置为 1 的 UniformGrid 一样简单,而不是 TabPanel。当然,您将不得不重新模板化,但这还不错。

【讨论】:

它运行良好,而且简单得多(考虑到你喜欢搞乱模板......但你这样做,对吧?) 这样的例子很好,对于那些喜欢复制和粘贴的人来说:) 使用示例:***.com/questions/28496809/… 哇,比转换器方法更容易、更可靠,谢谢! 更简单,甚至不需要与 C# 交互。完美的解决方案。【参考方案3】:

我可以使用这样的转换器来做到这一点:

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,如果有意义的话,我希望选项卡占其 1/4。

【讨论】:

对于代码下方的大量空白,我深表歉意。我不知道它为什么这样做。 此外,您可以根据需要使转换器变得复杂。你可以去拿 TabControl,看看它的 Items 有多少,然后用它来计算你的宽度。 乔丹:谢谢伙计,这是一种享受 :) “对象值”以双精度形式进入转换函数,因此您可以将其转换为“双高度 = (双)值;” +1 顺便说一句:)【参考方案4】:

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

    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>

谁能解释为什么每个人都喜欢样式控制而不是派生。

【讨论】:

【参考方案5】:

可以通过将宽度绑定到父选项卡控件的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 占据整个宽度。需要什么,他们需要共享宽度。【参考方案6】:

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

    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 元素中计算一个选项卡项的值,以避免为每个选项卡单独计算它。这是示例代码(请注意,在我的情况下,我需要一个水平 ScrollViewer,因为我有多个选项卡项,并且具有最小宽度):

<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。你知道为什么/如何解决这个问题吗? 请阅读此处对 WPF 窗口事件的描述(尤其是已初始化和已加载):blogs.msdn.com/b/mikehillberg/archive/2006/09/19/… 你的意思是,发生的事情是合乎逻辑的吗?至少那是我从博文中编造出来的(顺便谢谢你)因为那是几天前的事了,我不知道为什么这对我来说是个问题。现在运行良好,代码在 Loaded 事件中。谢谢! :-) 是的,这是合乎逻辑的。这意味着您在代码中以某种方式访问​​了 ActualWidth,然后才获得任何实际值。很高兴它现在对你有用!【参考方案7】:

这是一个仅使用模板的无痛解决方案:

<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>

希望我已经包含了所有颜色,它对你有用。啊……啪!我的去饱和效果! My WPF startup project 如果需要,您可以从那里获取该效果(在触发器中触发效果比重新着色所有内容更容易,与高光相同)。 是的,这是很多代码,但我只是将 ItemsContainer 更改为更好看,并用 UniformGrid 替换标准 Header 控件,并根据 TabStripPlacement 将 Rows 或 Columns 设置为 1。现在我可以折叠这段代码,或者把它藏在某个地方。 :)

【讨论】:

我发现效果不好的一面。当我在 TreeView 中突出显示交替项目时,这些项目变得模糊。所以我做了专门的背景边框,里面什么都没有,就在ItemPresenter后面,所以它不会受到这个效果的影响。【参考方案8】:

我听从了查理的建议,继续重新设计路线。这是TabControl 的一个简单实现,它使用UniformGridTabItems 之间平均分配可用空间:

控件的 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;
      
    
  

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

【讨论】:

为什么您使用边距来调整选项卡的大小,而不是将 RowDefinition0 的高度设置为静态数字而不是“自动”?这是没有意义的,是一种倒退的做事方式。 我没有使用边距来调整选项卡的大小。由于设置了UniformGrid.Columns,选项卡会自动调整大小(UniformGrid 就是这样做的)。另请注意,我们不能将 RowDefinition0 的高度设置为静态数字,因为用户可以使用任何高度的标题。最后,如果您对答案投了反对票,如果您不理解答案中的某些内容,请向 OP 询问总是善意和体贴的。 据我所知,情况正好相反。选项卡内容的高度是静态的 (300),并且选项卡本身的高度会填充剩余的可用空间。通过将 RowDefinition0 的 Height 属性设置为 40,并删除 UniformGrid 上的边距,我能够得到想要的效果。为什么 UniformGrid 上有 300 的下边距?那有什么用?也许这是与我将选项卡放入的 DockPanel 和 LastChildFill 的一些时髦的交互,但最终做出这些更改产生了预期的效果,这让我相信它应该是这样的。 @AnthonyStivers:感谢您的指出。 XAML 实际上有两个问题。首先,Margin 设置不应该存在;不知道它是如何通过的。其次,Grid.Row 应该是 0,而不是 1。除了这两个,其余的都是正确的,对我来说工作得很好。 啊,是的,我想我在代码中也将Grid.Row 更改为0。将RowDefinition0 的高度设置为自动确实有效。就我而言,我将其设置为 40,因为我想要更高且更明显的标签。我很高兴能够帮助改进答案。【参考方案9】:

我不知道它是否适用于标签,但每当我需要拉伸任何东西来填充容器时,我都会使用 ViewBox。这就是你要找的东西吗?

【讨论】:

【参考方案10】:

我正在使用以下解决方案: 在主窗口中,我使用窗口调整大小事件和 tabcontrol Initialized 事件来设置每个选项卡的宽度。数字“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;
        
    

【讨论】:

【参考方案11】:

除了 Ryan Versaw 公认的提供相等 tabItem 标题宽度的解决方案之外,我还发现了以下方法使其取决于每个标题的长度。

首先,我们通过将此行添加到 xaml 多重绑定来获取每个 tabItem 标头的字符串。这样就变成了:

<MultiBinding Converter="StaticResource tabSizeConverter">
           <Binding RelativeSource="RelativeSource Mode=FindAncestor, AncestorType=x:Type TabControl" />
           <Binding RelativeSource="RelativeSource Mode=FindAncestor, AncestorType=x:Type TabControl" Path="ActualWidth" />
           <Binding Path="Header" RelativeSource="RelativeSource Self"/>
</MultiBinding>

转换器中还有一些代码(values[] 也获取 tabItem Header):

public object Convert(object[] values, Type targetType, object parameter,
        System.Globalization.CultureInfo culture)
    
        TabControl tabControl = values[0] as TabControl;

        string AllHeaders = "";
        for (int i = 0; i < tabControl.Items.Count; i++)
        
            int index = tabControl.Items[i].ToString().IndexOf("Header:") + "Header:".Length;
            string currentHeader = tabControl.Items[i].ToString().Substring(index);
            currentHeader = currentHeader.Substring(0, currentHeader.Length - " Content:".Length);
            AllHeaders += currentHeader;
        

        //Normalize width according to header length
        double width = values[2].ToString().Length * tabControl.ActualWidth / AllHeaders.Length;

        //Subtract 1, otherwise we could overflow to two rows.
        var retVal = (width <= 1) ? 0 : (width - 1);
        return retVal;
    

我怀疑可能有一种更有效的方法来获取所有标题的 AllHeaders 字符串,但它可以正常工作......

【讨论】:

【参考方案12】:

这是最快捷的解决方案:

<TabControl.Template>
    <ControlTemplate TargetType="TabControl">
        <DockPanel>
            <UniformGrid IsItemsHost="True" Rows="1" DockPanel.Dock="Top"></UniformGrid>
            <ContentPresenter ContentSource="SelectedContent"></ContentPresenter>
        </DockPanel>
    </ControlTemplate>
</TabControl.Template>

【讨论】:

以上是关于如何将 WPF 选项卡项标题拉伸到父控件宽度的主要内容,如果未能解决你的问题,请参考以下文章

如何在 TabControl 中的选项卡项中保留控件状态

在 WPF 中开始时绑定所有选项卡项视图模型

如何创建可重用的 WPF 网格布局

Win32 选项卡控件中的右对齐选项卡项

2021-08-19 WPF控件专题 TabControl 控件详解

如何将 extjs Carousel 用于选项卡项