10

我有一组 ViewModel 绑定到 TabControl 的 ItemsSource 属性。我们将这些 ViewModel 称为 AViewModel、BViewModel 和 CViewModel。其中每一个都需要有不同的 ItemTemplate(用于标题;因为它们每个都需要显示不同的图标)和不同的 ContentTemplate(因为它们具有非常不同的交互模型)。

我想要的是这样的:

在某处的 Resource.xaml 文件中定义:

<DataTemplate x:Key="ItemTemplate" DataType="{x:Type AViewModel}">
    ...
</DataTemplate>

<DataTemplate x:Key="ItemTemplate" DataType="{x:Type BViewModel}">
    ...
</DataTemplate>

<DataTemplate x:Key="ItemTemplate" DataType="{x:Type CViewModel}">
    ...
</DataTemplate>

<DataTemplate x:Key="ContentTemplate" DataType="{x:Type AViewModel}">
    ...
</DataTemplate>

<DataTemplate x:Key="ContentTemplate" DataType="{x:Type BViewModel}">
    ...
</DataTemplate>

<DataTemplate x:Key="ContentTemplate" DataType="{x:Type CViewModel}">
    ...
</DataTemplate>

单独定义:

<TabControl ItemTemplate="[ Some way to select "ItemTemplate" based on the type ]"
            ContentTemplate="[ Some way to select "ContentTemplate" based on the type ]"/>

现在,我知道实际上,每次我使用相同的键定义 DataTemplate 时,系统都会抱怨。但是,有什么我可以做的与此类似的事情,可以让我根据名称和 DataType 将 DataTemplate 放入 TabControl 中吗?

4

5 回答 5

18

最简单的方法是使用自动模板系统,将 DataTemplates 包含在 ContentControl 的资源中。模板的范围仅限于它们所在的元素!

<TabControl ItemsSource="{Binding TabViewModels}">
    <TabControl.ItemTemplate>
        <DataTemplate>
            <ContentControl Content="{Binding}">
                <ContentControl.Resources>
                    <DataTemplate DataType="{x:Type AViewModel}">
                        ...
                    </DataTemplate>
                    <DataTemplate DataType="{x:Type BViewModel}">
                        ...
                    </DataTemplate>
                    <DataTemplate DataType="{x:Type CViewModel}">
                        ...
                    </DataTemplate>
                </ContentControl.Resources>
            </ContentControl>
        </DataTemplate>
    </TabControl.ItemTemplate>
    <TabControl.Resources>
        <DataTemplate DataType="{x:Type AViewModel}">
            ...
        </DataTemplate>
         <DataTemplate DataType="{x:Type BViewModel}">
            ...
        </DataTemplate>
        <DataTemplate DataType="{x:Type CViewModel}">
            ...
        </DataTemplate>
    </TabControl.Resources>
</TabControl>
于 2012-05-19T15:49:56.503 回答
8

