WPF TabControl и DataTemplates - PullRequest
       44

WPF TabControl и DataTemplates

8 голосов
/ 28 августа 2009

У меня есть набор 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 ]

16 голосов
/ 19 мая 2012

Самый простой способ - использовать автоматическую систему шаблонов, включив 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>
8 голосов
/ 28 августа 2009

Вы можете удалить 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, Андерсон

7 голосов
/ 28 августа 2009

Один из способов - использовать DataTemplateSelector s, и каждый из них разрешает ресурс из отдельного ResourceDictionary.

3 голосов
/ 06 августа 2015

В этом примере я использую 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;}
}
1 голос
/ 12 декабря 2009

Джош Смит использует именно эту технику (управление вкладками с помощью коллекции моделей представлений) в своей превосходной статье и образце проекта Приложения WPF с шаблоном проектирования Model-View-ViewModel .При таком подходе, поскольку каждый элемент в коллекции VM имеет соответствующий DataTemplate, связывающий View с типом VM (опуская 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 для повышения производительности».

...