Alessandro Del Sole's Blog

/* A programming space about Microsoft® .NET® */
posts - 159, comments - 0, trackbacks - 0

My Links

News

Your host

This is me! This space is about Microsoft® .NET® and Microsoft® Visual Basic development. Enjoy! :-)

These postings are provided 'AS IS' for entertainment purposes only with absolutely no warranty expressed or implied and confer no rights.

Microsoft MVP

My MVP Profile

I'm a VB!

Watch my interview in Seattle

My new book on VB 2015!

Pre-order VB 2015 Unleashed Pre-order my new book "Visual Basic 2015 Unleashed". Click for more info!

My new book on LightSwitch!

Visual Studio LightSwitch Unleashed My book "Visual Studio LightSwitch Unleashed" is available. Click the cover!

Your visits

Follow me on Twitter!

CodePlex download Download my open-source projects from CodePlex!

Article Categories

Archives

Post Categories

.NET Framework

Blogroll

Help Authoring

Microsoft & MSDN

Setup & Deployment

Visual Basic 2005/2008/2010

WPF: Introducing the Model-View-ViewModel pattern for Visual Basic 2010 developers - part 4

In my previous post related to the introductory series about the Model-View-ViewModel pattern with Visual Basic 2010, I discussed how to implement the capability of browsing data exposing an ICollectionView from the ViewModel; also, we saw how to provide a generic implementation of the RelayCommand class.

 

The purpose of this post is extending the previous application adding data validation features, taking advantage of WPF's ErrorTemplate and the IDataErrorInfo interface. It's worth mentioning that this is just one possible solution to the data validation problem, but you are free to provide your own. The approach that I'm going to show offers a cool advantage: via an ErrorTemplate we can simply send information from IDataErrorInfo to the UI, in a way that is very simple and user-friendly. At the bottom of this page you will find the link to download the full companion code for this post but you might consider downloading the previous one for working with the features discussed today. Our today's goal is reaching the following objective:

 

Validation logic

In our very particular scenario, the data validation logic must affect every single instance of the Customer class. Such a class will thus implement IDataErrorInfo and the data validation logic. Then the ViewModel will simply re-transmit to the UI all information on validation errors. Finally, in the View we'll just define an ErrorTemplate and set the ValidatesOnDataError properties for data-bound fields.

 

Extending the Customer class by implementing IDataErrorInfo

We are quite free to decide how IDataErrorInfo members must check for data validity. Let's imagine we want to check that the CompanyName and Representative fields are valid via two methods that will ensure that both fields are not empty plus another method that will invoke the appropriate method according to the property name. Finally we can add a boolean property named HasErrors that allows discovering if the instance has errors or not. This is the code for the class, with comments:

 

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

 

    'If the instance has errors, then returns True

    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

 

    'An array of property names that are allowed to be checked

    Private Shared ReadOnly ValidatedProperties() As String =

                            {"CompanyName", "Representative"}

 

    'According to the property name, invokes the appropriate method

    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("The specified property cannot be checked: " & propertyName)

        End Select

 

        Return [error]

    End Function

 

    'Validates CompanyName ensuring it's not empty

    Private Function ValidateCompanyName() As String

        If String.IsNullOrEmpty(Me.CompanyName) Then

            Return "CompanyName cannot be empty"

        Else

            Return Nothing

        End If

    End Function

 

    'Validates Representative ensuring it's not empty

    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

 

You can certainly implement validation for all properties and/or provide a different implementation for methods performing checks.

 

Validation and the ViewModel

As I briefly mentioned before, when talking about validation the ViewModel is simply responsible of picking up information coming from the Model and of re-transmitting such information to the UI. Let's go back to the CustomerViewModel.vb code file, let's implement IDataErrorInfo and then let's write the following code that implements the required members:

 

    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)

            'By invoking this the runtime will check again if commands can be executed

            CommandManager.InvalidateRequerySuggested()

 

            Return [error]

        End Get

    End Property

 

The code is quite easy. Both Error and Item properties read the same-named properties from the Customer instance and return their content. That's it. To be honest there is another step to perform. In order to ensure that data cannot be saved if they contain any error, we need to edit the CanSave method so that it checks the content of HasErrors enabling the Save button only if there are no errors, like this:

 

    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

 

Validation in the View

In the View, which is our main window, we need two things: implementing an ErrorTemplate and assigning such a template to the data-bound controls so that they will display information according to the ErrorTemplate in case of errors. Like any other template in WPF, an ErrorTemplate offers a very high customization level on the design side. Typically you place such templates inside application-level resources, therefore in the Application.Xaml file. This is the code that provides the result shown in the figure at the top of the article:

 

    <Application.Resources>

        <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>

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

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

        </Style>

    </Application.Resources>

 

As you can see, the Validation.ErrorTemplate property defines how the error message will be presented (in this case via a red adorner around the control together with an asterisk). This is the point where you can maximize the customization level for presenting the error messages. About triggers, basically when the Validation.HasError property is True the error's content is assigned to a tooltip, referring to the currently bound instance (RelativeSource.Self). Finally the code defines a style for TextBoxes so that all controls of this type will use the ErrorTemplate.

The last step is setting the ValidatesOnDataErrors property as True for each TextBox bound to properties that will be validated:

 

            <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" />

 

At this point we can run the application. When you click Insert, the new customer will show two empty properties and this will cause the UI to show two red borders representing the error. Such borders will disappear when you fill fields with valid data. Moreover the Save button will be enabled.

 

Source code download and what's next

The full source code for today's sample application is available from this address. Starting from next post I will no longer introduce M-V-VM in custom objects/XML data scenarios. I will instead focus on something more interesting: using the MVVM pattern against ADO.NET Entity Framework and I will discuss master-details relationship, also covering commands, navigation and validation within a more practical context.

 

Alessandro

Print | posted on lunedì 28 giugno 2010 21:17 | Filed Under [ Visual Studio 2010 Visual Basic Windows Presentation Foundation ]

Feedback

No comments posted yet.

Post Comment

Title  
Name  
Email
Url
Comment   
Please add 4 and 3 and type the answer here:

Powered by:
Powered By Subtext Powered By ASP.NET