为 WPF TabControl 配置动态 headers

Configuring dynamic headers for a WPF TabControl

我正在尝试制作一个基本的 window,它使用 WPF TabControl 来允许用户添加新选项卡。我希望最终产品看起来有点像选项卡在网络浏览器中的工作方式,其中最后一个选项卡只是一个“+”,单击它会添加一个新选项卡。

我正在尝试编写 XAML 代码来设置它,我发现我可以在 "TabControl.Resources" 中指定多个 DataTemplates,并且基于 "DataType" 正确的 DataTemplate 将是用于显示每个选项卡的正确视图...但是在处理选项卡 headers 时,我只能为 "TabControl.ItemTemplate"

指定一个 DataTemplate

这是我目前拥有的:

<TabControl ItemsSource="{Binding Tabs}">
    <TabControl.Resources>

        <!-- If the tab is of type "TabViewModel" I want this content -->
        <DataTemplate DataType="x:Type vm:TabViewModel">
            <!-- TabView is defined as a separate user control -->
            <v:TabView/>
        </DataTemplate>

        <!-- If the tab is of type "NewTabViewModel" I want this content -->
        <DataTemplate DataType="x:Type vm:NewTabViewModel">
            <!-- NewTabView is defined as a separate user control -->
            <v:NewTabView/>
        </DataTemplate>
    </TabControl.Resources>

    <TabControl.ItemTemplate>
        <!-- if the tab is of type "TabViewModel" I want this header -->
        <DataTemplate DataType="x:Type vm:TabViewModel">
            <TextBlock Text="{Binding Name}"/>
        </DataTemplate>

        <!-- If the tab is of type "NewTabViewModel" I want this header -->
        <!-- ERROR: Adding a second "DataTemplate" here results in an error -->
        <DataTemplate DataType="x:Type vm:NewTabViewModel">
            <TextBlock Text="+"/>
        </DataTemplate>
    </TabControl.ItemTemplate>
</TabControl>

谷歌搜索我发现了一些关于设置 TemplateSelector 和编写一堆后台 C# 代码的文章,但对于这么简单的事情来说,这似乎太过分了。我只希望它在常规 TabViewModel object 时显示选项卡名称,在 NewTabViewModel object.

时显示“+”

这里有两种完全不同的 DataTemplate 用法。

ItemTemplate 属性 是 DataTemplate 类型。具体来说,一个 DataTemplate,而不是 collection。它期望您将其设置为您需要的任何模板,这就是它将用来填充选项卡 header 的模板。此外,对于 TabControl,这是将应用于 all headers 的模板;您无法在 per-tab 基础上进行更改。

选项卡面板本身属于 ContentControl 类型,每个面板都绑定到一个视图模型。 ContentControl 包含一个 ContentPresenter,它遍历逻辑树,为它所绑定的数据类型寻找 DataTemplate(在内部,DataTemplate 的 DataType 属性 只是将类型本身设置为 x:Key).

你的问题是你试图像 ResourceDictionary 一样使用 ItemTemplate,指定多个 DataTemplates,当它期望你提供模板本身时,只有一个。因此,要实现您所追求的目标,您需要做的就是给它一个 DataTemplate 并用 ContentPresenter 填充它(如 Dreamer 所建议的那样),就像选项卡面板本身一样。这个 ContentPresenter 有它自己的 ResourceDictionary,那是你可以放置你的 header 模板的地方:

<TabControl ItemsSource="{Binding Tabs}">

    <TabControl.Resources>

        <!-- Panel templates -->

        <DataTemplate DataType="{x:Type vm:TabViewModel}">
            <v:TabView />
        </DataTemplate>

        <DataTemplate DataType="{x:Type vm:NewTabViewModel}">
            <v:NewTabView />
        </DataTemplate>
    </TabControl.Resources>

    <TabControl.ItemTemplate>
        <DataTemplate>
            <ContentPresenter Content="{Binding}">
                <ContentPresenter.Resources>

                    <!-- Header templates -->

                    <DataTemplate DataType="{x:Type vm:TabViewModel}">
                        <TextBlock Text="{Binding Name}"/>
                    </DataTemplate>

                    <DataTemplate DataType="{x:Type vm:NewTabViewModel}">
                        <TextBlock Text="+"/>
                    </DataTemplate>

                </ContentPresenter.Resources>
            </ContentPresenter>
        </DataTemplate>
    </TabControl.ItemTemplate>
</TabControl>