为 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>
我正在尝试制作一个基本的 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>