Alessandro Del Sole's Blog

{ A programming space about Microsoft® .NET® }
posts - 1909, comments - 2047, trackbacks - 352

My Links

News

Your host

This is me! Questo spazio è dedicato a Microsoft® .NET®, di cui sono molto appassionato :-)

Cookie e Privacy

Microsoft MVP

My MVP Profile

Microsoft Certified Professional

Microsoft Specialist

Xamarin Certified Mobile Developer

Il mio libro su VB 2015!

Pre-ordina il mio libro su VB 2015 Pre-ordina il mio libro "Visual Basic 2015 Unleashed". Clicca sulla copertina per informazioni!

Il mio libro su WPF 4.5.1!

Clicca sulla copertina per informazioni! E' uscito il mio libro "Programmare con WPF 4.5.1". Clicca sulla copertina per informazioni!

These postings are provided 'AS IS' for entertainment purposes only with absolutely no warranty expressed or implied and confer no rights.
If you're not an Italian user, please visit my English blog

Le vostre visite

I'm a VB!

Guarda la mia intervista a Seattle

Follow me on Twitter!

Altri spazi

GitHub
I miei progetti open-source su GitHub

Article Categories

Archives

Post Categories

Image Galleries

Privacy Policy

WPF: Introduzione al pattern Model-View-ViewModel per sviluppatori Visual Basic 2010 -parte 8

UPDATED!

Riprendiamo il nostro percorso nello studio del pattern MVVM nei confronti di applicazioni WPF scritte con VB 2010. La scorsa volta ci siamo salutati dopo aver implementato uno strato di servizi, in questo post invece ci occuperemo dei ViewModel. Nello specifico ci occuperemo di trovare un posto all'istanza della classe Messenger nonché di scrivere tutti i ViewModel, per cui sarà un lavoretto un po' lungo, soprattutto in termini di codice dato che i concetti sul ViewModel sono già stati illustrati in questo precedente post.

Messaggiamo!

In tutti i tutorial che ho trovato, e quindi studiato, la dichiarazione della classe Messenger viene messa a livello di applicazione come proprietà di sola lettura. I metodi di tale classe ricevono degli argomenti stringa che rappresentano i messaggi da scambiare tra oggetti "colleghi" e che poi verranno intercettati. Invece di scrivere tutte le volte delle stringhe, si possono definire delle costanti che contengono i messaggi. Quindi la classe Application diventa anche il luogo in cui definire tali costanti. A tal proposito, a noi interessa registrare e intercettare due messaggi: uno per aprire la finestra degli Order Details e uno relativo alla chiusura di tale finestra. Ciò premesso, ecco il codice che ci occorre all'interno del file Application.xaml.vb:

Class Application
 
    ' Application-level events, such as Startup, Exit, and DispatcherUnhandledException
    ' can be handled in this file.
 
    Friend Const VIEW_DETAILS_EXECUTE As String = "ViewDetailsExecute"
    Friend Const VIEW_DETAILS_CLOSE As String = "ViewDetailsClose"
 
    Shared ReadOnly _messenger As New Messenger()
 
    Friend Shared ReadOnly Property Msn As Messenger
        Get
            Return _messenger
        End Get
    End Property
 
    Private Sub Application_DispatcherUnhandledException(ByVal sender As System.ObjectByVal e As System.Windows.Threading.DispatcherUnhandledExceptionEventArgs)
        MessageBox.Show(e.Exception.Message)
        e.Handled = True
    End Sub
End Class

Troveremo anche nel corso di questo post un esempio di utilizzo della classe Messenger. Fatto questo, passiamo ai ViewModel.

Ogni View il suo ViewModel, ogni ViewModel la sua View

Partiamo da un concetto: ogni View, ossia ogni finestra o user control atto alla presentazione dei dati, ha un suo ViewModel col quale dialoga. Nella nostra applicazione abbiamo due finestre: quella principale e quella per la visualizzazione dei dettagli/ordine. Di conseguenza dobbiamo implementare due ViewModel. Come già facemmo precedentemente, implementeremo una classe chiamata ViewModelBase che, tramite ereditarietà, propagherà alcuni membri di interesse comune per tutti i ViewModel. Ciò premesso, nella cartella del progetto che abbiamo chiamato ViewModels aggiungiamo una nuova classe chiamata ViewModelBase.vb. Questa versione della classe ViewModelBase è estesa rispetto alla precedente, con alcuni membri che consentono la validazione di proprietà. Sebbene non faremo un uso particolare di queste feature, vi potrebbe in futuro servire. Inoltre, cosa fondamentale, introduciamo i metodi ServiceLocator e GetService che restituiscono, rispettivamente, l'istanza della classe ServiceLocator che smista le richieste sui servizi e l'istanza della classe di servizio specificata. Di questo faremo uso nei ViewModel derivati. Pertanto, la classe ora si presenta così:

