WPF选项卡控件和数据模板

WPF选项卡控件和数据模板,wpf,tabcontrol,datatemplate,Wpf,Tabcontrol,Datatemplate,我有一组ViewModels,绑定到TabControl的ItemsSource属性。让我们将这些视图模型称为AViewModel、BViewModel和CViewModel。其中每一个都需要有一个不同的ItemTemplate(用于标题;因为它们都需要显示不同的图标)和一个不同的ContentTemplate(因为它们有非常不同的交互模型) 我想要的是这样的: 在Resource.xaml文件中某处定义: <DataTemplate x:Key="ItemTemplate" DataTy

我有一组ViewModels,绑定到TabControl的ItemsSource属性。让我们将这些视图模型称为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 ]"/>

现在,我知道,实际上,每次我用相同的密钥定义数据模板时,系统都会抱怨。但是,我可以做一些类似的事情,让我根据名称和数据类型将数据模板放入TabControl中吗?

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

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

如果这对您的情况没有帮助,您可能可以使用样式(ItemContainerStyle)并使用数据触发器根据类型设置内容和标题,从而完成一些与您所需内容相近的工作

下面的示例取决于您的ViewModel,该ViewModel具有一个名为“Type”的属性,该属性的定义与此类似(如果您有基本ViewModel,则很容易放入其中):

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

这里我有两种方法…一种是应用模板(如果您已经在这些模板上投入了大量资金),另一种是使用setter将内容移动到正确的位置

<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>

头球!
开始“A”内容


HTH,Anderson

一种方法是使用
DataTemplateSelector
s,让每个人从一个单独的
ResourceDictionary
解析资源,Josh Smith正好使用这种技术(通过视图模型集合驱动选项卡控件)在他的优秀文章和示例项目中。在这种方法中,因为VM集合中的每个项都有一个相应的DataTemplate,将视图链接到VM类型(正如Anderson Imes正确指出的那样,通过省略x:Key),所以每个选项卡都可以有一个完全不同的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"
  />


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

最简单的方法是使用自动模板系统,将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>

...
...
...
...
...
...

在本例中,我在我的
选项卡控件的参考资料部分中使用DataTemplates,用于我要在选项卡项中显示的每个视图模型。
在本例中,我将
ViewModelType1
映射到
View1
ViewModelType2
映射到
View2
。 视图模型将自动设置为视图的
DataContext
对象

为了显示选项卡项标题,我使用
ItemTemplate
。 我绑定到的视图模型有不同的类型,但派生自一个公共基类
ChildViewModel
,该基类具有
Title
属性。因此,我可以设置一个绑定,以拾取标题,并将其显示在选项卡项标题中

此外,我在选项卡项标题中显示一个“Close”(关闭)按钮。如果您不需要,只需从示例代码中删除该按钮,就可以看到标题文本

选项卡项的内容通过一个简单的
ItemTemplate
呈现,该模板使用content=“{Binding}”在内容控件中显示视图


+1用于基于代码的方法。非常容易理解,而不是使用触发器。我似乎记得有一个复合键,键入了类型和标识符…可能在.Net 3.0版本的WPF中。这是否仍然存在?这样,我的DataTemplateSelector可以非常通用,不必担心如何找到不同的我找到了组件资源库,我创建了一个组件资源库密钥数据板选择器,它基于被模板化的项目的类型和一个RealCEID,找到一个数据板。你认为这是一个体面的解决方案吗?这不是他想要的。他需要一个复合的密钥,使得不同的TEM。具有相同类型的图版可以从相同的范围内解析。是的。我编辑了它。我认为使用这些数据触发b
<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>
<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>      
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;}
}