How to Stretch WPF Tab Item Headers to Parent Control Width

后端 未结 11 1921
花落未央
花落未央 2020-11-29 23:17

Is there a way in XAML to cause the tab item headers to stretch across the width of the tab control?

For example, I have three tabs: red, blue and green. If I have a

相关标签:
11条回答
  • 2020-11-29 23:44

    I am an old school style guy. and prefer this kind of functionality to encapsulate into the code of the control itself. My derived control looks like following:

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

    and my XAML looks like

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

    Can someone explain why everyone prefers styling control instead of deriving.

    0 讨论(0)
  • 2020-11-29 23:45

    I have solved this problem by creating a special converter:

        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();
        }
        }
    

    And I calculate the value of one tab item in the Tag element of the TabControl, to avoid calculating it for each tab separately. Here is the sample code (Note that In my case I needed a horizontal ScrollViewer, because I have multiple tab items, and with a minimum width):

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

    I can say that this works like a charm in my case :) Hope someone will find it useful!

    P.S. I did not need/want any style in my case.

    0 讨论(0)
  • 2020-11-29 23:54

    In addition to Ryan Versaw's accepted solution which gives equal tabItem header widths, I found the following way to make it dependent on the length of each header.

    First we get the string of each tabItem header by adding this line to the xaml multibinding. Thus it becomes:

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

    And a bit more of code in the Converter (values[] gets also the 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;
        }
    

    I suspect there might be a more efficient way to get the AllHeaders string of all the headers, but it works fine as it is ...

    0 讨论(0)
  • 2020-11-29 23:56

    Here is a painless solution that uses Templates only:

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

    Hope I've included all colors and it will work for you. Ahh... Snap! My Desaturation Effect! My WPF startup project you can grab that effect from there if you want (It's easier to plop the effect in trigger, than to recolor all the thing, same with highlights). Yes, that's a lot of code, but I simply changed ItemsContainer to look better and replaced standard Header control with UniformGrid and set Rows or Columns to 1 depending on TabStripPlacement. Now I can collapse this code, or hide it somewhere. :)

    0 讨论(0)
  • 2020-11-29 23:58

    Everyone seems to be going the converter route, but it really is as simple as using a UniformGrid with Rows set to 1 in the TabControl template, in place of the TabPanel. Of course, you will have to re-template it, but this isn't too bad.

    0 讨论(0)
提交回复
热议问题