2009-08-28 32 views
6

Tengo un conjunto de ViewModels que estoy vinculando a la propiedad ItemsSource de un TabControl. Llamemos a esos ViewModels AViewModel, BViewModel y CViewModel. Cada uno de ellos debe tener una ItemTemplate diferente (para el encabezado, porque cada uno debe mostrar un icono diferente) y una ContentTemplate diferente (porque tienen modelos de interacción muy diferentes).WPF TabControl y DataTemplates

Lo que me gustaría es algo como esto:

Definido en Resource.xaml archivos en otro lugar:

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

definen por separado:

<TabControl ItemTemplate="[ Some way to select "ItemTemplate" based on the type ]" 
      ContentTemplate="[ Some way to select "ContentTemplate" based on the type ]"/> 

Ahora, sé que de forma realista, cada vez que defino un DataTemplate con la misma clave, el sistema se va a quejar. Pero, ¿hay algo que pueda hacer que sea similar a esto que me permita poner un DataTemplate en un TabControl basado en un nombre y un DataType?

Respuesta

7

Una forma sería utilizar DataTemplateSelector s y hacer que cada uno resuelve el recurso de una separada ResourceDictionary.

+1

+1 para el enfoque basado en código. Bastante fácil de entender, en lugar de usar disparadores. –

+1

Me parece recordar que hay una clave compuesta que se desconectó de Tipo y un identificador ... tal vez en la versión .Net 3.0 de WPF. ¿Todavía está por aquí? De esta forma, mi DataTemplateSelector puede ser bastante genérico y no tener que preocuparse por cómo encontrar diferentes ResourceDictionaries y todo eso. – dustyburwell

+1

Encontré el ComponentResourceKey y creé un ComponentResourceKeyDataTemplateSelector que encuentra un DataTemplate basado en el tipo de elemento que se está modelando y un ResourceId que se transfiere. ¿Consideraría esto una solución decente? – dustyburwell

8

Se puede quitar el directorio x:. Clave :) Esto se aplicará automáticamente la plantilla cuando se encuentra el tipo determinado (probablemente una de las características más potentes e infrautilizadas de WPF, imo

En este artículo se va Dr. WPF sobre DataTemplates bastante bien. La sección que querrá prestar atención es "Definir una plantilla predeterminada para un tipo de datos CLR Dada".

http://www.drwpf.com/blog/Home/tabid/36/EntryID/24/Default.aspx

Si esto no ayuda a su situación, podría ser capaz de hacer algo parecido a lo que eres buscando usar un estilo (ItemContainerStyle) y establecer el contenido y el encabezado según el tipo usando un disparador de datos.

El ejemplo siguiente gira en torno a su modelo de vista que tienen una propiedad denominada "tipo" definido más o menos así (poner fácilmente en un modelo de vista base si tiene uno):

public Type Type 
{ 
    get { return this.GetType(); } 
} 

Así que, mientras usted tiene que , esto debería permitirle hacer lo que quiera. Tenga en cuenta que tengo "A Header!" en un bloque de texto aquí, pero eso podría ser fácilmente cualquier cosa (icono, etc.).

Lo tengo aquí de dos maneras ... un estilo aplica plantillas (si ya tienes una inversión importante en esto) y el otro solo usa instaladores para mover el contenido a los lugares correctos.

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

+0

Eso no es lo que él quiere. Necesita una clave compuesta para que diferentes plantillas con los mismos tipos puedan resolverse desde el mismo alcance. –

+0

Sí ... Lo edité. Creo que al usar estos activadores de datos basados ​​en Tipo, él debería poder configurar el Encabezado/Contenido a algo único para cada tipo. Todo lo que haría entonces es asignar el ItemContainerStyle a este estilo aquí. Creo que debería funcionar, pero avíseme si me falta la marca. Debería hacer lo que haría un selector de plantilla de datos, excepto en xaml. –

+0

@Kent: lo golpeas bien con el dinero. Eso es exactamente lo que me gustaría tener. – dustyburwell

1

Josh Smith usa exactamente esta técnica (de la conducción de un control de ficha con una colección de vista del modelo) en su excelente proyecto de artículo y muestra WPF Apps With The Model-View-ViewModel Design Pattern. En este enfoque, debido a que cada elemento de la colección de VM tiene una DataTemplate correspondiente que vincula la Vista al Tipo de VM (al omitir la tecla x: como correctamente notas de Anderson Imes), cada pestaña puede tener una UI completamente diferente. Vea el artículo completo y el código fuente para más detalles.

Las partes clave de la XAML son:

<DataTemplate DataType="{x:Type vm:CustomerViewModel}"> 
    <vw:CustomerView /> 
</DataTemplate> 

<DataTemplate x:Key="WorkspacesTemplate"> 
<TabControl 
    IsSynchronizedWithCurrentItem="True" 
    ItemsSource="{Binding}" 
    ItemTemplate="{StaticResource ClosableTabItemTemplate}" 
    Margin="4" 
    /> 

hay un inconveniente - la conducción de un WPF TabControl de un ItemsSource tiene problemas de rendimiento si la interfaz de usuario en el pestañas es grande/complejo y, por tanto, lento para dibujar (p. ej., datagrids con muchos datos). Para obtener más información sobre este tema, busque SO para "WPF VirtualizingStackPanel para un mayor rendimiento".

14

La manera más fácil sería usar el sistema de plantilla automática, al incluir las plantillas de datos en los recursos de un ContentControl. ¡El alcance de las plantillas está limitado al elemento en el que residen!

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

Mi TabControl solo me muestra el nombre de mi viewmodel .. ¿Cómo mostraría la vista correspondiente en contentTemplate? – Mafii

2

En este ejemplo utilizo DataTemplates en la sección de recursos de mi TabControl para cada modelo de vista que quiero mostrar en los elementos de la ficha. En este caso, asigno ViewModelType1 a View1 y ViewModelType2 a View2. Los modelos de vista se establecerán como el objeto DataContext de las vistas automáticamente.

Para mostrar el encabezado del elemento tabulador, uso ItemTemplate. Los modelos de vista a los que me enlace son de tipos diferentes, pero derivan de una clase base común ChildViewModel que tiene una propiedad Title. Así que puedo configurar un enlace para levantar el título y mostrarlo en el encabezado del elemento de tabulación.

Además, se muestra un botón "Cerrar" en el encabezado del elemento de tabulación. Si no lo necesita, simplemente elimine el botón del código de ejemplo para que solo tenga el texto del encabezado.

El contenido de los elementos de pestaña se representa con un simple ItemTemplate que muestra la vista en un control de contenido con 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>  


El control de usuario que contiene el control de la lengüeta tiene una vista de modelo de contenedores de tipo ContainerViewModel como DataContext. Aquí tengo una colección de todos los modelos de vista que se muestran en el control de pestañas. También tengo una propiedad para el modelo de vista seleccionado actualmente (elemento de tabulación).

Esta es una versión abreviada de mi modelo de vista de contenedor (omití la parte de notificación de cambio).

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;} 
} 
Cuestiones relacionadas