Finalità

In questo articolo vedremo alcune tematiche di base inerenti il pattern MVVM (Model View Viewmodel), cercando di rimanere quanto più possibile all'interno di quanto previsto dal modello, ovvero una riduzione (in vista della completa eliminazione) del code-behind relativo agli elementi grafici, utilizzando XAML, unitamente a classi predisposte, per effettuare la presentazione dei dati.

Introduzione

Il pattern MVVM nasce per mantenere separate la parte grafica dalla business logic di un'applicazione, ed il suo paradigma fondamentale è pertanto l'introduzione, da parte dello sviluppatore, di uno strato intermedio tra la View (ovvero, tutto ciò che dell'applicazione rappresenta la UI) ed il Model (la logica soggiacente al programma, ossia il suo flusso, indipendentemente da contesti grafici. In questo rientra, ovviamente, la gestione di fonti di dati delle quali si desidera effettuare una presentazione).  L'enfasi che si desidera dare a quanto segue è sulla chiarezza, pertanto eviterò tecnicismi eccessivi nella speranza di mantenere una migliore accessibilità ai concetti.

Un primo esempio

Vediamo in questa sezione due concetti primari relativi al pattern MVVM. Come prima cosa, sarà necessario analizzare in breve i concetti di DataContext e Binding. La prima è, in sostanza, la fonte, l'origine, degli elementi sulla base dei quali si può sfruttare il Binding, ovvero il collegamento tra una il valore di una data proprietà ed un controllo visuale.

Supponiamo, per esempio, di avere una finestra WPF con un solo TextBox, e che in quest'ultimo si desideri visualizzare il titolo della finestra. In aggiunta a ciò, si vuole fare in modo che la modifica del contenuto della TextBox produca la medesima variazione nel titolo della finestra, ovvero che i due controlli vengano legati in maniera bidirezionale. Sarà pertanto necessario indicare al TextBox che il suo DataContext è la finestra stessa, e che il Binding della proprietà Text è effettuato sulla proprietà Title della finestra. Nel codice XAML della nostra finestra, ciò si realizzerà con il seguente codice:

<Window x:Class="MainWindow"
    xmlns:local="clr-namespace:WpfApplication1"
    Name="MainWindow"
    Title="MainWindow" Height="111.194" Width="295.149">
     
    <TextBox Name="TB1" Text="{Binding Title, ElementName=MainWindow, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
             HorizontalAlignment="Left" Height="22"  VerticalAlignment="Top" Width="248"/>
</Window>

In sintesi, è stato assegnato alla finestra un dato nome (nell'esempio, MainWindow). Successivamente, si è bindata la proprietà Text del TextBox alla proprietà Title, appartenente all'elemento MainWindow (che, in questo caso, è DataContext di TextBox), in maniera bidirezionale (TwoWay), indicando nella modifica di una delle due proprietà l'evento che deve scatenare l'aggiornamento delle controparti (PropertyChanged). Per informazioni aggiuntive sulla sintassi relativa alle operazioni di Binding, cfr. VB.NET Esempi di base su Data Binding WPF (it-IT).

Testando questo semplice esempio, si noterà che il titolo della videata verrà modificato in funzione di ciò che viene digitato nel TextBox. Allo stesso modo, se da codice si dovesse intervenire sul titolo della finestra, si noterebbe un aggiornamento del testo della casella.

Binding di un modello dati


Supponiamo ora di avere la necessità di gestire un binding verso una classe esterna al contesto in cui lo visualizzeremo. Qui a seguire un semplice esempio di classe utile a rappresentare un ipotetico articolo, dotato di un codice e relativa descrizione:

Public Class ItemData
    Public _code As String
    Public _des As String
 
    Public Property Code As String
        Get
            Return _code
        End Get
        Set(value As String)
            _code = value
        End Set
    End Property
 
    Public Property Description As String
        Get
            Return _des
        End Get
        Set(value As String)
            _des = value
        End Set
    End Property
 
    Public Sub New(ByVal code As String, ByVal des As String)
        _code = code
        _des = des
    End Sub
End Class

Trattandosi di una classe che definisce nuove entità di prodotto, dotata di costruttore che ne inizializza le proprietà fondamentali, essa non può ovviamente fungere da DataContext fintanto che non è referenziata da una variabile. Questo significa che, per poter effettuare un binding su TextBox - supponiamo della proprietà Code - dovremo agire sul code-behind, andando a specificare il DataContext del TextBox solo dopo aver inizializzato una variabile di tipo ItemData.

Per esempio, se stiamo gestendo l'evento Loaded della finestra, potremmo scrivere:

Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs)
  Dim item As New ItemData("PRDCODE01", "TEST PRODUCT")
  TB1.DataContext = item
End Sub


Dove il corrispondente XAML sarà:

<Window x:Class="MainWindow"
    xmlns:local="clr-namespace:WpfApplication1"
    Name="MainWindow" Loaded="MainWindow_Loaded"
    Title="MainWindow" Height="111.194" Width="295.149">
     
    <TextBox Name="TB1" Text="{Binding Code}"
             HorizontalAlignment="Left" Height="22"  VerticalAlignment="Top" Width="248"/>   
</Window>


In questo caso, indicheremo nella proprietà Text del TextBox una sintassi più concisa rispetto alla precedente. Al di là del fatto che qui non si stia gestendo la bidirezionalità del dato, si noti la sola presenza della proprietà Code, senza alcun accenno all'elemento (o DataContext) da cui derivarla. Questo perché il DataContext è specificato code-side, inizializzando una variabile di tipo ItemData, e successivamente indicando come contesto dei dati del TextBox la variabile appena creata. Eseguendo questo esempio, si noterà che il TextBox espone il valore di PR_CODE_01, ovvero la stringa con la quale si è inizializzata la proprietà Code del nostro ItemData.

Come però descritto in apertura, questo approccio - sebbene funzionale - non soddisfa completamente i paradigmi MVVM. Nell'esempio sopra, infatti, abbiamo da un lato la nostra View (la finestra) ed il Model (la variabile item, referenziata come ItemData). Nel code-behind della prima, creiamo una referenza al modello, ed esiste quindi interdipendenza tra le due: se rimuovessimo il codice dell'evento Loaded, il binding sul TextBox non potrebbe avere luogo. Per mantenere separate le due entità, è necessario introdurre uno strato intermedio, ovvero la ViewModel: quest'ultima sarà una classe attraverso la quale eseguire l'esposizione del Model alla View, evitando che la modifica di uno degli altri strati influenzi la logica operativa. Vediamo come.

Una semplice ViewModel


Una ViewModel incapsula un modello, fornendo tutte le proprietà utili alla View per accedere ai dati sottostanti. Generalmente, implementa l'interfaccia INotifyPropertyChanged, che verrà utilizzata, in forma di evento, per tracciare tutte le modifiche alle proprietà di interesse. Nel nostro caso, supponendo di voler imbastire la più minimale delle ViewModel possibili, potremmo scrivere la seguente classe:

Imports System.ComponentModel
Public Class ItemDataView
    Implements INotifyPropertyChanged
 
    Dim item As New ItemData("PR_CODE_01", "TEST PRODUCT")
 
    Public Property Code
        Get
            Return item.Code
        End Get
        Set(value)
            If Not (item.Code = value) Then
                item.Code = value
                NotifyPropertyChanged("Code")
            End If
        End Set
    End Property
 
    Public Event PropertyChanged As PropertyChangedEventHandler _
        Implements INotifyPropertyChanged.PropertyChanged
 
    Private Sub NotifyPropertyChanged(Optional ByVal propertyName As String = Nothing)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub
End Class


Detta classe inizializza un ItemData, creandone una nuova istanza, e ne espone la proprietà Code. Al tempo stesso, permette la variazione di tale proprietà, sollevando una chiamata all'evento PropertyChanged, affinché venga prodotta notifica della modifica del campo.

Affinchè ItemDataView sia visibile ed utilizzabile nell'intero contesto di MainWindow, possiamo indicare nello XAML della finestra il DataContext globale a tutti i controlli in essa contenuti. La proprietà di Binding del TextBox continuerà ad essere Code, esposta dal ViewModel:

<Window x:Class="MainWindow"
    xmlns:local="clr-namespace:WpfApplication1"
    Name="MainWindow"
    Title="MainWindow" Height="111.194" Width="295.149">
     
    <Window.DataContext>
        <local:ItemDataView x:Name="MyItemView"/>
    </Window.DataContext>
     
    <TextBox Name="TB1" Text="{Binding Code}"
             HorizontalAlignment="Left" Height="22"  VerticalAlignment="Top" Width="248"/>   
</Window>

In alternativa, se si desidera indicare lo specifico DataContext del TextBox, si potrà scrivere:

<Window x:Class="MainWindow"
    xmlns:local="clr-namespace:WpfApplication1"
    Name="MainWindow" Title="MainWindow" Height="111.194" Width="295.149">
        
    <TextBox Name="TB1" DataContext="{Binding Source=ItemDataView}" Text="{Binding Code}"
             HorizontalAlignment="Left" Height="22"  VerticalAlignment="Top" Width="248"/>   
</Window>

In entrambi i casi, all'esecuzione del programma troveremo valorizzata la proprietà Text del TextBox con il valore PR_CODE_01, ottenuto dall'ItemData esposto tramite classe ItemDataView.

Conclusione


Abbiamo qui visto alcuni aspetti basilari relativi all'implementazione del pattern MVVM quanto alla presentazione dei dati, e loro successiva modifica, tralasciando tematiche successive per evidenziarne più il discorso teorico generale. In un futuro articolo, verrà trattato l'approccio MVVM applicato ai comandi. Come sempre, la speranza è quella di aver fornito un utile materiale a chi si approccia alla tematica in oggetto. Happy coding!