65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (50投票s)

2008 年 3 月 5 日

CPOL

4分钟阅读

viewsIcon

275193

downloadIcon

8523

将标准的 TabControl 重新样式化,使其看起来像 Outlook 导航窗格。

引言

不可否认,Office 2007 以其诸如 Ribbon 等伟大创新彻底改变了现代用户界面的外观。不过,本文将重点关注 Office 2007 导航窗格,更具体地说,是如何通过重塑 TabControl 来重新创建导航窗格!

WPF 团队通过允许“外观无关控件”进行重塑,在减少创建自定义控件的需求方面做得非常出色!这就是导航窗格的样子

OutlookPanel.jpg

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

TabControl.jpg

这是 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)”->“编辑副本...”

BlendTemplateEditCopy.jpg

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

HeaderControl.jpg

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

AfterApplyingBasicStyle.jpg

嗯,这样看起来好多了...

什么是 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>

现在看起来是这样的: "BetterStyle.jpg"

BetterStyle.jpg

啊哈... 更好!那么,下一步是什么?

什么是 Triggers?

Triggers Style 一样(和/或 TriggerActions 集合)有一个 Setters 的集合。但是,Style 无条件地应用其值,而 trigger 则基于一个或多个条件执行其工作。有三种类型的 triggers

  • 属性触发器 — 当依赖属性的值发生更改时调用
  • 数据触发器 — 当普通 .NET 属性的值发生更改时调用
  • 事件触发器 — 当路由事件被引发时调用

我们将使用一个 Event TriggerTabItem 为我们提供了一个 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>

看起来像这样:

EvenBeter.jpg

这样就好多了...

基本上,这就是重塑 TabControl 所需的全部内容!

历史

  • 2008 年 3 月 4 日 - 首次发布
  • 2008 年 3 月 5 日 - 更改了标题,更新了颜色
  • 2008 年 6 月 23 日 - 添加了源文件

如果您喜欢这篇文章,请投赞成票,并 访问我的博客
谢谢!

© . All rights reserved.