通过重新样式化 WPF TabControl 创建类似 Outlook 的导航窗格






4.67/5 (50投票s)
将标准的 TabControl 重新样式化,使其看起来像 Outlook 导航窗格。
引言
不可否认,Office 2007 以其诸如 Ribbon 等伟大创新彻底改变了现代用户界面的外观。不过,本文将重点关注 Office 2007 导航窗格,更具体地说,是如何通过重塑 TabControl
来重新创建导航窗格!
WPF 团队通过允许“外观无关控件”进行重塑,在减少创建自定义控件的需求方面做得非常出色!这就是导航窗格的样子

而这就是我们标准的 TabControl
目前的样子

这是 XAML
<TabControl VerticalAlignment="Stretch" Width="360" Height="Auto"
TabStripPlacement="Bottom">
<TabItem Header="Mail">
<Grid/>
</TabItem>
<TabItem Header="Calendar">
<Grid/>
</TabItem>
<TabItem Header="Contacts">
<Grid/>
</TabItem>
<TabItem Header="Tasks">
<Grid/>
</TabItem>
</TabControl>
在开始重塑之前,为了保持本文的初学者定位,我将简要解释一下 Style
和 Template
是什么以及它们在此处是如何使用的!
什么是 Style?
Style
的主要功能是将可以单独设置的属性值组合在一起。目的是然后将这些值集共享给多个元素。如果您查看 XAML 中的一个简单 Style
,您会注意到它通常设置了 TargetType
,然后是一个 Setter
集合。每个 Setter
允许您用提供的值替换给定的 Property
值。这是一个非常基本的示例
<Style x:Key="SimpleStyle" TargetType="{x:Type TabControl}">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="Background" Value="Yellow" />
</Style>
虽然这是 Style
的标准定义,但在本文的重塑过程中,它更多地被用于替换 Template
属性。
什么是 Template?
Template
允许您完全用任何您能想象到的东西替换元素的视觉树,同时保留其所有功能。这是一个关于我们如何使用 style
/setter
来替换 Template
的示例。
<Style x:Key="SimpleStyle" TargetType="{x:Type TabControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<!-- The new visual tree should be placed here -->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
由于 Template
只是 Control
对象(TabControl
从中派生)的一个属性,因此也可以通过使用 setter
来替换它!我们现在可以替换完整的视觉树,同时保留 TabControl
的基本功能!
有关派生自 Control
的更多信息,请 此处 查看。
重塑 TabControl
TabControl
使用 TabPanel
来布局 TabControl
的标签(或按钮)。我们现在将用 StackPanel
替换它
<Style x:Key="OutlookTabControlStyle" TargetType="{x:Type TabControl}">
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background"
Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="BorderBrush" Value=
"{x:Static Microsoft_Windows_Themes:ClassicBorderDecorator.ClassicBorderBrush}"/>
<Setter Property="BorderThickness" Value="3"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="MinWidth" Value="10"/>
<Setter Property="MinHeight" Value="10"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid ClipToBounds="true" SnapsToDevicePixels="true"
KeyboardNavigation.TabNavigation="Local">
<Grid.RowDefinitions>
<RowDefinition x:Name="RowDefinition0" Height="Auto"/>
<RowDefinition x:Name="RowDefinition1" Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="ColumnDefinition0"/>
<ColumnDefinition x:Name="ColumnDefinition1" Width="0"/>
</Grid.ColumnDefinitions>
<Grid x:Name="ContentPanel" Grid.Column="0" Grid.Row="1"
KeyboardNavigation.DirectionalNavigation="Contained"
KeyboardNavigation.TabIndex="2"
KeyboardNavigation.TabNavigation="Local">
<Microsoft_Windows_Themes:ClassicBorderDecorator
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderStyle="Raised"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter SnapsToDevicePixels=
"{TemplateBinding SnapsToDevicePixels}" Margin="2,2,2,2"
x:Name="PART_SelectedContentHost"
ContentSource="SelectedContent"/>
</Microsoft_Windows_Themes:ClassicBorderDecorator>
</Grid>
<StackPanel HorizontalAlignment="Stretch" Margin="0,-2,0,0"
x:Name="HeaderPanel" VerticalAlignment="Bottom" Width="Auto"
Height="Auto" Grid.Row="1" IsItemsHost="True"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="TabStripPlacement" Value="Bottom">
<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="ContentPanel" 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="ContentPanel" Value="0"/>
<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="Foreground"
Value="{DynamicResource
{x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
所有这些都是由 Blend 自动生成的,只需右键单击 TabControl
并选择“编辑控件部件(Template
)”->“编辑副本...”

接下来,在 XAML 中只需将 TabPanel
替换为 StackPanel
。唯一需要设置在 StackPanel
上的重要属性是 IsItemsHost="true"
。

在用 StackPanel
替换 TabPanel
后,将每个 TabItems
的高度设置为 30
,将 TabControl
的 Background
设置为 White
,并将 BorderBrush
设置为 #FF6593CF
。

嗯,这样看起来好多了...
什么是 Resources?
Resources,更具体地说,ResourceDictionary
只是一个键/值对的字典,可以从 XAML 中访问。这里用于定义常用的画刷。这是一个简单的示例
<SolidColorBrush x:Key="OutlookButtonForeground" Color="#FF204D89"/>
这里是它的一个用法
Foreground="{DynamicResource OutlookButtonForeground}"
重塑 TabItem
接下来,通过右键单击其中一个 TabItem
并选择“编辑控件部件(Template
)”->“编辑副本...”来创建一个 TabItem
的样式。
在我深入研究生成的默认样式之前,我想先创建两个画刷资源。第一个画刷资源是导航窗格按钮的正常颜色
<LinearGradientBrush x:Key="OutlookButtonBackground" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFD9EDFF" Offset="0"/>
<GradientStop Color="#FFC0DEFF" Offset="0.445"/>
<GradientStop Color="#FFC0D9GB" Offset="1"/>
<GradientStop Color="#FFAFD1F8" Offset="0.53"/>
</LinearGradientBrush>
下一个是按钮的默认文本颜色
<SolidColorBrush x:Key="OutlookButtonForeground" Color="#FF204D89"/>
因此,每个 TabItem
现在应该看起来像这样
<TabItem Header="Tasks" Height="30" Style="{DynamicResource OutlookTabItemStyle}"
Background="{DynamicResource OutlookButtonBackground}"
Foreground="{an>DynamicResource OutlookButtonForeground}">
<Grid/>
</TabItem>
为 TabItem
创建 style
有两个目的:将 TabItem
的标题左对齐,并将 TabItem
的边框从 ClassicBorder
更改为普通的 Border(以便更好地控制)。
<Style x:Key="OutlookTabItemStyle" TargetType="{x:Type TabItem}">
<Setter Property="FocusVisualStyle" Value="{StaticResource TabItemFocusVisual}"/>
<Setter Property="Padding" Value="12,2,12,2"/>
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background"
Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border SnapsToDevicePixels="true" x:Name="Bd"
Background="{TemplateBinding Background}"
BorderThickness="1" BorderBrush="#FF6593CF">
<ContentPresenter SnapsToDevicePixels=
"{TemplateBinding SnapsToDevicePixels}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{Binding Path=VerticalContentAlignment,
RelativeSource={RelativeSource AncestorType=
{x:Type ItemsControl}}}"
ContentSource="Header" RecognizesAccessKey="True"
HorizontalAlignment="Left"/>
</Border>
<ControlTemplate.Triggers>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
现在看起来是这样的:

