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
相关产品推荐
相关产品推荐

