Confessions of a Dangerous Mind

Brain.FlushBuffer()
posts - 176, comments - 234, trackbacks - 93

WPF Cookbook: Personalizzare una Listbox – Part 1

Già sento le voci… “noooo, sappiamo già come personalizzare una listbox!”. E’ vero, sul web si possono trovare centinaia di post che spiegano come personalizzare una Listbox, ma visto che personalmente ho faticato un pò per “aggregare” tutte le informazioni, ho pensato di fare un post che dimostri come personalizzare graficamente una Listbox con un tema con gradienti e selezione personalizzata. Il controllo ListBox permette di fatto di ottenere una altissima personalizzazione, ma non è tra i controlli più semplici di WPF da customizzare.

Tutto sarà pensato come “rivestimento” di un’applicazione MVVM, visto che ormai sono “schiavo” di questo pattern per lo sviluppo di client applications. Nell’immagine a destra potete vedere il risultato che vogliamo ottenere.

Il motore

Per prima cosa descriviamo brevemente la parte Model e ViewModel, ovvero l’implementazione in C# delle funzionalità della nostra applicazione. In pratica si tratta di una semplice lista di film che possono essere selezionati.

Necessiteremo di:

  • Un Model per il Film
  • Un Service che “estrae” i film dalla nostra sorgente dati
  • Un ViewModel per il film
  • Un ViewModel per la MainWindow

Quindi per quanto riguarda il Model, avremo una classe Film:

   1: public class Film
   2: {
   3:  
   4:     public Film(string title, string originalTitle, string cover, string genre)
   5:     {
   6:         this.Title = title;
   7:         this.OriginalTitle = originalTitle;
   8:         this.Cover = cover;
   9:         this.Genre = genre;
  10:     }
  11:  
  12:     public string Title { get; set; }
  13:     public string OriginalTitle { get; set; }
  14:     public string Cover { get; set; }
  15:     public string Genre { get; set; }
  16: }

La nostra ListBox si troverà in una window ed utilizzando il pattern MVVM dovremo assegnare al DataContext di questa Window un ViewModel così composto:

   1: public class MainViewModel : ViewModelBase
   2: {
   3:     //snip...
   4:     
   5:     ObservableCollection<FilmViewModel> _films;
   6:     public ObservableCollection<FilmViewModel> Films
   7:     {
   8:         get
   9:         {
  10:             if (_films == null)
  11:             {
  12:                 _films = new ObservableCollection<FilmViewModel>();
  13:                 Services.IFilmService _filmService = new Services.FilmService();
  14:                 List<Models.Film> _result = _filmService.GetFilms();
  15:                 foreach (Models.Film _film in _result)
  16:                 {
  17:                     _films.Add(new FilmViewModel(_film));
  18:                 }
  19:             }
  20:             return _films;
  21:         }
  22:     }
  23:  
  24:     //Snip...
  25: }

Il ViewModel del film è piuttosto banale:

   1: public class FilmViewModel
   2: {
   3:     private Models.Film _film;
   4:  
   5:     public FilmViewModel(Models.Film film)
   6:     {
   7:         _film = film;
   8:     }
   9:  
  10:     public string Title
  11:     {
  12:         get
  13:         {
  14:             return _film.Title;
  15:         }
  16:     }
  17:  
  18:     public string OriginalTitle
  19:     {
  20:         get
  21:         {
  22:             return _film.OriginalTitle;
  23:         }
  24:     }
  25:  
  26:     public string Cover
  27:     {
  28:         get
  29:         {
  30:             return _film.Cover;
  31:         }
  32:     }
  33:  
  34:     public string Genre
  35:     {
  36:         get
  37:         {
  38:             return _film.Genre;
  39:         }
  40:     }
  41: }

Queste sono le parti che sono necessarie per implementare le parti di “plumbing” della nostra applicazione. Ora procederemo con la personalizzazione della listbox, operazione decisamente divertente, ma non priva di qualche difficoltà e qualche accorgimento interessante.

La carrozzeria

Per personalizzare la nostra listbox, procederemo per prima cosa impiegando un DataTemplate. Il DataTemplate in questione verrà utilizzato all’interno della list box per visualizzare i film. In pratica si tratta di una View associata al FilmViewModel contenuto nella ObservableCollectionche verrà “databindata” alla ListBox. Come è possibile vedere nello snippet seguente, la ListBox contenuta nella View NonStyledMainView è piuttosto semplice:

   1: <Window x:Class="FilmList.Views.NonStyledMainView"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:Views="clr-namespace:FilmList.Views"
   5:     Title="NonStyledMainView" Height="600" Width="600">
   6:     <Grid>    
   7:         <ListBox ItemsSource="{Binding Path=Films}" Grid.Column="1" Grid.Row="1"
   8:          Grid.ColumnSpan="2" Grid.RowSpan="3" Margin="8,8,8,8" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
   9:          HorizontalContentAlignment="Stretch">
  10:             <ListBox.ItemTemplate>
  11:                 <DataTemplate>
  12:                     <Views:NonStyledFilmView/>
  13:                 </DataTemplate>
  14:             </ListBox.ItemTemplate>
  15:         </ListBox>
  16:     </Grid>
  17: </Window>

Nello specifico, l’item data template  è anch’esso una View, NonStyledFilmView, così realizzata:

   1: <UserControl x:Class="FilmList.Views.NonStyledFilmView"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:Converters="clr-namespace:FilmList.Converters">
   5:     <UserControl.Resources>
   6:         <Converters:ImagePathConverter x:Key="ImagePathConverter"/>
   7:     </UserControl.Resources>
   8:     <Grid>
   9:         <Grid.RowDefinitions>
  10:             <RowDefinition/>
  11:             <RowDefinition/>
  12:         </Grid.RowDefinitions>
  13:         <Grid.ColumnDefinitions>
  14:             <ColumnDefinition Width="70"></ColumnDefinition>
  15:             <ColumnDefinition Width="300"></ColumnDefinition>
  16:             <ColumnDefinition Width="*"></ColumnDefinition>
  17:         </Grid.ColumnDefinitions>
  18:         <Border Grid.Column="0" Grid.RowSpan="2" VerticalAlignment="Stretch" Background="White" Margin="0,0,10,0" CornerRadius="4">
  19:             <Image  Source="{Binding Path=Cover, Converter={StaticResource ImagePathConverter}, ConverterParameter=../Images}" Margin="2,2,2,2" VerticalAlignment="Center" Height="64"/>
  20:         </Border>
  21:         <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=Title}" MinWidth="50" Margin="0,0,5,0" />
  22:         <TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding Path=Genre}" FontSize="20" MinWidth="50" Margin="0,0,5,0" />
  23:         <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=OriginalTitle}" FontSize="20" MinWidth="50" Margin="0,0,5,0" />
  24:     </Grid>
  25: </UserControl>
  26:  

Questo approccio consente di isolare le view e di renderle indipendenti e potenzialmente riusabili. Il risultato non è dei migliori, in quanto:

  • mancano bordi, sfondi e gradienti
  • la selezione è fissata sul colore azzurro di default di windows (SystemColors.HighlightBrushKey)
  • tutto è molto “boring”… dobbiamo mettere un pò di pepe al controllo, altrimenti non va bene (si sa che l’abito fa il monaco ;-))

Nella prossima parte di questo lungo articolo ci focalizzeremo su stili e triggers per ottenere un aspetto gradevole per la nostra ListBox. Per il codice, pazientate fino alla seconda parte dell’articolo.

Print | posted on venerdì 9 ottobre 2009 03:03 | Filed Under [ Tech Articles ]

Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET