Template dinamici di WPF con DataTemplateSelector

di Alessandro Suppiej, in Windows Presentation Foundation,

Una delle caratteristiche più interessanti dei controlli di tipo ItemsControl (come la ListView), è quella di non avere una visualizzazione predefinita degli elementi che contiene.
O meglio, una visualizzazione standard esiste, ma come è logico attendersi essa è in grado di rappresentare solo dati di tipo elementare.

In presenza di una collection di oggetti complessi, la lista non è in grado di proporre una visualizzazione e per questo ogni elemento verrà rappresentato dal nome completo della classe da cui deriva (namespace+nomeclasse).
Ovviamente questo comportamento è accettabile proprio perché WPF mette a disposizione uno strumento molto potente quali sono i DataTemplate.
Come abbondantemente illustrato in più articoli di questa community, i DataTemplate consentono di dare una rappresentazione visiva dei dati contenuti in un oggetto.

Per una ripassata veloce, prendete spunto questo esempio, senza soffermarvi troppo nel suo contenuto :

<DataTemplate>
<StackPanel Grid.Row="0" Grid.Column="0" Orientation="Horizontal">
    <TextBlock Text="{Binding Path=Quesito}" />
            <TextBlock Text=" : " />
    <TextBox Width="200" Text="{Binding Path=Risposta}"/> 
  </StackPanel>
</DataTemplate>

Brevemente, questo Template non fa altro che dichiarare che l'oggetto da visualizzare (una singola rigola della listview) è composto da 3 TextBlock.
Il testo di due di queste, sarà associato ai campi Quesito e Risposta di una nostra ipotetica classe.

Tornando alla nostra ListView, la possibilità di configurare il modo di esporre i dati è senz'altro molto importante e di sicuro aiuto.
Ebbene, se questa caratteristica è già molto soddisfacente, la possibilità di avere non solo uno ma svariati modi di farlo, lo è sicuramente molto di più.
Infatti Windows Presentation Foundation mette a nostra disposizione una classe DataTemplateSelector che ci permette di avere all'interno della medesima lista tutti i DataTemplate di cui abbiamo bisogno e di fatto questa opzione ci consente di avere una ListView con una riga diversa dall'altra, in base agli oggetti che questa deve visualizzare.

Per chiarire questo concetto, seguiamo questo piccolo stralcio di codice.

public class Question
{
      public string Quesito { get; set; }

      public Question()       {}
      public Question(string TestoDomanda)
      {
          Quesito = TestoDomanda;
      }  
}

public class Quiz: Question 
{
     public Quiz( )  {}
     public bool Risposta { get; set; }
}

public class Domanda : Question
{
    public Domanda() {}
    public string Risposta {get; set; }
}

Una classe Question, che espone la proprietà Quesito per contenere il testo della domanda da porre.
Due classi che derivano da Question: la prima (Quiz) serve per rappresentare domande del tipo SI/No e per questo espone la property bool Risposta.
La seconda (Domanda) serve per rappresentare domande che richiedono una risposta digitabile e per questo espone la property Risposta che questa volta è di tipo string.

Supponiamo di creare una ListView all'interno della nostra finestra e di associarvi come ItemsSource, una collection di oggetti di tipo Question; per chiarezza scriviamo tutto via Xaml.
Prima di tutto però, all'interno del codice C# dobbiamo definire un alias per la lista di domande:

public class Listone : ObservableCollection<Question> {}

Questo ci consente di creare un'istanza della collection direttamente dal codice Xaml.
Aggiungiamo un namespace che punti al nostro codice c#.

xmlns:local="clr-namespace:WpfApplication1"

Adesso siamo in grado di istanziare una collection di oggetti Question, aggiungendovi contestualmente due elementi: un oggetto di tipo domanda ed uno di tipo quiz.

<local:Listone x:Key="istanceofListone">
      <local:Domanda Quesito="Come ti chiami?"/>
      <local:Quiz Quesito="Sei Sposato?"/>
