You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF:如何基于TabControl属性设置TabItem样式?实现Notepad++式分栏标签

实现类似Notepad++的双栏TabControl区分选中与激活状态

我刚好做过类似的需求,给你一套完整的解决方案,核心是通过自定义TabControl扩展属性,结合触发器来区分「单纯选中」和「选中+所属栏激活」两种状态:

1. 先创建带Active属性的自定义TabControl

原生TabControl没有我们需要的「激活状态」属性,所以先扩展一个TabControlEx,添加Active依赖属性(只有依赖属性才能在XAML触发器中绑定):

public class TabControlEx : TabControl
{
    public static readonly DependencyProperty ActiveProperty =
        DependencyProperty.Register(nameof(Active), typeof(bool), typeof(TabControlEx), new PropertyMetadata(true));

    public bool Active
    {
        get => (bool)GetValue(ActiveProperty);
        set => SetValue(ActiveProperty, value);
    }
}

这个属性用来标记当前TabControl是否处于「激活栏」状态,后续切换双栏时,只需要把激活的TabControl设为True,另一个设为False即可。

2. 修改TabItem的样式,添加状态区分触发器

接下来修改你的DocumentTabItem样式,用DataTrigger结合IsSelected和父级TabControlEx.Active属性,实现两种选中状态的样式差异:

<Style x:Key="DocumentTabItem" TargetType="TabItem" BasedOn="{StaticResource BaseRootTabItem}">
    <Style.Setters>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="TabItem">
                    <Border x:Name="TabBorder" BorderThickness="1,1,1,0" BorderBrush="Transparent" 
                            Background="{TemplateBinding Background}" TextBlock.Foreground="{TemplateBinding Foreground}">
                        <ContentPresenter x:Name="ContentSite" HorizontalAlignment="Center" VerticalAlignment="Center" 
                                          ContentSource="Header" Margin="6,2" />
                    </Border>
                    <ControlTemplate.Triggers>
                        <!-- 情况1:标签被选中,但所属TabControl未激活 -->
                        <Trigger Property="IsSelected" Value="True">
                            <Setter Property="Background" Value="{StaticResource SelectedDocumentTabBackgroundBrush}" />
                            <Setter Property="Foreground" Value="{StaticResource SelectedDocumentTabForegroundBrush}" />
                        </Trigger>
                        <!-- 情况2:标签被选中,且所属TabControl处于激活状态 -->
                        <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:TabControlEx}}, Path=Active}" 
                                     Value="True">
                            <DataTrigger.Conditions>
                                <Condition Property="IsSelected" Value="True" />
                            </DataTrigger.Conditions>
                            <Setter Property="Background" Value="{StaticResource ActiveSelectedDocumentTabBackgroundBrush}" />
                            <Setter Property="Foreground" Value="{StaticResource ActiveSelectedDocumentTabForegroundBrush}" />
                            <!-- 可以额外加边框区分激活状态 -->
                            <Setter TargetName="TabBorder" Property="BorderBrush" Value="{StaticResource ActiveTabBorderBrush}" />
                        </DataTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style.Setters>
</Style>

这里用DataTrigger.Conditions同时判断两个条件:标签被选中,且它所在的TabControlEx是激活状态,此时应用更醒目的样式(比如更深的背景、高亮边框)。

3. 实际使用示例

在XAML中放置两个TabControlEx,并添加切换激活状态的逻辑(比如点击栏时切换):

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <!-- 左侧Tab栏 -->
    <local:TabControlEx x:Name="LeftTabControl" Active="True"
                        ItemsSource="{Binding LeftDocuments}" ItemContainerStyle="{StaticResource DocumentTabItem}">
        <!-- 内容模板 -->
    </local:TabControlEx>

    <!-- 右侧Tab栏 -->
    <local:TabControlEx x:Name="RightTabControl" Active="False" Grid.Column="1"
                        ItemsSource="{Binding RightDocuments}" ItemContainerStyle="{StaticResource DocumentTabItem}">
        <!-- 内容模板 -->
    </local:TabControlEx>

    <!-- 点击Tab栏时切换激活状态 -->
    <Grid.Triggers>
        <EventTrigger RoutedEvent="MouseDown" SourceName="LeftTabControl">
            <BeginStoryboard>
                <Storyboard>
                    <BooleanAnimationUsingKeyFrames Storyboard.TargetName="LeftTabControl" Storyboard.TargetProperty="Active">
                        <DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True"/>
                    </BooleanAnimationUsingKeyFrames>
                    <BooleanAnimationUsingKeyFrames Storyboard.TargetName="RightTabControl" Storyboard.TargetProperty="Active">
                        <DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="False"/>
                    </BooleanAnimationUsingKeyFrames>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
        <EventTrigger RoutedEvent="MouseDown" SourceName="RightTabControl">
            <BeginStoryboard>
                <Storyboard>
                    <BooleanAnimationUsingKeyFrames Storyboard.TargetName="RightTabControl" Storyboard.TargetProperty="Active">
                        <DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True"/>
                    </BooleanAnimationUsingKeyFrames>
                    <BooleanAnimationUsingKeyFrames Storyboard.TargetName="LeftTabControl" Storyboard.TargetProperty="Active">
                        <DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="False"/>
                    </BooleanAnimationUsingKeyFrames>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Grid.Triggers>
</Grid>

补充提示

  • 记得提前定义好对应的资源画笔,比如ActiveSelectedDocumentTabBackgroundBrush、ActiveTabBorderBrush等,用来直观区分不同状态。
  • 这种方式完全基于WPF的依赖属性和触发器机制,不需要额外的后台事件处理,样式逻辑和控件状态绑定得非常清晰。

内容的提问来源于stack exchange,提问作者Spook

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 20:03:14