2009-08-28 16 views
6

Ho un set di ViewModels che sto vincolando alla proprietà ItemsSource di un TabControl. Chiamiamo ViewModels AViewModel, BViewModel e CViewModel. Ognuno di questi deve avere un ItemTemplate diverso (per l'intestazione, perché ognuno deve mostrare un'icona diversa) e un ContentTemplate diverso (perché hanno modelli di interazione molto diversi).TabControl e DataTemplates WPF

Quello che mi piacerebbe è qualcosa di simile:

Definito in Resource.xaml file da qualche:

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

definito separatamente:

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

Ora, io so che realisticamente, ogni volta che definisco un DataTemplate con la stessa chiave, il sistema si lamenterà. Ma, c'è qualcosa che posso fare che è simile a questo che mi permetterà di inserire un DataTemplate in un TabControl basato su un nome e un DataType?

risposta

7

Un modo potrebbe essere quello di utilizzare DataTemplateSelector s ed avere ognuno risolve la risorsa da un separato ResourceDictionary.

+1

+1 per l'approccio basato sul codice. Piuttosto facile da capire, piuttosto che usare i trigger. –

+1

Mi sembra di ricordare che ci sia una chiave composita che è stata esclusa da Type e un identificatore ... forse nella versione .Net 3.0 di WPF. È ancora in giro da qualche parte? In questo modo, il mio DataTemplateSelector può essere piuttosto generico e non deve preoccuparsi di come trovare diversi ResourceDictionaries e tutto il resto. – dustyburwell

+1

Ho trovato ComponentResourceKey e ho creato un ComponentResourceKeyDataTemplateSelector che trova un DataTemplate in base al tipo dell'elemento in fase di costruzione e a un ResourceId in cui passi. Considereresti una soluzione decente? – dustyburwell

8

È possibile rimuovere la x:. Chiave :) Questo applicherà automaticamente il modello quando si verifica il tipo di data (probabilmente una delle caratteristiche più potenti e sottoutilizzate di WPF, imo

questo articolo Dr. WPF va oltre DataTemplates abbastanza bene. La sezione si vorrà prestare attenzione è "Definizione di un modello predefinito per un dato CLR Tipo di dati".

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

Se questo non aiuta la situazione, è potrebbe essere in grado di fare qualcosa di simile a quello che sei cercando l'utilizzo di uno stile (ItemContainerStyle) e l'impostazione del contenuto e dell'intestazione in base al tipo utilizzando un trigger di dati.

L'esempio riportato di seguito cerniere sul tuo ViewModel che hanno una proprietà chiamata "Tipo" definito più o meno come questo (facilmente mettere in una ViewModel base se ne avete uno):

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

Quindi, fintanto che si dispone che , questo dovrebbe permetterti di fare tutto ciò che vuoi. Nota: ho "Un'intestazione!" in un blocco di testo qui, ma che potrebbe facilmente essere qualsiasi cosa (icona, ecc.).

L'ho ottenuto qui in due modi ... uno stile applica i modelli (se si dispone già di un investimento significativo in questi) e l'altro utilizza solo i setter per spostare il contenuto nei punti giusti.

<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

Questo non è quello che vuole. Ha bisogno di una chiave composta in modo tale che diversi modelli con gli stessi tipi possano essere risolti dallo stesso ambito. –

+0

Sì ... l'ho modificato. Penso che usando questi datatrigger basati su Type dovrebbe essere in grado di impostare l'intestazione/contenuto su qualcosa di unico per ogni tipo. Tutto quello che farebbe quindi è assegnare l'ItemContainerStyle a questo stile qui. Penso che dovrebbe funzionare, ma fammi sapere se mi manca il marchio. Dovrebbe fare ciò che farebbe un selettore di modelli di dati, tranne in xaml. –

+0

@Kent - lo colpisci nel modo giusto. Questo è esattamente quello che mi piacerebbe avere. – dustyburwell

1

Josh Smith usa esattamente questa tecnica (di guidare un controllo a schede con una collezione di modelli di vista) nel suo eccellente articolo e progetto di esempio WPF Apps With The Model-View-ViewModel Design Pattern. In questo approccio, poiché ogni elemento della raccolta VM ha un DataTemplate corrispondente che collega la vista al tipo di macchina virtuale (omettendo la chiave x: Key come note di Anderson Imes), ciascuna scheda può avere un'interfaccia utente completamente diversa. Vedi l'articolo completo e il codice sorgente per i dettagli.

Le parti chiave del codice XAML sono:

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

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

C'è un aspetto negativo - alla guida di una WPF TabControl da un ItemsSource ha problemi di prestazioni se l'interfaccia utente in schede è grande/complesso e quindi lento a disegnare (es. datagrids con molti dati). Per ulteriori informazioni su questo problema, cerca SO per "WPF VirtualizingStackPanel per maggiori prestazioni".

14

Il modo più semplice sarebbe utilizzare il sistema di modello automatico, includendo i DataTemplates nelle risorse di un ContentControl. Lo scopo dei modelli è limitato all'elemento in cui risiedono!

<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

My TabControl mostra solo il nome del mio viewmodel. Come visualizzerei la vista corrispondente nel contentTemplate? – Mafii

2

In questo esempio io uso DataTemplates nella sezione delle risorse del mio TabControl per ciascun modello di vista Voglio visualizzare negli elementi scheda. In questo caso, mappare ViewModelType1 a View1 e ViewModelType2 a View2. I modelli di visualizzazione verranno impostati automaticamente come oggetto DataContext delle viste.

Per visualizzare l'intestazione della scheda, utilizzo uno ItemTemplate. I modelli di visualizzazione a cui mi collego sono di tipi diversi, ma derivano da una classe base comune ChildViewModel con una proprietà Title. Quindi posso impostare un binding per raccogliere il titolo per visualizzarlo nell'intestazione della voce della scheda.

Inoltre, viene visualizzato un pulsante "Chiudi" nell'intestazione della voce della scheda. Se non è necessario, basta rimuovere il pulsante dal codice di esempio in modo da avere solo il testo dell'intestazione.

Il contenuto degli elementi di tabulazione viene visualizzato con un semplice ItemTemplate che visualizza la visualizzazione in un controllo contenuto 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>  


Il controllo di utente che contiene il controllo scheda ha una vista modello contenitore di tipo ContainerViewModel come DataContext. Qui ho una collezione di tutti i modelli di visualizzazione visualizzati nel controllo struttura a schede. Ho anche una proprietà per il modello di vista attualmente selezionato (elemento della scheda).

Questa è una versione abbreviata del modello di visualizzazione contenitore (ho saltato la parte relativa alle notifiche delle modifiche).

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;} 
} 
Problemi correlati