您可以删除 x:Key :) 这将在遇到给定类型时自动应用模板(可能是 WPF 最强大且未被充分利用的功能之一,imo。

这篇 WPF 博士的文章很好地介绍了 DataTemplates。您需要注意的部分是“为给定的 CLR 数据类型定义默认模板”。

http://www.drwpf.com/blog/Home/tabid/36/EntryID/24/Default.aspx

如果这对您的情况没有帮助,您可以使用样式 (ItemContainerStyle) 并使用数据触发器根据类型设置内容和标题来做一些接近您正在寻找的事情。

下面的示例取决于您的 ViewModel,它有一个类似这样定义的名为“Type”的属性(如果有的话,很容易放入基本 ViewModel):

public Type Type 
{ 
   get { return this.GetType(); } 
}

所以只要你有这个,这应该允许你做任何你想做的事情。注意我有“标题!” 在这里的文本块中,但这很容易是任何东西(图标等)。

我在这里有两种方式......一种样式应用模板(如果您已经在这些方面进行了大量投资),另一种只是使用设置器将内容移动到正确的位置。

<Window x:Class="WpfApplication1.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300"
        xmlns:local="clr-namespace:WpfApplication1">
    <Window.Resources>
        <CompositeCollection x:Key="MyCollection">
            <local:AViewModel Header="A Viewmodel" Content="A Content" />
            <local:BViewModel Header="B ViewModel" Content="B Content" />
        </CompositeCollection>

    <DataTemplate x:Key="ATypeHeader" DataType="{x:Type local:AViewModel}">
        <WrapPanel>
            <TextBlock>A Header!</TextBlock>
            <TextBlock Text="{Binding Header}" />
        </WrapPanel>
    </DataTemplate>
    <DataTemplate x:Key="ATypeContent" DataType="{x:Type local:AViewModel}">
        <StackPanel>
            <TextBlock>Begin "A" Content</TextBlock>
            <TextBlock Text="{Binding Content}" />
        </StackPanel>
    </DataTemplate>

    <Style x:Key="TabItemStyle" TargetType="TabItem">
        <Style.Triggers>
            <!-- Template Application Approach-->
            <DataTrigger Binding="{Binding Path=Type}" Value="{x:Type local:AViewModel}">
                <Setter Property="HeaderTemplate" Value="{StaticResource ATypeHeader}" />
                <Setter Property="ContentTemplate" Value="{StaticResource ATypeContent}" />
            </DataTrigger>

            <!-- Just Use Setters Approach -->
            <DataTrigger Binding="{Binding Path=Type}" Value="{x:Type local:BViewModel}">
                <Setter Property="Header">
                    <Setter.Value>
                        <WrapPanel>
                            <TextBlock Text="B Header!"></TextBlock>
                            <TextBlock Text="{Binding Header}" />
                        </WrapPanel>
                    </Setter.Value>
                </Setter>
                <Setter Property="Content" Value="{Binding Content}" />
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Window.Resources>
<Grid>
    <TabControl ItemContainerStyle="{StaticResource TabItemStyle}" ItemsSource="{StaticResource MyCollection}" />
</Grid>

HTH,安德森

于 2009-08-28T17:26:10.760 回答
7

一种方法是使用DataTemplateSelectors 并让每个人从单独的ResourceDictionary.

于 2009-08-28T17:27:00.167 回答
3

在此示例中,我在我的资源部分中TabControl为每个要在选项卡项中显示的视图模型使用 DataTemplates。在这种情况下,我映射ViewModelType1View1和。视图模型将自动设置为视图的对象。ViewModelType2View2DataContext

为了显示选项卡项标题,我使用ItemTemplate. 我绑定到的视图模型属于不同类型,但派生自ChildViewModel具有Title属性的公共基类。所以我可以设置一个绑定来获取标题以在选项卡项标题中显示它。

此外,我在选项卡项标题中显示一个“关闭”按钮。如果您不需要,只需从示例代码中删除按钮,这样您就只有标题文本。

选项卡项的内容以简单的形式呈现,它在Content="{Binding}"ItemTemplate的内容控件中显示视图。

<UserControl ...>
    <UserControl.DataContext>
        <ContainerViewModel></ContainerViewModel>
    </UserControl.DataContext>      
        <TabControl ItemsSource="{Binding ViewModels}"
                    SelectedItem="{Binding SelectedViewModel}">
            <TabControl.Resources>
                <DataTemplate DataType="{x:Type ViewModelType1}">
                    <View1/>
                </DataTemplate>
                <DataTemplate DataType="{x:Type ViewModelType2}">
                    <View2/>
                </DataTemplate>             
            </TabControl.Resources>
            <TabControl.ItemTemplate>
                <DataTemplate>
                    <DockPanel>
                        <TextBlock Text="{Binding Title}" />
                        <Button DockPanel.Dock="Right" Margin="5,0,0,0"
                                Visibility="{Binding RemoveButtonVisibility}" 
                                Command="{Binding DataContext.CloseItemCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TypeOfContainingView}}}"
                                >
                            <Image Source="/Common/Images/ActiveClose.gif"></Image>
                        </Button>
                    </DockPanel>
                </DataTemplate>
            </TabControl.ItemTemplate>
            <TabControl.ContentTemplate>
                <DataTemplate>
                    <ContentControl Content="{Binding}"/>
                </DataTemplate>
            </TabControl.ContentTemplate>
        </TabControl>
</UserControl>      


包含选项卡控件的用户控件具有类型ContainerViewModel为的容器视图模型DataContext。在这里,我收集了选项卡控件中显示的所有视图模型。我还有一个当前选择的视图模型(选项卡项)的属性。

这是我的容器视图模型的缩短版本(我跳过了更改通知部分)。

public class ContainerViewModel
{
    /// <summary>
    /// The child view models.
    /// </summary>
    public ObservableCollection<ChildViewModel> ViewModels {get; set;}

    /// <summary>
    /// The currently selected child view model.
    /// </summary>
    public ChildViewModel SelectedViewModel {get; set;}
}
于 2015-08-06T14:09:26.853 回答
1

Josh Smith 在他的优秀文章和示例项目WPF Apps With The Model-View-ViewModel Design Pattern中正是使用了这种技术(使用视图模型集合驱动选项卡控件)。在这种方法中,因为 VM 集合中的每个项目都有一个相应的 DataTemplate 将 View 链接到 VM 类型(通过省略 x:Key,正如 Anderson Imes 正确指出的那样),每个选项卡都可以具有完全不同的 UI。有关详细信息,请参阅完整文章和源代码。

XAML 的关键部分是:

 <DataTemplate DataType="{x:Type vm:CustomerViewModel}">
   <vw:CustomerView />
 </DataTemplate>

<DataTemplate x:Key="WorkspacesTemplate">
<TabControl 
  IsSynchronizedWithCurrentItem="True" 
  ItemsSource="{Binding}" 
  ItemTemplate="{StaticResource ClosableTabItemTemplate}"
  Margin="4"
  />

有一个缺点 - 如果选项卡中的 UI 很大/很复杂,因此绘制速度很慢(例如,具有大量数据的数据网格),从 ItemsSource 驱动 WPF TabControl 会出现性能问题。有关此问题的更多信息,请搜索“WPF VirtualizingStackPanel 以提高性能”。

于 2009-12-12T01:40:38.260 回答