WPF TabControl и DataTemplates

у меня есть набор ViewModels, который я привязываю к свойству ItemsSource TabControl. Назовем эти модели AViewModel, BViewModel и CViewModel. Каждый из них должен иметь разные ItemTemplate (для заголовка; потому что каждый из них должен показывать другой значок) и другой ContentTemplate (потому что у них очень разные модели взаимодействия).

то, что я хотел бы, это что-то вроде этого:

определено в ресурсе.файл 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 на основе имени и типа данных?

5 ответов


один из способов-использовать DataTemplateSelectors и каждый из них разрешает ресурс из отдельного 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, имеющей свойство под названием "Тип", определенное примерно так (легко помещается в базовую 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 в разделе ресурсов my TabControl для каждой модели представления, которую я хочу отобразить на вкладке "элементы". В этом случае я map ViewModelType1 to View1 и ViewModelType2 to 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>      


пользователь элемент управления, содержащий элемент управления tab, имеет модель представления контейнера типа ContainerViewModel as DataContext. Здесь у меня есть коллекция всех моделей представления, отображаемых в элементе управления tab. У меня также есть свойство для выбранной модели представления (элемент вкладки).

это сокращенная версия моей модели представления контейнера (я пропустил часть уведомления об изменении).

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 с шаблоном дизайна Model-View-ViewModel. В этом подходе, поскольку каждый элемент в коллекции VM имеет соответствующий DataTemplate, связывающий представление с типом VM (опуская ключ x:, Как правильно отмечает 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 имеет проблемы с производительностью, если пользовательский интерфейс на вкладках большой / сложный и поэтому медленный для рисования (например, datagrids с большим количеством данных). Для получения дополнительной информации по этой проблеме найдите "WPF VirtualizingStackPanel для повышения производительности".