Alessandro Del Sole's Blog

{ A programming space about Microsoft® .NET® }
posts - 1907, 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

Disabilita cookie ShinyStat

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 con Visual Basic 2010 - parte 4

Nel precedente post della serie introduttiva a Model-View-ViewModel con Visual Basic 2010, abbiamo visto come implementare funzionalità di spostamento tra “record” esponendo una ICollectionView dal ViewModel e abbiamo altresì visto come fornire un’implementazione generica della classe RelayCommand.

In questo post ci proponiamo di estendere l’applicazione della volta scorsa aggiungendo funzionalità di validazione dei dati che sfruttino la caratteristica degli ErrorTemplate di WPF e l’interfaccia IDataErrorInfo. Premetto che questa è una modalità, ma chiaramente si possono prevedere altre forme di validazione dei dati. Il vantaggio di questo tipo di approccio è che tramite un ErrorTemplate possiamo agganciare le informazioni di IDataErrorInfo e mostrarle nell’interfaccia in modo semplice e user friendly. In fondo alla pagina trovate il link per scaricare il progetto completo, ma potrebbe esservi utile lavorare su quello dello scorso post in modo da estenderlo col codice presentato oggi. Il risultato che andremo ad ottenere alla fine del post è il seguente:

La logica di validazione

In uno scenario come quello che stiamo affrontando, la logica di validazione deve influenzare il singolo Customer. Tale classe dovrà quindi implementare IDataErrorInfo e la relativa logica di validazione dei dati. Il compito del ViewModel sarà poi quello, molto semplicemente, di ritrasmettere alla UI le informazioni sugli errori di validazione riscontrati. Nella View, invece, le uniche due cose che dovremo fare saranno la definizione di un ErrorTemplate e l’impostazione della proprietà ValidatesOnDataError per i campi data-bound.

 

Estendere la classe Customer con IDataErrorInfo

Abbiamo abbastanza libertà nel definire come i membri dell’interfaccia IDataErrorInfo debbano verificare la correttezza dei dati. Nel codice di esempio che segue a breve, decidiamo di verificare la correttezza dei campi CompanyName e Representative attraverso due metodi che verificano che i campi non siano vuoti, più un metodo che, a seconda del nome della proprietà, si occupa di invocare il metodo appropriato oltre a una proprietà booleana HasErrors che ci consentirà di determinare se l’istanza ha o meno errori. Ecco il codice della classe, con i commenti:

 

Imports System.ComponentModel

 

Public Class Customer

    Implements IDataErrorInfo

 

    Public Property CompanyName As String

    Public Property CustomerID As Integer

    Public Property Address As String

    Public Property Representative As String

 

    Public ReadOnly Property [Error] As String Implements System.ComponentModel.IDataErrorInfo.Error

        Get

            Return Nothing

        End Get

    End Property

 

    Default Public ReadOnly Property Item(ByVal columnName As String) _

                   As String Implements System.ComponentModel.IDataErrorInfo.Item

        Get

            Return Me.GetValidationError(columnName)

        End Get

    End Property

 

    'Ritorna True se l'istanza contiene errori

    Public ReadOnly Property HasErrors As Boolean

        Get

            For Each [property] As String In ValidatedProperties

                If GetValidationError([property]) IsNot Nothing Then

                    Return True

                End If

            Next [property]

 

            Return False

        End Get

    End Property

 

    'Un array di nomi di proprietà ammesse alla verifica

    'del loro contenuto

    Private Shared ReadOnly ValidatedProperties() As String =

                            {"CompanyName", "Representative"}

 

    'Determina la proprietà da verificare

    'e invoca il metodo appropriato

    Private Function GetValidationError(ByVal propertyName As String) As String

        If Array.IndexOf(ValidatedProperties, propertyName) < 0 Then

            Return Nothing

        End If

 

        Dim [error] As String = Nothing

 

        Select Case propertyName

            Case "CompanyName"

                [error] = Me.ValidateCompanyName

 

            Case "Representative"

                [error] = Me.ValidateRepresentative

 

            Case Else

                Debug.Fail("La proprietà specificata non è verificabile: " & propertyName)

        End Select

 

        Return [error]

    End Function

 

    'Valida CompanyName verificando che non sia vuota

    Private Function ValidateCompanyName() As String

        If String.IsNullOrEmpty(Me.CompanyName) Then

            Return "CompanyName cannot be empty"

        Else

            Return Nothing

        End If

    End Function

 

    'Valida Representative verificando che non sia vuota

    Private Function ValidateRepresentative() As String

        If String.IsNullOrEmpty(Me.Representative) Then

            Return "Representative cannot be empty"

        Else

            Return Nothing

        End If

    End Function

End Class

 

Ovviamente è possibile prevedere la validazione di tutte le proprietà e un’implementazione diversa dei metodi che eseguono la verifica anche a seconda del tipo di dato del membro verificato.

 

Validazione nel ViewModel

Come accennato precedentemente, nell’ambito della validazione il ViewModel si occupa semplicemente di riprendere le informazioni provenienti dal Model e di ritrasmetterle alla UI. Andiamo quindi nel file CustomerViewModel.vb, implementiamo IDataErrorInfo e scriviamo il seguente codice per i due membri richiesti:

 

    Public ReadOnly Property [Error] As String Implements System.ComponentModel.IDataErrorInfo.Error

        Get

            Return (TryCast(Me.Customer, IDataErrorInfo)).Error

        End Get

    End Property

 

    Default Public ReadOnly Property Item(ByVal columnName As String) As String Implements System.ComponentModel.IDataErrorInfo.Item

        Get

            Dim [error] As String = Nothing

 

            [error] = (TryCast(Me.Customer, IDataErrorInfo))(columnName)

            'Fa sì che il runtime verifichi nuovamente l’eseguibilità dei comandi

            CommandManager.InvalidateRequerySuggested()

 

            Return [error]

        End Get

    End Property

 

Il codice non è difficile. Le proprietà Error e Item leggono le corrispondenti provenienti dall’istanza di Customer e le restituiscono a loro volta. Tutto qua. C’è però un ulteriore passaggio da fare. Affinché i dati non vengano salvati quando ci sono errori, è necessario modificare il metodo CanSave affinché verifichi il contenuto di HasErrors e abiliti il pulsante di salvataggio solo quando non ci sono errori, in questo modo:

 

    Private Function CanSave(ByVal param As Customer) As Boolean

        If CType(Me.customersView.CurrentItem, Customer).HasErrors Then

            Return False

        Else

            Return True

        End If

    End Function

 

Validazione nella View

Nella View, ossia nella nostra finestra principale, dobbiamo fare due cose: prevedere un ErrorTemplate e dire ai controlli data-bound di utilizzarlo in caso di errori. Come normale in WPF, gli ErrorTemplate hanno un livello elevatissimo di personalizzazione a livello grafico. Generalmente tali template si mettono nelle risorse a livello di applicazione, quindi nel file Application.Xaml. Ecco il codice che realizza il risultato evidenziato nella figura a inizio pagina:

 

    <Application.Resources>

        <!--Template per validazione controlli

            Mostrerà un bordo rosso e un messaggio

            quando la validazione dell'ordine fallisce

        -->

        <Style TargetType="Control" x:Key="myErrorTemplate">

            <Setter Property="Validation.ErrorTemplate">

                <Setter.Value>

                    <ControlTemplate>

                        <DockPanel LastChildFill="True">

                            <TextBlock DockPanel.Dock="Right"

                        Foreground="Red"

                        FontSize="14pt"

                        FontWeight="ExtraBold">*

                            </TextBlock>

                            <Border BorderBrush="Red" BorderThickness="4">

                                <AdornedElementPlaceholder Name="myControl"/>

                            </Border>

                        </DockPanel>

                    </ControlTemplate>

                </Setter.Value>

            </Setter>

            <Style.Triggers>

                <Trigger Property="Validation.HasError" Value="True">

                    <Setter Property="ToolTip"

                Value="{Binding RelativeSource={x:Static RelativeSource.Self},

                Path=(Validation.Errors)[0].ErrorContent}"/>

                </Trigger>

            </Style.Triggers>

        </Style>

        <!-- Definisce uno stile per le TextBox, che sfrutta l'ErrorTemplate -->

        <Style TargetType="TextBox" BasedOn="{StaticResource myErrorTemplate}">

            <Setter Property="FontSize" Value="14"/>

        </Style>

    </Application.Resources>

 

Come potete osservare, la proprietà Validation.ErrorTemplate definisce come, a livello di layout, verrà presentato il messaggio di errore, in questo caso con un adornement rosso intorno al controllo e un asterisco. Questo è il punto dove potete personalizzare al massimo la modalità di visualizzazione dell’errore. Per quanto riguarda i trigger, sostanzialmente quando la proprietà Validation.HasError è True viene associato il contenuto dell’errore, riferito all’istanza correntemente in binding (RelativeSource.Self), a una tooltip. Infine, viene definito uno stile per le TextBox di modo che tutti i controlli di questo tipo possano utilizzare l’ErrorTemplate definito.

L’ultimo passaggio è quello di impostare la proprietà ValidatesOnDataErrors su True per le TextBox associate alle proprietà che sappiamo saranno validate:

 

            <TextBox Grid.Column="1" Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="3" Name="CompanyNameTextBox" Text="{Binding Path=CompanyName,

                Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true, ValidatesOnDataErrors=True}" VerticalAlignment="Center" Width="120" />

           

            <TextBox Grid.Column="1" Grid.Row="3" Height="23" HorizontalAlignment="Left" Margin="3" Name="RepresentativeTextBox"

                     Text="{Binding Path=Representative, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true, ValidatesOnDataErrors=True}" VerticalAlignment="Center" Width="120" />

 

A questo punto possiamo avviare l’applicazione. Nel momento in cui faremo clic su Insert, il nuovo Customer avrà due proprietà vuote per cui verranno mostrati i bordi rossi a significare l’errore. Tali bordi spariranno nel momento in cui riempiremo i campi con dei dati validi, al tempo stesso il pulsante di salvataggio verrà riabilitato.

 

Download del codice e cosa ci aspetta

Il codice completo del progetto così come mostrato oggi è scaricabile da questo indirizzo dell’area Download di VB T&T. Per quanto riguarda l’utilizzo di M-V-VM in uno scenario come questo, ossia dati provenienti da XML e oggetti business personalizzati, mi fermo qui. Vi rimando a questo post di Mauro Servienti per uno scenario master-details, mentre dal prossimo post mi occuperò di qualcosa di più attuale e interessante: utilizzare il pattern nei confronti di ADO.NET Entity Framework dove mi occuperò anche di master-details, ma dove rivedremo commands, navigazione, validazione in un contesto sicuramente più pratico.

 

Alessandro

Print | posted on domenica 20 giugno 2010 16:16 | Filed Under [ Visual Basic Windows Presentation Foundation Visual Studio 2010 ]

Powered by:
Powered By Subtext Powered By ASP.NET