啊哈... 更好!那么,下一步是什么?
什么是 Triggers?
Triggers
像 Style
一样(和/或 TriggerActions
集合)有一个 Setter
s 的集合。但是,Style
无条件地应用其值,而 trigger
则基于一个或多个条件执行其工作。有三种类型的 triggers
属性触发器
— 当依赖属性的值发生更改时调用数据触发器
— 当普通 .NET 属性的值发生更改时调用事件触发器
— 当路由事件被引发时调用
我们将使用一个 Event Trigger
。TabItem
为我们提供了一个 IsSelected
路由事件!
选择
我们现在需要指示(通过更改颜色)一个项目已被选中。再次,我们创建一个画刷来表示按钮被高亮后需要更改的颜色
<LinearGradientBrush x:Key="OutlookButtonHighlight" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFFFBD69" Offset="0"/>
<GradientStop Color="#FFFFB75A" Offset="0.0967"/>
<GradientStop Color="#FFFFB14C" Offset="0.2580"/>
<GradientStop Color="#FFFB8C3C" Offset="0.3870"/>
<GradientStop Color="#FFFEB461" Offset="0.9677"/>
<GradientStop Color="#FFFEBB67" Offset="1"/>
</LinearGradientBrush>
这是 trigger
<ControlTemplate.Triggers>
<Trigger Property="Selector.IsSelected" Value="True">
<Setter Property="Background" TargetName="Bd"
Value="{DynamicResource OutlookButtonHighlight}"/>
</Trigger>
</ControlTemplate.Triggers>
看起来像这样:

这样就好多了...
基本上,这就是重塑 TabControl
所需的全部内容!
历史
- 2008 年 3 月 4 日 - 首次发布
- 2008 年 3 月 5 日 - 更改了标题,更新了颜色
- 2008 年 6 月 23 日 - 添加了源文件
如果您喜欢这篇文章,请投赞成票,并 访问我的博客。
谢谢!