Imports System.ComponentModel
Public MustInherit Class ViewModelBase     Implements INotifyPropertyChanged
    Dim myServiceLocator As New ServiceLocator
    Public Event PropertyChanged(ByVal sender As ObjectByVal e As System.ComponentModel.PropertyChangedEventArgsImplements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
    Protected Sub OnPropertyChanged(ByVal strPropertyName As String)         If Me.PropertyChangedEvent IsNot Nothing Then             RaiseEvent PropertyChanged(MeNew System.ComponentModel.PropertyChangedEventArgs(strPropertyName))         End If     End Sub  
    Private privateThrowOnInvalidPropertyName As Boolean
    Protected Overridable Property ThrowOnInvalidPropertyName() As Boolean         Get             Return privateThrowOnInvalidPropertyName         End Get         Set(ByVal value As Boolean)             privateThrowOnInvalidPropertyName = value         End Set     End Property
      <Conditional("DEBUG"), DebuggerStepThrough()> _     Public Sub VerifyPropertyName(ByVal propertyName As String)         ' Verify that the property name matches a real,           ' public, instance property on this object.         If TypeDescriptor.GetProperties(Me)(propertyName) Is Nothing Then
            Dim msg As String = "Invalid property name: " & propertyName

            If Me.ThrowOnInvalidPropertyName Then                 Throw New Exception(msg)             Else                 Debug.Fail(msg)             End If         End If     End Sub

    Private privateDisplayName As String
    Public Overridable Property DisplayName() As String         Get             Return privateDisplayName         End Get
        Protected Set(ByVal value As String)             privateDisplayName = value         End Set     End Property
    Public Function ServiceLocator() As ServiceLocator         Return Me.myServiceLocator     End Function
    Public Function GetService(Of T)() As T         Return myServiceLocator.GetService(Of T)()     End Function
End Class

Niente di complicato quindi. E, infatti, il bello viene adesso.

Un ViewModel per la relazione Customer/Orders

Nella finestra principale dell'applicazione vogliamo visualizzare un elenco di clienti e, alla selezione del cliente, mostrare i dettagli degli ordini associati al cliente stesso, offrendo anche funzionalità di spostamento tra dati nonché di inserimento ed eliminazione. Quindi dobbiamo creare un ViewModel per esporre alla View i dati richiesti nonché i membri necessari all'esecuzione di varie operazioni. Il ViewModel implementerà anche IDataErrorInfo (tecnica descritta qui) al fine di inviare alla View gli errori di validazione sollevati dal Model. Ora il punto è questo: generalmente, noi esponiamo dati attraverso proprietà di tipo ObservableCollection; questo è corretto, perché tale collezione offre pieno supporto al data-binding in WPF. Come detto, però, a noi interessa avere dei metodi che ci permettano di sfogliare e modificare i dati. Per fare questo, possiamo utilizzare oggetti di tipo ICollectionView ed esporre alle View oggetti di tipo ListCollectionView. Questo particolare oggetto, infatti, offre funzionalità avanzate di lavoro sui dati ed è "bindabile". Detto questo, dovremo implementare in sostanza:

  • Commands per eseguire le operazioni
  • IDataErrorInfo per la validazione
  • Istanze delle classi di servizio che ci consentano di accedere al db
  • Proprietà relative ai Model che espongano i dati alla View
  • Oggetti di tipo ListCollectionView che, in binding, permettano di gestire i dati attraverso la View
  • Proprietà di tipo ObservableCollection che restituiscano i dati. Sebbene questo non sarà utilizzato, può essere utile per futuri e diversi utilizzi mentre i relativi backing field ci occorrono come supporto per le ListCollectionView

Quindi, dopo aver aggiunto un nuovo file di codice chiamato OrdersViewModel.vb al progetto, iniziamo con l'aggiungere i campi di supporto:

Public Class OrdersViewModel
    Inherits ViewModelBase
 
    Implements IDataErrorInfo
 
#Region " Declarations "
 
    'Serie di comandi necessari
    Private _cmdDeleteCommand As ICommand
    Private _cmdInsertCommand As ICommand
    Private _cmdNextCommand As ICommand
    Private _cmdPreviousCommand As ICommand
    Private _cmdSaveCommand As ICommand
    Private _cmdViewDetails As ICommand
 
    'Un singolo ordine
    Private _objOrder As Order
    'Rappresenta l'istanza del cliente selezionato
    Private _selection As Customer
 
    'Espone i dati nel modo classico
    Private _orders As ObservableCollection(Of Order)
    Private _customers As ObservableCollection(Of Customer)
 
    'Un "ponte" tra View e dati
    Private _customerViewSource As New CollectionViewSource
    Private _customerOrdersViewSource As New CollectionViewSource
 
    'Liste di oggetti modificabili, per clienti e ordini
    Private WithEvents _customersView As ListCollectionView
    Private WithEvents _customerOrdersView As ListCollectionView
 
    'Otterrà l'istanza della classe di servizio
    Private orderAccess As IOrderDataService
 
#End Region

A questo punto possiamo passare alle proprietà relative ai dati. Innanzitutto esporremo le CollectionView tramite delle proprietà e la stessa cosa sarà fatta per gli oggetti di tipo ListCollectionView; questo è importante per avere la base di dati su cui lavorare. Poi avremo una proprietà che rappresenta l'istanza del cliente selezionato e una che rappresenta l'istanza dell'ordine selezionato. Esporremo, per completezza, anche 2 proprietà di tipo ObservableCollection per customers e orders nel caso in cui un domani decidessimo di usare queste invece che le ListCollectionView. Alla fine del discorso, il codice delle proprietà è il seguente:

#Region " Properties "
 
    Public ReadOnly Property CustomerViewSource As CollectionViewSource
        Get
            Return Me._customerViewSource
        End Get
    End Property
 
    Public Property CustomerOrdersViewSource As CollectionViewSource
        Get
            Return Me._customerOrdersViewSource
        End Get
        Set(ByVal value As CollectionViewSource)
            Me._customerOrdersViewSource = value
            OnPropertyChanged("CustomerOrdersViewSource")
        End Set
    End Property
 
    Public Property CustomersView As ListCollectionView
        Get
            Return Me._customersView
        End Get
        Set(ByVal value As ListCollectionView)
            Me._customersView = value
            OnPropertyChanged("CustomersView")
        End Set
    End Property
 
    Public Property CustomerOrdersView As ListCollectionView
        Get
            Return Me._customerOrdersView
        End Get
        Set(ByVal value As ListCollectionView)
            Me._customerOrdersView = value
            OnPropertyChanged("CustomerOrdersView")
        End Set
    End Property
 
    'Rappresenta l'istanza del cliente selezionato
    Public Property Selection As Customer
        Get
            Return Me._selection
        End Get
        Set(ByVal value As Customer)
            If value Is _selection Then
                Return
            End If
            _selection = value
            MyBase.OnPropertyChanged("Selection")
        End Set
    End Property
 
    Public Property Customers As ObservableCollection(Of Customer)
        Get
            Return Me._customers
        End Get
        Set(ByVal value As ObservableCollection(Of Customer))
            Me._customers = value
            OnPropertyChanged("Customers")
        End Set
    End Property
 
    Public Property Orders As ObservableCollection(Of Order)
        Get
            Return Me._orders
        End Get
        Set(ByVal value As ObservableCollection(Of Order))
            Me._orders = value
            OnPropertyChanged("Orders")
        End Set
    End Property
 
    Public Property Order() As Order
        Get
            Return _objOrder
        End Get
        Set(ByVal Value As Order)
            _objOrder = Value
            OnPropertyChanged("Order")
        End Set
    End Property
 
    Public Property Customer() As Customer
        Get
            Return _objOrder.Customer
        End Get
        Set(ByVal Value As Customer)
            _objOrder.Customer = Value
            OnPropertyChanged("Customer")
        End Set
    End Property
 
#End Region

Fatto questo abbiamo bisogno delle proprietà che espongano i Command. Niente di difficile, ne abbiamo parlato qui. Ecco il codice:

#Region " Command Properties "
 
    Public ReadOnly Property DeleteCommand() As ICommand
        Get
            If _cmdDeleteCommand Is Nothing Then
                _cmdDeleteCommand = New RelayCommand(AddressOf DeleteExecute, AddressOf CanDeleteExecute)
            End If
            Return _cmdDeleteCommand
        End Get
    End Property
 
    Public ReadOnly Property InsertCommand() As ICommand
        Get
            If _cmdInsertCommand Is Nothing Then
                _cmdInsertCommand = New RelayCommand(AddressOf InsertExecute, AddressOf CanInsertExecute)
            End If
            Return _cmdInsertCommand
        End Get
    End Property
 
    Public ReadOnly Property NextCommand() As ICommand
        Get
            If _cmdNextCommand Is Nothing Then
                _cmdNextCommand = New RelayCommand(AddressOf NextExecute, AddressOf CanNextExecute)
            End If
            Return _cmdNextCommand
        End Get
    End Property
 
    Public ReadOnly Property PreviousCommand() As ICommand
        Get
            If _cmdPreviousCommand Is Nothing Then
                _cmdPreviousCommand = New RelayCommand(AddressOf PreviousExecute, AddressOf CanPreviousExecute)
            End If
            Return _cmdPreviousCommand
        End Get
    End Property
 
    Public ReadOnly Property SaveCommand() As ICommand
        Get
            If _cmdSaveCommand Is Nothing Then
                _cmdSaveCommand = New RelayCommand(AddressOf SaveExecute, AddressOf CanSaveExecute)
            End If
            Return _cmdSaveCommand
        End Get
    End Property
 
    Public ReadOnly Property ViewDetailsCommand() As ICommand
        Get
            If _cmdViewDetails Is Nothing Then
                _cmdViewDetails = New RelayCommand(AddressOf ViewDetailsExecute, AddressOf CanViewDetailsExecute)
            End If
            Return _cmdViewDetails
        End Get
    End Property
 
#End Region

A questo punto passiamo ai membri dell'interfaccia IDataErrorInfo. Anche in questo caso, nulla di complicato e già discusso precedentemente:

#Region " IDataErrorInfo members "
    Public ReadOnly Property [Error] As String Implements System.ComponentModel.IDataErrorInfo.Error
        Get
            Return TryCast(Me.Order, IDataErrorInfo).Error
        End Get
    End Property
 
    Default Public ReadOnly Property Item(ByVal columnName As StringAs String Implements System.ComponentModel.IDataErrorInfo.Item
        Get
            Dim [error] As String = Nothing
 
            [error] = (TryCast(Me.Order, IDataErrorInfo))(columnName)
            ' Dirty the commands registered with CommandManager,
            ' such as our Save command, so that they are queried
            ' to see if they can execute now.
            CommandManager.InvalidateRequerySuggested()
 
            Return [error]
        End Get
    End Property
#End Region

Le cose si fanno più interessanti perché, prima di fare altro, scriviamo il codice del costruttore.

Quando il gioco si fa duro...

Il costruttore del nostro ViewModel svolge un ruolo fondamentale. Al suo interno, infatti, dovremo registrare e istanziare le classi di servizio che eseguiranno l'effettivo lavoro di accesso ai dati tramite Entity Framework. Prima il codice, poi le considerazioni:

#Region " Constructors "
 
    Public Sub New()
        Me._customers = New ObservableCollection(Of Customer)
 
        'Registra l'istanza della classe di servizio relativa ai clienti
        ServiceLocator.RegisterService(Of ICustomerDataService)(New CustomerDataService)
        'Registra l'istanza della classe di servizio relativa agli ordini
        ServiceLocator.RegisterService(Of IOrderDataService)(New OrderDataService)
 
        'Ottiene l'istanza delle due classi
        Dim dataAccess = GetService(Of ICustomerDataService)()
        Me.orderAccess = GetService(Of IOrderDataService)()
 
        'Cicla l'elenco dei clienti ottenuto e lo aggiunge
        'alla collezione
        For Each element In dataAccess.GetAllCustomers
            Me._customers.Add(element)
        Next
 
        'Imposta la CollectionViewSource
        _customerViewSource.Source = Me.Customers
 
        'Ottiene la View della CollectionViewSource e la converte
        'in una ListCollectionView con supporto alla modifica
        Me.CustomersView = CType(Me.CustomerViewSource.View, ListCollectionView)
        Me.CustomersView.MoveCurrentToFirst()
    End Sub

#End Region

Tramite il metodo condiviso RegisterService della classe ServiceLocator, registriamo due istanze per le classi di servizio CustomerDataService e OrderDataService. Notate che il parametro generico di RegisterService non è la classe, ma l'interfaccia. Ecco perché diventa importante scrivere delle interfacce che poi vengono implementate. Tramite il metodo GetService esposto dalla classe base, poi otteniamo l'istanza delle classi di servizio. Quella relativa agli ordini è dichiarata a livello di classe perché la useremo, a breve, all'interno dei command methods. Un primo esempio d'uso è il ciclo For Each che consente di popolare il campo relativo all'elenco clienti; notate, infatti, come si invochi il metodo GetAllCustomers esposto dalla classe di servizio. Qui la cosa è di fondamentale interesse: il ViewModel ottiene un elenco di dati ma non sa che tali dati provengono da un Entity Data Model. Il resto, poi, è cosa nota.

Proseguiamo con i command methods

Ora ci occupiamo di implementare i metodi per l'esecuzione dei commands, in linea generale i metodi CanXXXExecute e XXXExecute, dove XXX è il nome dell'azione da eseguire. Non c'è nulla di particolarmente difficile, abbiamo parlato di questo discorso qui. Però ci sono da notare alcune cose. Ogni metodo di esecuzione richiama il corrispondente metodo esposto dalla classe di servizio. Quindi, mentre in scenari diversi abbiamo visto come eseguire le azioni nei confronti dei dati direttamente all'interno di questi metodi, qui deleghiamo il tutto alla classe di servizio. In questo modo, il ViewModel è comunque in grado di lavorare con i dati ma è astratto dal Data Access Layer, che in questo caso è rappresentato da Entity Framework a cui si rivolge la classe OrderDataService. Quindi se un domani vorrò cambiare sorgente dati, pur con le ovvie attenzioni del caso, il mio ViewModel può rimanere sostanzialmente lo stesso. C'è un altro dettaglio interessante, di cui vi do conto dopo il codice:

#Region " Command Methods "
 
    Private Function CanDeleteExecute(ByVal param As ObjectAs Boolean
        If Me.CustomerOrdersView Is Nothing Then Return False
        Return Me.CustomerOrdersView.CurrentPosition > -1
    End Function
 
    Private Sub DeleteExecute(ByVal param As Object)
        Me.orderAccess.Delete(Me.CustomerOrdersView)
    End Sub
 
    Private Function CanInsertExecute(ByVal param As ObjectAs Boolean
        Return True
    End Function
 
    Private Sub InsertExecute(ByVal param As Object)
        Me.orderAccess.Insert(Me.CustomerOrdersView, Me.Selection)
    End Sub
 
    Private Function CanNextExecute(ByVal param As ObjectAs Boolean
        If Me.CustomerOrdersViewSource.View Is Nothing Then Return False
        Return Me.CustomerOrdersViewSource.View.CurrentPosition <
            CType(Me.CustomerOrdersViewSource.View, CollectionView).Count - 1
    End Function
 
    Private Sub NextExecute(ByVal param As Object)
        If Me.CanNextExecute(param) Then
            Me.orderAccess.MoveToNext(Me.CustomerOrdersViewSource)
        End If
    End Sub
 
    Private Function CanPreviousExecute(ByVal param As ObjectAs Boolean
        If Me.CustomerOrdersViewSource.View Is Nothing Then Return False
        Return Me.CustomerOrdersViewSource.View.CurrentPosition > 0
    End Function
 
    Private Sub PreviousExecute(ByVal param As Object)
        If Me.CanPreviousExecute(param) Then
            Me.orderAccess.MoveToPrevious(Me.CustomerOrdersViewSource)
        End If
    End Sub
 
    Private Function CanSaveExecute(ByVal param As ObjectAs Boolean
        Try
            If CType(Me.CustomerOrdersView.CurrentItem, Order).HasErrors Then
                Return False
            Else
                Return True
            End If
        Catch ex As Exception
 
        End Try
    End Function
 
    Private Sub SaveExecute(ByVal param As Object)
        Me.orderAccess.Save()
    End Sub
 
    Private Function CanViewDetailsExecute(ByVal param As ObjectAs Boolean
        Return Me.Selection IsNot Nothing
    End Function
 
    Private Sub ViewDetailsExecute(ByVal param As Object)
        Application.Msn.NotifyColleagues(Application.VIEW_DETAILS_EXECUTE)
    End Sub
 
#End Region

L'ultimo metodo, ViewDetailsExecute, fa finalmente utilizzo della classe Messenger. Il metodo NotifyColleagues sta inviando il messaggio definito nella costante VIEW_DETAILS_EXECUTE di modo che, quando il messaggio viene intercettato dalla View, verrà aperta la finestra relativa agli Order Details. Per quanto riguarda il lato della View lo vedremo al termine della serie di post, nel quale faremo un raccordo con le spiegazioni inerenti questa tecnica.

Cambio di selezione

L'ultimo step di questo OrdersViewModel è la gestione di un evento. Poiché abbiamo necessità di capire quando il nostro utente seleziona un Customer differente tramite la UI, possiamo gestire l'evento CurrentChanged sulla CollectionView che espone i Customer. Quando l'evento viene intercettato, si caricano gli ordini relativi al nuovo cliente selezionato. Il tutto avviene ancora invocando i metodi dalla classe di servizio OrderDataService. Ecco il codice, che chiude anche la classe:

#Region " Event handlers"
    '_customersView è il backing field per la proprietà CustomersView
    Private Sub _customersView_CurrentChanged(ByVal sender As ObjectByVal e As System.EventArgsHandles _customersView.CurrentChanged
 
        Dim currentCustomer = CType(Me.CustomersView.CurrentItem, Customer)
 
        Try
            Me.Orders = orderAccess.GetAllOrders(currentCustomer.CustomerID)
            Me.CustomerOrdersViewSource.Source = Me.Orders
            Me.CustomerOrdersView = CType(Me.CustomerOrdersViewSource.View, ListCollectionView)
        Catch ex As Exception
 
        End Try
    End Sub
#End Region
 
End Class

Passiamo ora al secondo ViewModel, quello relativo agli Order Details.

Parte due: OrderDetailsViewModel

Il ViewModel per la gestione degli Order Details funziona, come logica di base, in modo analogo al precedente anche se con minori funzionalità. Diciamo che per quanto riguarda la nostra applicazione a noi interesserà semplicemente mostrare l'elenco degli Order Details ma implementeremo anche dei metodi di salvataggio dati e chiusura, soprattutto perché quest'ultima informazione potrebbe essere utile alla View chiamante. Anche in questo ViewModel faremo uso di un'istanza della classe OrderDataService, secondo le modalità viste nel precedente ViewModel, per invocare il metodo che ci consentirà di ottenere l'elenco degli order details per l'ordine specificato. Tutte le tecniche usate in questo ViewModel sono state già discusse nel precedente, per cui se qualcosa non è chiaro potete riguardare la precedente sezione. Questo, invece, è il codice del nuovo OrderDetailsViewModel che va aggiunto, come classe, alla cartella di progetto chiamata ViewModels:

Imports System.Collections.ObjectModel
 
Public Class OrderDetailsViewModel
    Inherits ViewModelBase
 
#Region " Declarations "
 
    Private _cmdSaveCommand As ICommand
    Private _cmdCloseCommand As ICommand
 
    Private _objOrder_Detail As Order_Detail
    Private _orderDetails As ObservableCollection(Of Order_Detail)
    Dim _selection As Order_Detail
 
    Private _orderDetailsViewSource As CollectionViewSource
    Private _orderDetailsView As ListCollectionView
 
    Private dataAccess As IOrderDataService
#End Region
 
#Region " Properties "
 
 
    Public Property OrderDetailsViewSource As CollectionViewSource
        Get
            Return Me._orderDetailsViewSource
        End Get
        Set(ByVal value As CollectionViewSource)
            Me._orderDetailsViewSource = value
            OnPropertyChanged("OrderDetailsViewSource")
        End Set
    End Property
 
    Public Property OrderDetailsView As ListCollectionView
        Get
            Return Me._orderDetailsView
        End Get
        Set(ByVal value As ListCollectionView)
            Me._orderDetailsView = value
            OnPropertyChanged("OrderDetailsView")
        End Set
    End Property
 
    Public Property Order_Detail() As Order_Detail
        Get
            Return _objOrder_Detail
        End Get
        Set(ByVal Value As Order_Detail)
            _objOrder_Detail = Value
            OnPropertyChanged("Order_Detail")
        End Set
    End Property
 
    Public Property Order_Details As ObservableCollection(Of Order_Detail)
        Get
            Return Me._orderDetails
        End Get
        Set(ByVal value As ObservableCollection(Of Order_Detail))
            Me._orderDetails = value
            OnPropertyChanged("Order_Details")
        End Set
    End Property


    Public Property Selection As Order_Detail
        Get
            Return Me._selection
        End Get
        Set(ByVal value As Order_Detail)
            If value Is _selection Then
                Return
            End If
            _selection = value
            MyBase.OnPropertyChanged("Selection")
        End Set
    End Property
 
#End Region
 
#Region " Command Properties "
 
    Public ReadOnly Property SaveCommand() As ICommand
        Get
            If _cmdSaveCommand Is Nothing Then
                _cmdSaveCommand = New RelayCommand(AddressOf SaveExecute, AddressOf CanSaveExecute)
            End If
            Return _cmdSaveCommand
        End Get
    End Property
 
    Public ReadOnly Property CloseCommand As ICommand
        Get
            If _cmdCloseCommand Is Nothing Then
                _cmdCloseCommand = New RelayCommand(AddressOf CloseExecute, AddressOf CanCloseExecute)
            End If

            Return _cmdCloseCommand
        End Get
    End Property
#End Region
 
#Region " Constructors "
 
    Public Sub New(ByVal OrderID As Integer)
        Me._orderDetails = New ObservableCollection(Of Order_Detail)
 
        ServiceLocator.RegisterService(Of IOrderDataService)(New OrderDataService)
        Me.dataAccess = GetService(Of IOrderDataService)()

        Me._orderDetails = dataAccess.GetOrderDetailsByOrderId(OrderID)
        Me._orderDetailsViewSource = New CollectionViewSource
        Me.OrderDetailsViewSource.Source = Me.Order_Details
        Me.OrderDetailsView = CType(Me.OrderDetailsViewSource.View, ListCollectionView)
 
    End Sub
#End Region
 
#Region " Command Methods "
 
    Private Function CanSaveExecute(ByVal param As ObjectAs Boolean
        Return True
    End Function
 
    Private Sub SaveExecute(ByVal param As Object)
        Me.dataAccess.Save()
    End Sub
    Private Sub CloseExecute(ByVal param As Object)
        Application.Msn.NotifyColleagues(Application.VIEW_DETAILS_CLOSE)
    End Sub
 
    Private Function CanCloseExecute(ByVal param As ObjectAs Boolean
        Return True
    End Function
 
#End Region
End Class

Notate come, anche qui, venga richiamato il metodo Messenger.NotifyColleagues in fase di chiusura dell'oggetto. Questo può essere utile nel caso in cui dobbiamo far sapere al chiamante che la View associata si stia chiudendo. Quindi ora abbiamo tutta l'infrastruttura che lavora con i dati, passando per uno strato di servizi che permette ai ViewModel di non sapere con chi sta dialogando, ma di usarne le fonti dati. Bello, no?

Fine della parte

Questo lungo post si conclude qui, dopo un lungo lavoro. Riepilogando abbiamo:

  1. collocato la dichiarazione di istanza della classe Messenger
  2. implementato una classe ViewModelBase
  3. implementato due ViewModel, uno per visualizzare la relazione customer/orders e uno per visualizzare gli order details

Ci manca l'ultimo passaggio, ossia definire le View. Ma non lo faremo nel prossimo post, bensì tra due. Infatti nel prossimo post faremo una digressione che ritengo interessante: faremo innanzitutto un po' di refactoring del codice, ma soprattutto scriveremo degli unit test per testare porzioni del nostro codice ed osservare uno dei benefici più importanti del pattern MVVM.

Alessandro

Print | posted on domenica 8 agosto 2010 00:11 | Filed Under [ Visual Basic Windows Presentation Foundation Visual Studio 2010 ]

Powered by:
Powered By Subtext Powered By ASP.NET