Data-binding a una ListView in WPF con Visual Basic 2008 e LINQ
a cura di Alessandro Del Sole (requisiti: conoscenze di base su Windows Presentation Foundation)Introduzione
Come ormai sapete, Windows Presentation Foundation è la branca di .NET Framework che offre l'infrastruttura per applicazioni caratterizzate dall'interfaccia grafica estremamente accattivante e in grado di incapsulare contenuti multimediali come audio e video sfruttando, dietro le scene, le librerie grafiche Microsoft DirectX®.Tuttavia, WPF non è solo multimedialità. Le applicazioni basate su WPF, infatti, possono avere la necessità di offrire funzionalità di accesso ai dati congiuntamente all'implementazione di interfacce grafiche avanzate. In questo articolo introdurremo il concetto di data-binding in applicazioni WPF, ovvero la possibilità di collegare una sorgente dati ad elementi dell'interfaccia esaminando le modalità di aggiornamento automatico della stessa al variare della sorgente.
Lo scopo dell'applicazione di esempio che realizzeremo sarà quello di ottenere un elenco dei processi attivi di Windows sfruttando interrogazioni e filtri basati su Microsoft LINQ, per poi inviare il risultato della query (la sorgente dati) a una ListView di WPF, il tutto utilizzando Visual Basic 2008.
Per raggiungere gli obiettivi proposti è sufficiente utilizzare Visual Basic 2008 Express Edition. Prima di proseguire nella lettura dell'articolo, potete scaricare il progetto sorgente di esempio dall'area download di VB T&T e, se non avete familiarità con WPF, è bene che leggiate prima la serie di miei articoli introduttivi all'argomento.
Un briciolo di teoria - INotifyPropertyChanged e ObservableCollection(Of T)
Il data-binding è un concetto molto caro alla maggior parte degli sviluppatori, poiché, come detto, riguarda la possibilità di collegare una sorgente dati (come, per esempio, una collection) ad elementi dell'interfaccia facendo in modo che, al variare della sorgente dati, l'interfaccia stessa si aggiorni automaticamente e viceversa. In WPF, il data-binding avviene in maniera piuttosto diversa rispetto a Windows Forms.Affinché una classe sia collegabile (bindable) a un elemento dell'interfaccia grafica, è necessario che implementi l'interfaccia INotifyPropertyChanged. Questa interfaccia richiede l'implementazione, nella classe, di un evento PropertyChanged. In questo modo, quando l'oggetto viene modificato, l'elemento dell'interfaccia a cui è collegato riceve una notifica e si aggiorna automaticamente.
Non entreremo nel merito di queste affermazioni poiché questo scenario, per quanto possibile, può avere dei limiti. Infatti, una situazione di questo tipo si può verificare quando si hanno controlli indipendenti (per esempio una TextBox e una CheckBox) ognuno dei quali collegato a una proprietà della classe bindable. Ma cosa succede se avessimo la necessità di lavorare su una collezione di oggetti e collegarla a un controllo in grado di utilizzarla come fonte dati, come per esempio ListBox e ListView? Fortunatamente, il .NET Framework mette a disposizione una speciale collezione generica chiamata ObservableCollection(Of T) appartenente al namespace System.Collections.ObjectModel. Questa collection implementa di base l'interfaccia INotifyPropertyChanged e, cosa molto importante, consente agli elementi dell'interfaccia di ricevere notifiche circa le modifiche agli oggetti e, al tempo stesso, realizza l'effetto contrario ossia permette agli oggetti di aggiornarsi in base all'input fornito dall'utente attraverso l'interfaccia grafica.
Nel nostro esempio faremo uso della ObservableCollection per ottenere una collezione di processi di Windows da collegare a una ListView, ma non ci soffermeremo sulla possibilità di ricevere input da parte dell'utente. Ad ogni buon conto, al termine dell'articolo troverete dei riferimenti alla documentazione MSDN che vi permetteranno di approfondire lo specifico settore, oltre ai cenni sui DataTemplate, che vi permetteranno di capire come strutturare gli elementi della ListView anche come input. Dopo questa infarinatura, possiamo passare alla pratica creando la nostra applicazione di esempio.
Creazione del progetto di esempio
Una volta avviato Visual Studio 2008, create un nuovo progetto vuoto selezionando il modello di progetto chiamato Applicazione WPF ed assegnategli il nome adsWPFDataBinding, come in figura:![]()
Fatto questo, l'IDE si presenta nel modo consueto con riferimento alle applicazioni WPF. Ora passeremo a scrivere codice inerente il funzionamento della nostra applicazione. A differenza di quanto abbiamo visto finora negli articoli dedicati a WPF, stavolta la definizione dell'interfaccia grafica costituirà la seconda parte del nostro lavoro. Infatti, in questo caso, è opportuno dapprima definire gli oggetti che costituiranno la sorgente dati, che sarà collegata all'interfaccia solo successivamente.
Predisposizione della sorgente dati
La prima operazione da compiere è attivare l'editor di codice Visual Basic 2008 sul file di code-behind relativo al file Window1.Xaml, quindi Window1.Xaml.vb. Questa operazione si effettua utilizzando la finestra Esplora soluzioni, espandendo la struttura del file .Xaml e facendo doppio clic sul file di codice .vb. Quando l'editor è disponibile, scriviamo in primo luogo la seguente direttiva Imports utile per abbreviare il richiamo all'oggetto ObservableCollection:Imports System.Collections.ObjectModelOra facciamo una considerazione preliminare. Supponiamo di voler ottenere un elenco dei processi attivi di Windows e, per ciascuno di essi, di voler estrarre solo alcune proprietà come l'ID e il nome. Eseguiremo questa estrazione mediante una query LINQ, ma prima è opportuno dichiarare una classe che esponga queste due semplici proprietà e che rappresenti, in maniera ridotta, un singolo processo. Quindi, all'interno della classe Window1, scrivete quanto segue:
'Una classe che rappresenti parzialmente un processo Class CustomProcess Private _ProcessID As Integer Private _ProcessName As String Public Property ProcessID() As Integer Get Return _ProcessID End Get Set(ByVal value As Integer) _ProcessID = value End Set End Property Public Property ProcessName() As String Get Return _ProcessName End Get Set(ByVal value As String) _ProcessName = value End Set End Property End ClassNon esplicitiamo il costruttore, poiché faremo uso della tecnica degli Object initializer.
Ora aggiungete la seguente dichiarazione:Private CustomProcesses As ObservableCollection(Of CustomProcess)Quindi abbiamo a disposizione una collezione di oggetti CustomProcess che implementa funzionalità di invio e ricezione notifiche di modifiche da e per l'interfaccia. Istanzieremo la collezione successivamente; ora, invece, ci soffermiamo sulla definizione di un metodo che ottenga l'elenco dei processi sfruttando LINQ:
Private Sub GetCustomProcesses() Dim processi = From proc In Process.GetProcesses _ Where proc.Id > 10 _ Select proc.Id, proc.ProcessName For Each processo In processi Dim proc As New CustomProcess With {.ProcessID = processo.Id, .ProcessName = processo.ProcessName} CustomProcesses.Add(proc) Next End SubIn breve, la nostra query LINQ ottiene l'elenco dei processi (From proc In Process.GetProcesses) il cui numero identificativo sia maggiore di dieci (Where proc.Id > 10) facendo confluire in una collezione di tipo IEnumerable(Of tipo anonimo), determinata per inferenza, solo l'ID e il nome del processo, ossia le due proprietà che caratterizzano la nostra classe personalizzata.
La successiva iterazione tramite For..Each istanzia un oggetto CustomProcess per ciascun elemento della collezione ottenuta, utilizzando la sintassi degli Object initializer per l'assegnazione in-line delle proprietà della classe. Infine, ciascun oggetto ottenuto viene aggiunto alla collezione CustomProcesses. Per ulteriori informazioni sull'utilizzo di LINQ per interrogare oggetti in memoria, vi rimando al mio articolo introduttivo dedicato a LINQ-to-Objects.
Ora non ci rimane che istanziare la collezione CustomProcesses e assegnarla all'interfaccia come sorgente dati. Ciò premesso, riscriviamo il costruttore della classe Window1 come segue:
Public Sub New() ' This call is required by the Windows Form Designer. InitializeComponent() ' Add any initialization after the InitializeComponent() call. 'Istanziamo la collezione... CustomProcesses = New ObservableCollection(Of CustomProcess) '..la popoliamo... GetCustomProcesses() '..e la assegnamo come sorgente dati alla ListView MyList.ItemsSource = CustomProcesses End SubI commenti nel codice aiutano nella comprensione. Sottolineiamo l'assegnazione della collezione CustomProcesses alla proprietà ItemsSource di un oggetto chiamato MyList, il quale altro non è che un'istanza di un controllo ListView che ora definiremo tramite codice XAML.
Implementazione dell'interfaccia grafica
Dopo aver scritto codice managed, è il momento di scrivere qualche riga di codice XAML per definire la UI della nostra applicazione. In Esplora soluzioni, fate doppio clic sul file Window1.Xaml per tornare al designer WPF. Per impostazione predefinita, Visual Studio 2008 aggiunge alla nostra Window un contenitore Grid. All'interno di questo definiremo una ListView. Scrivete il codice seguente, del quale vedremo alcuni approfondimenti di seguito:<Grid> <ListView x:Name="MyList"> <ListView.View> <GridView> <GridViewColumn Header="Name" Width="150" DisplayMemberBinding="{Binding Path=ProcessName}"/> <GridViewColumn Header="ID" Width="100" DisplayMemberBinding="{Binding Path=ProcessID}"/> </GridView> </ListView.View> </ListView> </Grid>L'attributo x:Name per l'elemento ListView stabilisce l'identificatore che ci ha permesso di richiamare il controllo da Visual Basic. Come in Windows Forms, anche in WPF la ListView può presentarsi in modo diverso, specificato attraverso l'elemento ListView.View. In questo caso utilizziamo un oggetto nidificato chiamato GridView, che ci permette di impostare la visualizzazione del controllo sotto forma di griglia e di specificare delle colonne di intestazione (GridViewColumn). In merito a queste ultime, l'attributo Header consente di indicare il testo per l'intestazione della colonna mentre di fondamentale importanza è l'attributo DisplayMemberBinding.
A tal proposito, facciamo una considerazione. Ricorderete sicuramente come nell'ultimo frammento di codice Visual Basic abbiamo assegnato una sorgente dati alla proprietà ItemsSource della ListView. Ciascuna riga della ListView, quindi, corrisponde a un singolo oggetto della collezione assegnata. Per tale motivo, la proprietà DisplayMemberBinding permette di indicare la proprietà del singolo oggetto (in questo caso CustomProcess) il cui valore deve essere mostrato nella colonna di riferimento. Proprio perché la ListView è collegata alla collezione CustomProcesses, non è necessario riportare il nome completo della classe ma solo il nome della proprietà. Questo si fa utilizzando una cosiddetta XAML extension, caratterizzata da una dicitura inserita tra parentesi graffe costituita dalla parola chiave Binding seguita dall'attributo Path il cui valore è costituito proprio dalla proprietà da mostrare in ciascuna colonna.
La XAML extension caratterizzata da Binding è molto utilizzata in WPF e non solo per l'associazione di dati, intesi in senso classico, a controlli dell'interfaccia. Pensate, per esempio, a controlli come ProgressBar e Slider il cui stato di avanzamento può essere modificato a run-time modificando il valore tramite codice Visual Basic; tale valore viene poi inviato all'interfaccia proprio mediante il Binding del controllo interessato.
Ora che abbiamo completato anche la fase di definizione dell'interfaccia, possiamo avviare l'applicazione dimostrativa prima di fare alcune considerazioni sulla personalizzazione della ListView secondo l'ottica tipica di WPF.Esecuzione dell'applicazione
Premete F5 per avviare la generazione della soluzione e la successiva esecuzione dell'applicazione di esempio. Dopo alcuni secondi, otterrete un risultato simile a quanto rappresentato in figura:![]()
Nel nostro esempio abbiamo utilizzato LINQ-to-Objects per ottenere l'elenco dei processi attivi nel sistema. Utilizzando la stessa metodologia, avremmo potuto ottenere un elenco di clienti o di prodotti da un database SQL sfruttando LINQ-to-SQL e collegando il risultato dell'interrogazione all'interfaccia. In questo senso, le possibilità sono molto ampie e dipendono prevalentemente dalle vostre esigenze personali di sviluppo.
Cenni sullo styling della ListView
La ListView è un controllo WPF al quale possono essere applicati gli stili, altra importante tematica di questa tecnologia, che abbiamo descritto in questo mio precedente articolo. In particolare, è possibile stilizzare intere colonne o il comportamento dei singoli elementi. Oltre ad utilizzare gli stili propriamente detti, è possibile implementare un particolare modello per ciascuna riga della ListView. Questo modello viene definito DataTemplate e permette di aggregare tra loro controlli dell'interfaccia al fine di definire il comportamento complessivo del controllo principale. Ipotizziamo, per esempio, di voler visualizzare il risultato del codice di cui sopra in una ListView che abbia sempre due colonne, ma in cui la prima abbia sfondo giallo con testo di primo piano verde e la seconda con sfondo rosso e testo di primo piano bianco.Se volete verificare, tornate all'editor di codice XAML e modificate il codice che dichiara la ListView nel modo seguente:
<ListView x:Name="MyList" > <ListView.ItemTemplate> <DataTemplate> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <TextBlock Width="150" Grid.Column="0" Foreground="Green" Text="{Binding Path=ProcessName}" Background="Yellow"/> <TextBlock Width="100" Grid.Column="1" Foreground="White" Text="{Binding Path=ProcessID}" Background="Red"/> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView>L'elemento ListView.ItemTemplate indica che si sta definendo un modello (template) per ciascun elemento della ListView. L'elemento DataTemplate è il contenitore dei controlli che definiscono l'interfaccia di ciascun elemento. Per esempio, il nostro DataTemplate è costituito da una griglia con due colonne. Entrambe le colonne contengono un controllo TextBlock che riceve, tramite binding, i dati di interesse e per i quali sono specificate alcune proprietà come per esempio i colori.
Particolarmente interessante da far notare è che possiamo definire un DataTemplate che permetta di inserire in ciascun ListViewItem controlli di altro genere, come per esempio CheckBox, TextBox, ProgressBar o addirittura controlli complessi composti da ulteriori aggregazioni di controlli o controlli utente personalizzati. Pensate, per esempio, a scenari in cui si ottengano dalla sorgente dati valori booleani che debbano essere rappresentati mediante lo stato di una CheckBox: grazie ai DataTemplate, è possibile realizzare questo obiettivo.
Se provate ad avviare l'applicazione dopo le modifiche suggerite, otterrete un risultato simile a quanto rappresentato in figura:![]()
Ovviamente, sarebbe preferibile implementare degli stili in base a particolari eventi o stati del controllo; per esempio, quando l'elemento della ListView viene selezionato, potrebbe essere opportuno prevedere un colore di sfondo più adatto. Nella sezione successiva viene riportato un link al mio blog che illustra come raggiungere quest'ultimo obiettivo.
Riferimenti
Si segnalano i seguenti articoli tratti dalla libreria MSDN, utili all'approfondimento della tematica presa in considerazione:
- Procedura: creare una ListView con celle modificabili;
- Panoramica sul controllo ListView (illustra anche la possibilità di assegnare una sorgente dati alla proprietà ItemsSource della ListView anche in modalità dichiarativa, sfruttando le XAML extension);
- post sul mio blog che illustra l'utilizzo di Microsoft Expression Blend 2.5 per applicare stili agli elementi di controlli ListBox e ListView.
Conclusioni
Il data-binding è un concetto fondamentale in ogni contesto di sviluppo e WPF non rimane escluso. In questo articolo abbiamo solo introdotto il concetto in questione applicandolo a un controllo ListView e, sebbene le possibilità siano davvero molte, ora sapete qualcosa in più sul funzionamento del data-binding in Windows Presentation Foundation e avete le basi necessarie per approfondire l'argomento.
Per ulteriori informazioni potete consultare la documentazione MSDN ufficiale mentre, come di consueto potete contattarmi al mio indirizzo e visitare il mio blog, nel quale potrete trovare numerosi esempi e articoli dedicati a Windows Presentation Foundation.