我有一组ViewModel,我绑定到TabControl的ItemsSource属性。让我们调用那些ViewModels 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时,系统就会抱怨。但是,有什么我可以做的与此类似的东西,让我根据名称和数据类型将DataTemplate放入TabControl中吗?
答案 0 :(得分:16)
最简单的方法是使用自动模板系统,将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>
答案 1 :(得分:8)
您可以删除x:Key :)当遇到给定类型时,这将自动应用模板(可能是WPF中最强大和未充分利用的功能之一,即imo。
WPF博士的这篇文章很好地介绍了DataTemplates。您需要注意的部分是“为给定的CLR数据类型定义默认模板”。
http://www.drwpf.com/blog/Home/tabid/36/EntryID/24/Default.aspx
如果这对你的情况没有帮助,你可以使用Style(ItemContainerStyle)做一些接近你想要的东西,并使用数据触发器根据类型设置内容和标题。
下面的示例取决于您的ViewModel,其中有一个名为“Type”的属性,它的定义非常类似(如果有的话,可以轻松放入View ViewModel中):
public Type Type
{
get { return this.GetType(); }
}
所以,只要你拥有它,这应该可以让你做任何你想做的事情。注意我有“A Header!”在这里的文本块中,但很容易就是任何东西(图标等)。
我在这里有两种方式......一种风格应用模板(如果你已经对这些进行了大量投资),另一种只使用设置器将内容移动到正确的位置。
<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,安德森
答案 2 :(得分:7)
一种方法是使用DataTemplateSelector
并让每个人从单独的ResourceDictionary
解析资源。
答案 3 :(得分:3)
在这个例子中,我在TabControl
的资源部分使用DataTemplates,用于我想在选项卡项中显示的每个视图模型。
在这种情况下,我将ViewModelType1
映射到View1
,将ViewModelType2
映射到View2
。
视图模型将自动设置为视图的DataContext
对象。
为了显示标签项标题,我使用ItemTemplate
。
我绑定的视图模型具有不同的类型,但派生自具有ChildViewModel
属性的公共基类Title
。所以我可以设置一个绑定来获取标题,以便在标签项标题中显示它。
此外,我在标签项标题中显示“关闭”按钮。如果您不需要,只需从示例代码中删除按钮,这样您就可以使用标题文本。
标签项的内容使用简单的ItemTemplate
呈现,该内容使用 Content =“{Binding}”在内容控件中显示视图。
<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;}
}
答案 4 :(得分:1)
Josh Smith在他出色的文章和示例项目WPF Apps With The Model-View-ViewModel Design Pattern中使用了这种技术(用视图模型集合驱动标签控件)。在此方法中,因为VM集合中的每个项目都具有将View链接到VM类型的相应DataTemplate(通过省略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以提高性能”。