WPF TabControl и DataTemplates
У меня есть набор ViewModels, которые я связываю со свойством ItemsSource элемента TabControl. Давайте назовем эти 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 в TabControl на основе имени и типа DataType?
5 ответов
Одним из способов было бы использовать DataTemplateSelector
и каждый из них разрешает ресурс из отдельного ResourceDictionary
,
Самый простой способ - использовать автоматическую систему шаблонов, включив 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>
Вы можете удалить 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, Андерсон
В этом примере я использую DataTemplates в разделе ресурсов моего TabControl
для каждой модели представления я хочу отобразить во вкладке элементы. В этом случае я карта 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;}
}
Джош Смит использует именно эту технику (управление вкладками с помощью коллекции моделей представлений) в своей превосходной статье и образце проекта WPF Apps с шаблоном проектирования Model-View-ViewModel. При таком подходе, поскольку каждый элемент в коллекции VM имеет соответствующий шаблон DataTemplate, связывающий представление с типом виртуальной машины (опуская x:Key, как правильно отмечает Anderson Imes), каждая вкладка может иметь совершенно другой пользовательский интерфейс. Смотрите полную статью и исходный код для деталей.
Ключевые части XAML:
<DataTemplate DataType="{x:Type vm:CustomerViewModel}">
<vw:CustomerView />
</DataTemplate>
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4"
/>
Есть один недостаток - управление WPF TabControl из ItemsSource имеет проблемы с производительностью, если пользовательский интерфейс на вкладках большой или сложный и поэтому медленно рисует (например, сетки данных с большим количеством данных). Для получения дополнительной информации по этому вопросу выполните поиск по запросу "WPF VirtualizingStackPanel для повышения производительности".