</local:Listone>

E' l'ora della ListView:

<ListView Name="ListviewDomande" 
          ItemsSource="{StaticResource istanceofListone}" >
</ListView>

Allo stato attuale delle cose, lanciando l'applicazione, vedremo stampato il nome delle classi e sicuramente non è molto confortante.

A questo punto entrano in scena i DataTemplate che risolvono il problema. Ne creiamo uno per rappresentare la risposta secca del quiz (si/no) e l'altro per consentire all'utente di digitare una risposta più complessa

<DataTemplate x:Key="stringtpl">
<StackPanel Grid.Row="0" Grid.Column="0" Orientation="Horizontal">
    <TextBlock  Text="{Binding Path=Quesito}" />
            <TextBlock  Text=" : " />
    <TextBox  Width = "200" Text="{Binding Path=Risposta}"/> 
  </StackPanel>
</DataTemplate>
    
<DataTemplate x:Key="boolTpl">
  <StackPanel Grid.Row="0" Grid.Column="0" Orientation="Horizontal">
    <TextBlock Text="{Binding Path=Quesito}" />
    <TextBlock Text=" : " />
    <CheckBox IsChecked="{Binding Path=Risposta}"/>
      </StackPanel>
</DataTemplate>

Più che soffermarmi sul contenuto dei due DataTemplate, voglio solo far notare le loro piccole differenze: il primo ha una TextBox per ospitare la proprietà Risposta dell'oggetto Domanda, perché questa è di tipo string.
Il secondo, invece, siccome tratta con campi di tipo boolean, può permettersi di introdurre una chechbox dall'effetto 'scenico' sicuramente più brillante.
Resta infine da annotarsi che entrambe queste dichiarazioni devono essere decorate dall'attributo x:Key (perché verranno ricercate per nome) e dovranno essere inserite nella sezione .

Detto questo, andiamo ad introdurre il DataTemplateSelector, il meccanismo che consente alla ListView, e a tutti gli ItemsControl, di scegliere tra un template e l'altro, in base agli oggetti che carica.

public class SelettoreGuidatoDalTipo : DataTemplateSelector
{
     public SelettoreGuidatoDalTipo() {}
       
      //true false
     public DataTemplate tplBool   {  get;  set;  }

      //template per campi editabili
      public DataTemplate tplString  { get; set; }

       
     public override DataTemplate SelectTemplate(object item, DependencyObject container)
     {
         if (item is Quiz )
             return tplBool;

          if (item is Question )
              return tplString ;
            
          return base.SelectTemplate(item, container);
      }
}

Abbiamo creato una classe 'SelettoreGuidatoDalTipo' facendola derivare da DataTemplateSelector e al suo interno abbiamo messo due proprietà di tipo DataTemplate: tplBool e tplString.
Veniamo ora all'ultima cosa che manca per chiudere l'opera: dobbiamo ridefinire il metodo virtuale SelectTemplate. L'implementazione è semplicissima: mi limito a testare il tipo di oggetto che mi viene passato all'interno del campo item ed in base a questo, ritorno al chiamante il DataTemplate che preferisco.

Ora, per chiudere, non resta che tornare sul codice Xaml e aggiungervi un riferimento al TemplateSelector, prima di tutto istanzanziando nella sezione delle risorse un oggetto di tipo 'SelettoreGuidatoDalTipo':

<local:SelettoreGuidatoDalTipo 
    x:Key="customTemplateSelector"  
    tplBool="{StaticResource boolTpl}" 
  tplString="{StaticResource stringtpl}"/>

e poi aggiungiamo alla lista l'attributo: ItemTemplateSelector.

<ListView ItemTemplateSelector="{StaticResource customTemplateSelector}" 
    Name="ListviewDomande" 
    ItemsSource="{StaticResource istanceofListone}" >
</ListView>

Adesso rilanciamo l'applicazione per vedere il risultato:

Decisamente meglio!

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi