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

WPF 选项卡标题控件,带滚动按钮、可移动的选项卡项和每个选项卡中的关闭按钮

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2020年4月30日

CPOL

5分钟阅读

viewsIcon

16957

downloadIcon

629

使用两个按钮控件和一个修改后的列表框(列表项水平排列)的 WPF Tab 标题控件。

引言

对于许多应用程序来说,拥有一个可以滚动、重新排列和关闭的选项卡项的选项卡控件是很有用的。幸运的是,使用标准的 WPF 组件来实现这一目标并不难。

本文介绍了一个选项卡标题控件,其选项卡项可以通过左右箭头按钮进行滚动,并通过简单的拖动操作进行重新排列。我决定创建一个选项卡标题控件,而不是一个完整的选项卡控件,以允许更大的定制程度。将此选项卡标题控件与其他 WPF 控件结合以创建完整的选项卡控件非常容易。

该选项卡标题控件包含许多依赖属性,可用于自定义,例如用于选中和未选中选项卡背景的画笔。

示例代码包含以下演示应用程序

顶部的选项卡控件包含四个选项卡项。在选项卡标题的两侧都有左右滚动按钮。默认情况下,滚动按钮显示为简单的绿色三角形。最右边有一个按钮,会弹出一个菜单,允许用户选择其中一个选项卡项,如下所示

中间的选项卡控件的选项卡位于选项卡内容窗格下方,并且滚动按钮具有自定义模板。

底部的选项卡控件的选项卡位于选项卡内容窗格上方,并且每个选项卡项都有自定义模板。

主窗口底部的控件允许用户配置字体、选项卡边框和背景颜色。请注意,这些设置不适用于第三个选项卡控件,因为它使用了自定义项容器样式。

每个选项卡控件的一个关键特性是能够通过拖动选项卡项到新位置来重新排列它们。

背景

您需要对 C# 和 Windows 开发有深入的了解,以及对 WPF 的基本知识。

Using the Code

第一个选项卡控件是从 XAML 中使用一个选项卡标题控件、一个按钮和一个简单的标签来显示当前选项卡项的内容来构建的。选项卡标题控件的定义如下

        <wpfcontrollibrary:TabHeaderControl Grid.Row="1" Grid.Column="0" 
        x:Name="_tabHeader1" ItemsSource="{Binding ListBoxItems}" 
        SelectedItem="{Binding SelectedHeader, Mode=TwoWay}">
            <wpfcontrollibrary:TabHeaderControl.DisplayMemberPath>
            HeaderText</wpfcontrollibrary:TabHeaderControl.DisplayMemberPath>
            <wpfcontrollibrary:TabHeaderControl.SelectedTabBackground>
                <Binding Path="SelectedTabBackground" Mode="TwoWay"/>
            </wpfcontrollibrary:TabHeaderControl.SelectedTabBackground>
            <wpfcontrollibrary:TabHeaderControl.UnselectedTabBackground>
                <Binding Path="UnselectedTabBackground" Mode="TwoWay"/>
            </wpfcontrollibrary:TabHeaderControl.UnselectedTabBackground>
            <wpfcontrollibrary:TabHeaderControl.SelectedTabBorderThickness>
                <Binding Path="SelectedTabBorderThickness_Top"/>
            </wpfcontrollibrary:TabHeaderControl.SelectedTabBorderThickness>
            <wpfcontrollibrary:TabHeaderControl.SelectedTabForeground>Black
            </wpfcontrollibrary:TabHeaderControl.SelectedTabForeground>
            <wpfcontrollibrary:TabHeaderControl.UnselectedTabForeground>White
            </wpfcontrollibrary:TabHeaderControl.UnselectedTabForeground>
            <wpfcontrollibrary:TabHeaderControl.FontSize>
                <Binding Path="FontSize"/>
            </wpfcontrollibrary:TabHeaderControl.FontSize>
            <wpfcontrollibrary:TabHeaderControl.FontFamily>
                <Binding Path="FontFamily"/>
            </wpfcontrollibrary:TabHeaderControl.FontFamily>
            <wpfcontrollibrary:TabHeaderControl.DisabledArrowBrush>Transparent
            </wpfcontrollibrary:TabHeaderControl.DisabledArrowBrush>
        </wpfcontrollibrary:TabHeaderControl>

以上大部分内容应该不言自明。通过将 `TabHeaderItem` 实例的可观察集合分配给 `ItemsSource` 属性来填充选项卡列表。`TabHeaderItem` 类定义如下

class TabHeaderItem
{
    public string Label { get; set; }
    public int ID { get; set; }

    public string HeaderText
    {
        get
        {
            return Label + " : " + ID;
        }
    }
}

`DisplayMemberPath` 属性的用法与 `ListBox` 的 `DisplayMemberPath` 属性完全相同。它被设置为 "HeaderText",因此每个选项卡都会显示关联的 `TabHeaderItem` 实例的 `HeaderText` 属性返回的文本。

有许多属性控制选项卡项的外观。因此,`UnselectedTabBackground` 属性定义了未选中选项卡的背景画笔。

第二个选项卡控件类似,只是选项卡标题项位于选项卡内容下方,并且滚动按钮已重新设计样式。

第三个选项卡控件最有趣,因为它重写了 `TabHeaderControl` 的 `ItemContainerStyle` 属性。这允许完全自定义选项卡的外观和功能。在这种情况下,大多数属性(如 `UnselectedTabBackground`)将不被使用。该示例定义了一个带有文本字符串和关闭按钮的选项卡项,关闭功能在代码隐藏中实现。`ItemContainerStyle` 属性的定义如下

<wpfcontrollibrary:TabHeaderControl Grid.Row="7" Grid.Column="0" 
 x:Name="_tabHeader3" ItemsSource="{Binding ListBoxItems}" 
 SelectedItem="{Binding SelectedHeader, Mode=TwoWay}">
    <wpfcontrollibrary:TabHeaderControl.ItemContainerStyle>
        <Style TargetType="ListBoxItem">
            <Setter Property="FrameworkElement.Margin" Value="0"/>
            <Setter Property="FocusVisualStyle" Value="{x:Null}" />
            <Setter Property="Padding" Value="0" />
            <Setter Property="Margin" Value="0" />
            <EventSetter Event="PreviewMouseLeftButtonDown" 
             Handler="ListBoxItem_PreviewMouseLeftButtonDown" />

            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ListBoxItem">
                        <Border Background="{TemplateBinding Background}" 
                         Padding="4" 
                         SnapsToDevicePixels="true" BorderThickness="0" 
                         CornerRadius="20,10,0,0">
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="4"/>
                                    <ColumnDefinition Width="Auto"/>
                                    <ColumnDefinition Width="2"/>
                                    <ColumnDefinition Width="Auto"/>
                                    <ColumnDefinition Width="2"/>
                                </Grid.ColumnDefinitions>
                                <Label Grid.Column="1" FontSize="14" 
                                 Foreground="{TemplateBinding Foreground}" 
                                 Background="{TemplateBinding Background}" 
                                 Width="Auto" Padding="2,0,2,0" Margin="0" 
                                 VerticalAlignment="Center">
                                    <TextBlock>
                                        <Run Text="{Binding Label}"/><Run Text=" ("/>
                                        <Run Text="{Binding ID}"/><Run Text=")"/>
                                    </TextBlock>
                                </Label>
                                <Button Grid.Column="3" Width="20" Height="20" 
                                 Content="X" FontSize="12" 
                                 Background="{TemplateBinding Background}" 
                                 Foreground="{TemplateBinding Foreground}" 
                                 BorderThickness="0" Click="Button_Click"/>
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="IsSelected" Value="True">
                    <Setter Property="Background" Value="Orange"/>
                    <Setter Property="Foreground" Value="Black"/>
                </Trigger>
                <Trigger Property="IsSelected" Value="False">
                    <Setter Property="Background" Value="DarkSlateBlue"/>
                    <Setter Property="Foreground" Value="White"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </wpfcontrollibrary:TabHeaderControl.ItemContainerStyle>
    <wpfcontrollibrary:TabHeaderControl.FontFamily>
        <Binding Path="FontFamily"/>
    </wpfcontrollibrary:TabHeaderControl.FontFamily>
</wpfcontrollibrary:TabHeaderControl>

触发器用于为选中的和未选中的选项卡设置样式。

值得注意的一点是以下数据模板的提取

<EventSetter Event="PreviewMouseLeftButtonDown" 
 Handler="ListBoxItem_PreviewMouseLeftButtonDown" />

`ListBoxItem_PreviewMouseLeftButtonDown` 处理程序在按钮单击事件之前被调用。发送者参数是 `ListBoxItem`,允许代码隐藏确定与后续按钮按下相关的选项卡项的索引。处理程序代码如下

private void ListBoxItem_PreviewMouseLeftButtonDown
(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    System.Windows.Controls.ListBoxItem listBoxItem = 
                   sender as System.Windows.Controls.ListBoxItem;
    if (listBoxItem == null)
    {
        return;
    }

    TabHeaderItem tabHeaderItem = listBoxItem.DataContext as TabHeaderItem;
    if (tabHeaderItem == null)
    {
        return;
    }

    _listBoxItemIndex = (DataContext as MainWindowModel).ListBoxItems.IndexOf(tabHeaderItem);
}

因此,`_listBoxItemIndex` 值定义了与后续关闭按钮单击事件相关的列表框项的索引。

自定义

可以使用以下依赖属性修改控件的外观和行为

ItemsSource 获取或设置用于生成选项卡内容的集合
SelectedItem 获取或设置当前选中的项
SelectedIndex 获取或设置当前选中选项卡的索引,如果没有选中选项卡则返回 `-1`
SelectedValue

获取或设置当前选中的值

SelectedValuePath 获取或设置用于从 `SelectedItem` 获取 `SelectedValue` 的路径
ArrowTemplate 获取或设置箭头按钮的控件模板。右侧按钮与左侧按钮相同,只是旋转了 180 度。
DisplayMemberPath 获取或设置源对象上用于作为对象视觉表示的值的路径
SelectedTabBackground 获取或设置用于选中选项卡背景的画笔
UnselectedTabBackground 获取或设置用于未选中选项卡背景的画笔
SelectedTabBorderThickness 获取或设置选中选项卡的边框厚度
SelectedTabForeground 获取或设置用于选中选项卡前景的画笔
UnselectedTabForeground 获取或设置用于未选中选项卡前景的画笔
ItemContainerStyle 获取或设置应用于为每个项生成的容器元素的样式
   

`ItemContainerStyle` 是最强大的依赖属性,因为它允许完全自定义每个选项卡项。演示应用程序包含一个具有自定义 `ItemContainerStyle` 的选项卡标题控件。

此外,控件中显示的文本遵循用户控件的标准 `FontSize`、`FontFamily` 和 `FontStyle` 依赖属性。

实现

选项卡标题控件是使用两个按钮控件和一个修改过的列表框实现的,列表项水平排列。列表框由 `TabHeader` 类实现,该类继承自标准的 `ListBox` 类,并增加了重新排列项的功能。代码相对容易理解,尽管弄清楚如何实现所需的功能并不容易!

注释

选项卡标题控件不支持垂直选项卡。通过修改底层 `TabHeader` 类,添加此功能并不难。

历史

  • 2020年4月30日:首个版本
  • 2021年1月1日:用一个更有用的示例替换了原来的下载。
© . All rights reserved.