Alessandro Del Sole's Blog

/* A programming space about Microsoft® .NET® */

  Home :: Contact :: Syndication  :: Login
  80 Posts :: 10 Stories :: 388 Comments :: 2327 Trackbacks

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 2010

Order my book about VB 2010 on Amazon My book "Visual Basic 2010 Unleashed" is available. Click the cover!

Your visits

Windows Live Alerts

Vsi Builder 2008

My tool for VS 2005/2008 Download Vsi Builder, my dev tool for Visual Studio 2005/2008!

Follow me on Twitter!

Messenger me!


Grab this badge here!

CyberInstaller Beta Tester

Download CIS 2008!!

CodePlex download Download my open-source projects from CodePlex!

Search the blog



Article Categories

Archives

Post Categories

.NET Framework

Blogroll

Help Authoring

Microsoft & MSDN

Setup & Deployment

Visual Basic 2005/2008/2010

Wednesday, August 25, 2010 #

Probably many of you already know that recently Microsoft announced the first beta of a new product in the Visual Studio family, called Visual Studio LightSwitch that is publicly available since August 23rd. In this blog post I'm going to make an overview of LightSwitch with a few words and some pictures.

What Visual Studio LightSwitch Is (and my personal thought for VB 6 developers)

Visual Studio LightSwitch is an integrated development environment for rapidly developing Line Of Business Applications. Rapid because the pipeline is: "I create my data -> I define my screens -> LightSwitch prepares the infrastructure -> I just press F5 and get a full application even with no code". Maybe this is familiar to you if you ever used Access/VBA to develop data-centric apps (although LightSwitch is absolutely not an Access replacement) and this is an important opportunity for VB 6 developers to migrate to the most modern .NET technologies fast.

Who should use it

VisualStudio LightSwitch is typically for:

  • small companies that do not need to develop deeply complex applications
  • developers who are not expert or who also are, but that need to be productive quickly

What LightSwitch uses

LightSwitch applications take advantage, behind the scenes, modern technologies and advanced patterns although the developer is not required to know this. It uses: Silverlight 4, WCF RIA Services, SQL Server Express, SharePoint 2010, SQL Azure, MVVM pattern, N-Tier architecture.

How it works

Start Splash screen:

The Start Page and the environment look similar to Visual Studio 2010:

It's easy to choose what kind of app you want to create, since you just have 2 choices: VB or C#:

First you decide how to create a data source, starting from scratch or connecting to an existing data source (such as SQL Server, SharePoint 2010, WCF RIA Services):

Imagine you start from scratch by creating a new table. You can notice new business data types specific for email addresses and phone numbers. These are important because the offer built-in data validation features (the Italian sentence drawn on next picture says that there are 2 new types):

Once you have your data, you can create screens. There is a number of default templates. First, you select a screen template in order to show a list of elements:

Next, you need a screen for creating and adding new elements:

The designer will show how the screen is composed, listing command controls and data-bound controls:

 

You just press F5 and get your application working:

This is nothing but a Silverlight 4 application that is running on the desktop as an "Out-of-browser" application, storing data into a SQL Server database that LightSwitch generated for the application. Also notice:

  1. Ribbon control containing main commands
  2. Task tab containing navigation commands between screens
  3. automatic data validation, without writing a single line of code

Then we can show the list of items (the Italian sentences drawn on the picture say that exporting to Excel and the search field are provided by LightSwitch):

The screen includes by default:

  1. search capabilities
  2. exporting data to Microsoft Excel
  3. data paging at the bottom of the screen

If you want to add a new table to be associated to the first one, adding a relationship is also very easy:

 

By default LightSwitch apps are 2-tier applications but you can move to 3-tier desktop (passing through IIS) or 3-tier in-browser:

 

When you use the in-browser option, the Silverlight app runs inside your default Web browser:

Visual Studio LightSwitch is absolutely not only what I described here. You can in fact write also complex code, such as LINQ queries, setting custom validations, customizing the user interface at runtime, accessing SharePoint, creating enumerations inside tables, interacting with Azure. In some next post I will cover some more specific topics related to this new exciting tool. For now be sure to periodically check out the LightSwitch Developer Center, where you can find a lot of learning materials.

Alessandro

posted @ 3:20 PM | Feedback (553)

Friday, August 13, 2010 #

Following the last part of the blog post introductory series on MVVM in WPF 4 applications with Visual Basic 2010, I think that you can find useful the full list of 10 posts for easier reading. Here they are:

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

Part 1 (introduction)

Part 2 (command logic)

Part 3 (details view + generic RelayCommand)

Part 4 (data validation)

(From part 5 on, MVVM is against the ADO.NET Entity Framework)

Part 5 (Entity Data Model creation + data validation)

Part 6 (Message Broker and commanding)

Part 7 (service layer)

Part 8 (creating ViewModels)

Part 9 (refactoring + unit testing)

Part 10 (Views definition, UI and code download)

I'm planning some further blog posts in which I will use the sample application created in this series to show how you can replace the data store without changing the ViewModel and how you add data at design-time. I would be happy if you could leave your feedback on the series.

Alessandro

posted @ 4:01 PM | Feedback (4)

This is definitely the last blog post of the introductory series about the Model-View-ViewModel in WPF with Visual Basic 2010, particularly against the ADO.NET Entity Framework. I will explain how to build the user interface and how to bind it to ViewModels. Although this is the last blog post of the series, this does not mean that I will no longer discuss MVVM with VB 2010. On the contrary this will be an important topic for this blog, but for the sake of clarity I will distinguish topics in different blog posts and today we will complete the sample application. You can find the download link at the bottom of the page. I will go quite fast now, since I have to show you a lot of code and most of the concepts have been explained previously. Nothing difficult, just things you already learned through the series.

The ErrorTemplate

As you may remember, in part 5 we implemented data validation rules inside the Order class, also implementing the IDataErrorInfo interface. In order to make the UI show correctly data validation errors, we need an ErrorTemplate for controls of type TextBox (which will be used in the main View). The following code has been already described in part 4 and it has to be added to the Application.xaml file:

 

<Application x:Class="Application"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="Views\MainWindow.xaml">
    <Application.Resources>
        <Style x:Key="ButtonStyle" TargetType="Button">
            <Setter Property="Width" Value="80"/>
            <Setter Property="Height" Value="40"/>
            <Setter Property="Margin" Value="5"/>
        </Style>
 

        <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}"/>
 
        <Style TargetType="Label">
            <Setter Property="Foreground" Value="White"/>
        </Style>
    </Application.Resources>
</Application>

The template will display a red border around TextBox controls whose content is invalid, until the user fixes the data.

The main View

According to the figure shown in Part 5, the user interface is composed of the following ingredients:

  • a main Grid nesting:
    • a ListBox for the customers list 
    • a Grid containing controls for displaying information from a single order information
    • a StackPanel containing action buttons

With that said the first thing to do is defining the main Window as follows, also specifying references to the application's assembly and adding a graceful gradient background: 

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Northwind Traders Orders Management" Height="508" Width="876" mc:Ignorable="d" 
       
 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
       
 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
   
 xmlns:my="clr-namespace:adsMVVM_EntityFramework_Complete"
    WindowStartupLocation="CenterScreen">
    <Window.Background>
        <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
            <GradientStop Color="Black" Offset="0" />
            <GradientStop Color="White" Offset="1" />
        </LinearGradientBrush>
    </Window.Background>

Of course the name of the application's assembly may vary depending on the name you assigned to the project. Now let's divide the main Grid into three columns, also assigning an identifier for code-behind interaction: 

    <Grid Name="MainGrid" ShowGridLines="False" >
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="200" />
            <ColumnDefinition />
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

Now let's declare the ListBox for the customers list. Pay attention to the data-binding:

        <ListBox Name="ListBox1" Grid.Column="0"  ItemsSource="{Binding Path=CustomerViewSource.View}" DisplayMemberPath="CompanyName"
                 SelectedItem="{Binding Path=Selection, Mode=TwoWay}" />

The control is data-bound (ItemsSource) to the CustomersViewSource.View property from the object that will be assigned to the main Grid's DataContext (that is, the ViewModel). This time controls are bound to the view objects from CollectionViewSources/ListCollectionViews instead of an ObservableCollection. DisplayMemberPath allows specifying the element of the collection that will be displayed. Also notice how the selected item in the ListBox is data-bound to the Selection property from the ViewModel, in both reading and writing. Now it's time to declare the central Grid which contains TextBox and DatePicker controls (the latter are useful for working with dates) in order to represent a single order. Also notice how each control is data-bound to the related property exposed by the data source (which is an instance of the Order class) but most of all how the Grid is populated via the CustomerOrdersView object which exposed by the ViewModel that will be associated to the View in a few moments:

        <Grid Grid.Column="1" Name="Grid1" DataContext="{Binding Path=CustomerOrdersView}" >
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Label Content="Order ID:" Grid.Column="0" Grid.Row="0" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
            <TextBox Grid.Column="1" Grid.Row="0" Height="23" HorizontalAlignment="Left" Margin="3" Name="OrderIDTextBox" 
                     Text="{Binding Path=OrderID, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
            <Label Content="Customer ID:" Grid.Column="0" Grid.Row="1" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
            <TextBox Grid.Column="1" Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="3" Name="CustomerIDTextBox" 
                     Text="{Binding Path=CustomerID, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
            <Label Content="Employee ID:" Grid.Column="0" Grid.Row="2" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
            <TextBox Grid.Column="1" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="3" Name="EmployeeIDTextBox" 
                     Text="{Binding Path=EmployeeID, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
            <Label Content="Order Date:" Grid.Column="0" Grid.Row="3" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
            <DatePicker Grid.Column="1" Grid.Row="3" Height="25" HorizontalAlignment="Left" Margin="3" Name="OrderDateDatePicker" 
                        SelectedDate="{Binding Path=OrderDate, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="115" />
            <Label Content="Required Date:" Grid.Column="0" Grid.Row="4" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
            <DatePicker Grid.Column="1" Grid.Row="4" Height="25" HorizontalAlignment="Left" Margin="3" Name="RequiredDateDatePicker" 
                        SelectedDate="{Binding Path=RequiredDate, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="115" />
            <Label Content="Shipped Date:" Grid.Column="0" Grid.Row="5" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
            <DatePicker Grid.Column="1" Grid.Row="5" Height="25" HorizontalAlignment="Left" Margin="3" Name="ShippedDateDatePicker" 
                        SelectedDate="{Binding Path=ShippedDate, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="115" />
            <Label Content="Ship Via:" Grid.Column="0" Grid.Row="6" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
            <TextBox Grid.Column="1" Grid.Row="6" Height="23" HorizontalAlignment="Left" Margin="3" Name="ShipViaTextBox" 
                     Text="{Binding Path=ShipVia, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
            <Label Content="Freight:" Grid.Column="0" Grid.Row="7" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
            <TextBox Grid.Column="1" Grid.Row="7" Height="23" HorizontalAlignment="Left" Margin="3" Name="FreightTextBox" 
                     Text="{Binding Path=Freight, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
            <Label Content="Ship Name:" Grid.Column="0" Grid.Row="8" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
            <TextBox Grid.Column="1" Grid.Row="8" Height="23" HorizontalAlignment="Left" Margin="3" Name="ShipNameTextBox" 
                     Text="{Binding Path=ShipName, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
            <Label Content="Ship Address:" Grid.Column="0" Grid.Row="9" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
            <TextBox Grid.Column="1" Grid.Row="9" Height="23" HorizontalAlignment="Left" Margin="3" Name="ShipAddressTextBox" 
                     Text="{Binding Path=ShipAddress, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
            <Label Content="Ship City:" Grid.Column="0" Grid.Row="10" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
            <TextBox Grid.Column="1" Grid.Row="10" Height="23" HorizontalAlignment="Left" Margin="3" Name="ShipCityTextBox" 
                     Text="{Binding Path=ShipCity, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
            <Label Content="Ship Region:" Grid.Column="0" Grid.Row="11" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
            <TextBox Grid.Column="1" Grid.Row="11" Height="23" HorizontalAlignment="Left" Margin="3" Name="ShipRegionTextBox" 
                     Text="{Binding Path=ShipRegion, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
            <Label Content="Ship Postal Code:" Grid.Column="0" Grid.Row="12" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
            <TextBox Grid.Column="1" Grid.Row="12" Height="23" HorizontalAlignment="Left" Margin="3" Name="ShipPostalCodeTextBox" 
                     Text="{Binding Path=ShipPostalCode, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
            <Label Content="Ship Country:" Grid.Column="0" Grid.Row="13" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
            <TextBox Grid.Column="1" Grid.Row="13" Height="23" HorizontalAlignment="Left" Margin="3" Name="ShipCountryTextBox" 
                     Text="{Binding Path=ShipCountry, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true, ValidatesOnDataErrors=True}" VerticalAlignment="Center" Width="120" />
        </Grid>

Remember to set the ValidatesOnDataErrors = True markup inside the TextBox's binding, with regard to the ShipCountry property, which is the one that has validation rules in the model. In order to complete the user interface of the main window we just need to define some buttons and command bindings like this:

        <StackPanel Name="Stack2" Orientation="Vertical" Grid.Column="2" >
            <Button Content="Save changes" 
                   
 Command="{Binding SaveCommand}"
                    Name="SaveButton" Style="{StaticResource ButtonStyle}" />

           <Button Content="Next"
  
                   
 Name="NextButton"
 
                   
 Command="{Binding NextCommand}"
 
                   
 Style="{StaticResource ButtonStyle}" />
            <Button Content="Previous" Command="{Binding PreviousCommand}"
                    Name="PrevButton" Style="{StaticResource ButtonStyle}" />
            <Button Content="Delete order"
                    Name="DeleteButton"
  
                   
 Command="{Binding DeleteCommand}"
 
                   
 Style="{StaticResource ButtonStyle}" />
            <Button Content="New order"
 
                   
 Command="{Binding Path=InsertCommand}"
                    Name="NewButton"
 
                   
 Style="{StaticResource ButtonStyle}" />
            <Button Content="View details"
                    Command="{Binding ViewDetailsCommand}"
                    Name="Button1"
  
                   
 Style="{StaticResource ButtonStyle}"  />
        </StackPanel>
    </Grid>
</Window>

You should easily remember how in MVVM you do not handle the Click event while you associate the Command property to a property of type ICommand exposed by the ViewModel. Now let's switch to the Visual Basic code so that the UI will become ready to perform some actions.

No code? No party!
In the first blog posts of this series we saw how the main Window, which is the first View, contains only code that creates an instance of the ViewModel, assigning such an instance to the Window's DataContext. Now this is the principle: each View can contain also other code, but such a code must be only UI code so it cannot access data or other stuff. In our particular scenario, the Window's code-behind will contain code that creates an instance of the ViewModel but also it will contain code that registers a message that will allow displaying the Order Details window, once it is sent. This is accomplished via the
Register method from the Messenger class. This is finally the code-behind:

Class MainWindow
 
    
Private Sub MainWindow_Loaded(ByVal sender As ObjectByVal e As System.Windows.RoutedEventArgsHandles Me.Loaded
        
Dim startupViewModel As New OrdersViewModel
        Me.MainGrid.DataContext = startupViewModel
 
        
Application.Msn.Register(Application.VIEW_DETAILS_EXECUTE, Sub()
                                                                       
Try
                                                                           'Get the instance of the current order and send the ID to the constructor
                                                                           'of the OrderDetailsView window
                                                                           Dim odv As New OrderDetailsView(CType(startupViewModel.CustomerOrdersViewSource.View.CurrentItem, Order).OrderID)
                                                                           odv.ShowDialog()
                                                                           odv = 
Nothing
                                                                       Catch ex As Exception
                                                                           MessageBox.Show(ex.Message)
                                                                       
End Try
                                                                   End Sub)
    
End Sub
End Class

The following are some important considerations:

  1. The instance of the ViewModel is not assigned to the Window's DataContext but to the Grid's DataContext. This is a particular case, because for hierarchical reasons the various controls are populated starting from the first panel.
  2. The Register method receives, as arguments, the message to be registered and the action to take once the message is sent by the ViewModel. In this case the message has been defined as a constant at the application level, while the action is defined via a statement lambda instead of pointing to a stand-alone delegate via AddressOf (but if you use this older approach, it is correct the same).
  3. With regard to the second argument of Register, the lambda expression retrieves the instance of the selected Order passing its identifier to the constructor of a window named OrderDetailsView, that will be created shortly. Then the code runs this new window. Since this is UI-related code, it can be written at the View level.
  4. About messages exchange: the View registers the message and the application will listen for such a message, then the ViewModel emits the message when the appropriate command is invoked, finally the View intercepts the message and shows the second window.

Once the first View is completed, we can move forward to the second one.

Building the Order Details view

In our sample application, Order Details will be displayed inside a DataGrid in a new Window; for the sake of simplicity, I will not implement any other control but you are free to do it on your own once you understand the logic. Now add a new Window to the Views project subfolder, calling it OrderDetailsView.xaml. When ready, you can also use the new VS 2010 drag'n'drop data-binding feature to make your life easier, by dragging the Order_Details entity from the Data Sources window onto the designer. This will also enable some design-time features for working with data, by the way you need to write the following XAML (which also contains the auto-generated code from Visual Studio after the drag'n'drop):

<Window x:Class="OrderDetailsView"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:my="clr-namespace:adsMVVM_EntityFramework_Complete"

    xmlns:dal="clr-namespace:DAL;assembly=DAL" ShowInTaskbar="True" WindowStartupLocation="CenterScreen"

    Title="OrderDetailsView" Height="300" Width="370" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:my1="clr-namespace:DAL;assembly=DAL">

    <Window.Resources>

        <CollectionViewSource x:Key="Order_DetailViewSource" Source="{Binding Path=Order_Details}" d:DesignSource="{d:DesignInstance dal:Order_Detail, CreateList=True}" />

    </Window.Resources>

    <Grid DataContext="{StaticResource Order_DetailViewSource}">

        <DataGrid AutoGenerateColumns="False" EnableRowVirtualization="True" ItemsSource="{Binding}" Name="Order_DetailsDataGrid"

                  RowDetailsVisibilityMode="VisibleWhenSelected">

            <DataGrid.Columns>

                <DataGridTextColumn x:Name="ProductIDColumn" Binding="{Binding Path=ProductID}" Header="Product ID" Width="SizeToHeader" />

                <DataGridTextColumn x:Name="UnitPriceColumn" Binding="{Binding Path=UnitPrice, StringFormat=c}" Header="Unit Price" Width="SizeToHeader" />

                <DataGridTextColumn x:Name="QuantityColumn" Binding="{Binding Path=Quantity}" Header="Quantity" Width="SizeToHeader" />

                <DataGridTextColumn x:Name="DiscountColumn" Binding="{Binding Path=Discount, StringFormat=p}" Header="Discount" Width="SizeToHeader" />

            </DataGrid.Columns>

        </DataGrid>

    </Grid>

</Window>

Notice how you can use a CollectionViewSource to set the Grid's DataContext at design-time. Such a data source points to the Order_Details property exposed by the ViewModel. At the code-behind level, all is similar to the main View. In this case we need an explicit constructor, because we need the OrderID as a requisite:

Public Class OrderDetailsView
    Private _orderID As Integer
    Private WithEvents OrderDetailsVM As OrderDetailsViewModel

    
Sub New(ByVal OrderID As Integer
)
        InitializeComponent()
        
' TODO: Complete member initialization 
        Me
._orderID = OrderID
    
End Sub
 
    
Private Sub Window_Loaded(ByVal sender As System.ObjectByVal e As System.Windows.RoutedEventArgsHandles MyBase
.Loaded
        
Me.Title = "Order details for order nr. " & Me
._orderID.ToString
        
Me.OrderDetailsVM = New OrderDetailsViewModel
(_orderID)
        
Me.DataContext = Me
.OrderDetailsVM
        
Application.Msn.Register(Application.VIEW_DETAILS_CLOSE, Sub
()
                                                                     
Me
.Close()
                                                                 
End Sub
)
    
End Sub
End Class

Basically we are registering the message for closing the window but we will not use it effectively. You can remove it, if you wish, or you can add a button bound to the CloseCommand exposed by the ViewModel.

C'mon, let me press F5!
At the end of the work, we can finally run the application. You will get a result similar to the following figure, which also shows the Order Details window running:

This is instead what we get in case of a data validation error:

Conclusions and Download

As I said several times, nothing is perfect and the possibility of improving the code is something that must be considered. By the way, my hope is that this blog post series on MVVM from a Visual Basic 2010 perspective (especially the parts dedicated to the Entity Framework) has been useful as a pure introduction. I will post shortly the full series summary for your convenience. Finally, you can download the full source code from this address. The prerequisites is that you need the Northwind database available on your SQL Server instance (you can change the connection string in order to point to a local database).

Stay tuned for other blog posts on MVVM with VB 2010!

Alessandro

posted @ 3:52 PM | Feedback (26)

Thursday, August 12, 2010 #

So far we wrote a lot of code, in this blog post series related to MVVM in WPF with Visual Basic 2010. Of course there are a lot of things to improve before heading to the last part. This will also allow us discovering a great benefit from using MVVM, which is writing unit tests against the ViewModel. Let's start by refactoring some code.

Refactoring time!

Refactoring the code is something that is mainly about both ViewModels. OrdersViewModel is the one requiring more attention. The first thing we want to improve is how the code uses the RelayCommand class: since we also wrote a generic implementation it's a good idea to use this one instead of the non generic class. With that said let's move to the OrdersViewModel class placing the cursor in the code region that exposes properties of type ICommand. As an example let's start by the first one, then you will have to repeat the same work against all the other properties. Let's re-write them using RelayCommand(Of Order):

    Public ReadOnly Property DeleteCommand() As ICommand
        Get
            If _cmdDeleteCommand Is Nothing Then
                _cmdDeleteCommand = New RelayCommand(Of Order)(AddressOf DeleteExecute, AddressOf CanDeleteExecute)
            End If
            Return _cmdDeleteCommand
        End Get
    End Property

Obviously we must also change the signature of methods pointed via AddressOf, so that they receive a typed parameter instead of an Object. Here they are:

    Private Function CanDeleteExecute(ByVal param As OrderAs Boolean
        If Me.CustomerOrdersView Is Nothing Then Return False
        Return Me.CustomerOrdersView.CurrentPosition > -1
    End Function
      Private Sub DeleteExecute(ByVal param As Order)
        Me.orderAccess.Delete(Me.CustomerOrdersView)
    End Sub

Of course you need to perform the same action for all other methods. Next, let's move to the _customersView_CurrentChanged event handler. Here we can extract a new private method from the event handler's body so that we can better organize the code and providing a useful approach for unit tests (if you like):

    Private Sub _customersView_CurrentChanged(ByVal sender As ObjectByVal e As System.EventArgsHandles _customersView.CurrentChanged
        Me.UpdateOrdersViewAfterCustomerChanged(CType(Me.CustomersView.CurrentItem, Customer))
    End Sub

    Private Function UpdateOrdersViewAfterCustomerChanged(ByVal currentCustomer As CustomerAs Boolean
        Try
            Me.Orders = orderAccess.GetAllOrders(currentCustomer.CustomerID)
            Me.CustomerOrdersViewSource.Source = Me.Orders
            Me.CustomerOrdersView = CType(Me.CustomerOrdersViewSource.View, ListCollectionView)
            Return True
        Catch ex As Exception
            Return False
        End Try
    End Function

Again with regard to unit test creation, instead of invoking the CustomerDataService.GetAllCustomers from within the constructor, such an invocation can be made from a new method inside the ViewModel that can be also tested and its result will be assigned to the appropriate object. Thus, first let's move at class level the following declaration:

Private dataAccess As ICustomerDataService

Once done this, let's write the following method which returns the result of the call:

    Private Function GetAllCustomers() As IQueryable(Of Customer)
       Return Me.dataAccess.GetAllCustomers
    End Function

Then inside the constructor we need to replace the following code:

        Dim dataAccess = GetService(Of ICustomerDataService)()
        Me.orderAccess = GetService(Of IOrderDataService)()
        For Each element In dataAccess.GetAllCustomers
            Me._customers.Add(element)
        Next

with this one taking advantage of the new method:

        Me.dataAccess = GetService(Of ICustomerDataService)()
        Me.orderAccess = GetService(Of IOrderDataService)()
        For Each element In Me.GetAllCustomers
            Me._customers.Add(element)
        Next

Don't be afraid if you are missing something, in next blog post you will be able to download the full source code. About the OrderDetailsViewModel, we need to apply the usage of RelayCommand(Of Order_Detail). I leave this to you as an exercise, since they are the same steps shown at the beginning of this paragraph. 

Writing and running unit tests

As you probably know, unit tests allow developers to test code blocks in a way that is abstracted from the application context. This means that a unit test checks if a code block works without running the application, in a fully separated context via specific tools from Visual Studio 2010. Writing unit tests is a best practice and the MVVM pattern provides the best support. In fact, how would you write unit tests against code that is associated to Views? Maybe you can do it, but this is in the UI context and is not correct. Now think of the  GetAllCustomers method that we implemented before. It resides in the ViewModel, so it is separated from the UI but it can access data. So it is possible to test if it works via a unit tests checking if it actually loads data, without interfering with the UI. Even if this sample is very simple, it will let you understand this benefit. With that said, go to the OrdersViewModel class and right click the GetAllCustomers method, then select Create Unit Test. Now you will get the dialog represented in the following figure, where you can select additional methods to test and specify a test project name:

At this point Visual Studio 2010 generates the project test, and the first thing to do is copying the App.config file from the primary project to the test project since it stores data connection information. Next, in the OrdersViewModelTest.vb code file let's search for the GetAllCustomersTest method. Our goal is testing if GetAllCustomers actually loads data from the database, thus returning a non-null value. Because of this the test method can be re-written as follows:

    <TestMethod(), _
     DeploymentItem("adsMVVM_EntityFramework_Complete.exe")> _
    Public Sub GetAllCustomersTest()
        Dim target As OrdersViewModel_Accessor = New OrdersViewModel_Accessor() ' TODO: Initialize to an appropriate value
        Dim actual As IQueryable(Of Customer)
        actual = target.GetAllCustomers
        Assert.IsNotNull(actual)
    End Sub

Now, using the appropriate Visual Studio tooling (or simply right-clicking inside the code and then Run Tests) let's run the test. As you can see, the unit test passes:

This is because the GetAllCustomers method actually loaded data and thus returns something not null, adhering to Assert requisites. Now you should understand why having a ViewModel is also useful: you can test code, even when you don't have a UI.

End of the story

In next post we will finally complete our work, preparing Views that is the UI, seeing also some messages exchange, a lot of data-binding and how View is not "no code" but it is "UI code".

Alessandro

posted @ 2:42 PM | Feedback (3)

Tuesday, August 10, 2010 #

Let's go ahead with our discussion about the MVVM pattern in WPF applications with VB 2010. Last time I explained how to implement a service layer; in this post I will focus on ViewModels. Particularly I will show where to place an instance of the Messenger class and then I will show all the required ViewModels. Because of this it will be a quite long work, especially in terms of lines of code since concepts have all been explained in this previous post.

Send me a message!

In all tutorials I found (and studied) declaring an instance of the Messenger class is done at the application level under the form of a read-only property. Methods offered by such a class receive arguments of type string, representing the messages to exchange with colleague objects and that will be intercepted later. Instead of writing strings every time, you can define constants storing messages. So the Application class also becomes the place where you define such constants. With regard to this, we are interested in intercepting two messages: one for opening the Order Details window and one for closing the window itself. With that said this is the code required in the Application.xaml.vb file:

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

Later in this post I will show an example of using the Messenger class but for now let's go ahead with ViewModels.

Each View owns a ViewModel 

Let's start by this statement: each View (meaning each Window or user control whose job is presenting data) talks to a specific ViewModel. In our application we have two windows: the main window and the window for showing order details. So we need to implement two ViewModels. Like we did previously, we can implement a class name ViewModelBase which will send (via inheritance) some members that are common for all ViewModels. At this point add a new code file to the ViewModels folder, naming it ViewModelBase.vb. I will now show an extended version of the class, adding some members for property validation. Although in the sample application I will not use any special features, these can be useful in the future. Moreover, I'm going to introduce the ServiceLocator and GetService methods which return respectively the instance of the ServiceLocator class (which switches service requests) and the instance of the specified service class. Both will be used later in the derived ViewModels. Now the class looks like this:

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

Nothing difficult but more has to come in a few moments.

A ViewModel for the Customer/Orders relationship

In the application's main window we want to show a list of customers and when the user selects a customer, the application will show the orders related to the customer and the user will be able of adding, removing and browsing data. So we need to create a ViewModel in order to expose to the View the necessary data over than the appropriate members that will perform the required operations. Also the ViewModel will implement IDataErrorInfo (I described such a technique here) so that it will send data validation errors to the View, which are raised by the Model. Now the point is this: generally we expose data through properties of type ObservableCollection; this is correct because such a collection provides full support to data-binding in WPF. By the way, we need methods which allow browsing and editing data. To accomplish this we can use objects of type ICollectionView and we can expose to Views some objects of type ListCollectionView. This particular kind of object in fact offers advanced functionalities for working against data and supports data-binding. With that said we basically need to implement:

  • Commands for executing tasks 
  • IDataErrorInfo for data validation 
  • Instances of the service classes that will actually access the database
  • Properties coming from the Model that will expose data to Views
  • Objects of type ListCollectionView that, while bound, will allow managing data through the View
  • Properties of type ObservableCollection that return data. Although these properties will not be used effectively, they can be useful for future utilizations and purposes while their backing fields are necessary in order to provide support for ListCollectionViews

Thus after adding a new code file named OrdersViewModel.vb to the project, let's begin by adding some support fields:

Public Class OrdersViewModel
    Inherits ViewModelBase
 
    Implements IDataErrorInfo
 
#Region " Declarations "
 
    'A number of required commands
    Private _cmdDeleteCommand As ICommand
    Private _cmdInsertCommand As ICommand
    Private _cmdNextCommand As ICommand
    Private _cmdPreviousCommand As ICommand
    Private _cmdSaveCommand As ICommand
    Private _cmdViewDetails As ICommand
 
    'A single order
    Private _objOrder As Order
    'The instance of the current customer
    Private _selection As Customer
 
    'Exposing data the common way
    Private _orders As ObservableCollection(Of Order)
    Private _customers As ObservableCollection(Of Customer)
 
    'A "bridge" between data and View
    Private _customerViewSource As New CollectionViewSource
    Private _customerOrdersViewSource As New CollectionViewSource
 
    'Lists of editable objects, both for customers and orders
    Private WithEvents _customersView As ListCollectionView
    Private WithEvents _customerOrdersView As ListCollectionView
 
    'The instance of the service class
    Private orderAccess As IOrderDataService
 
#End Region

At this point we can go to data properties. First we expose CollectionView objects via some properties and the same will be done for ListCollectionView objects; this is important to get the data to work with. Next we have a property representing the instance of the current customer and one representing the instance of the current order. For the sake of completeness we expose also 2 properties of type ObservableCollection for both customers and orders that you can use instead of ListCollectionViews. In the end, the code for properties is the following:

#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
 
    'The instance of the current customer
    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

Now we need command properties. Nothing new, I already talked about this here.

#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

Now it's time for IDataErrorInfo. Again, I discussed this technique in this previous post:

#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

Things become more interesting at the point in which we need to write the constructor.

When the game gets harder

Our ViewModel's constructor plays a fundamental role. In fact it will register and instantiate service classes that will do the actual data access work via Entity Framework. First the code, then some considerations.

#Region " Constructors "
 
    Public Sub New()
        Me._customers = New ObservableCollection(Of Customer)
 
        'Register the instance of the CustomerDataService service class
        ServiceLocator.RegisterService(Of ICustomerDataService)(New CustomerDataService)
        'Register the instance of the service class related to orders
        ServiceLocator.RegisterService(Of IOrderDataService)(New OrderDataService)
 
        'Get the instance of both classes
        Dim dataAccess = GetService(Of ICustomerDataService)()
        Me.orderAccess = GetService(Of IOrderDataService)()
 
        'Retrieves the list of customers, adding each of them to the collection
        For Each element In dataAccess.GetAllCustomers
            Me._customers.Add(element)
        Next
 
        'Set the CollectionViewSource
        _customerViewSource.Source = Me.Customers
 
        'Get a View from the CollectionViewSource and perform a conversion to
        'ListCollectionView which supports editing
        Me.CustomersView = CType(Me.CustomerViewSource.View, ListCollectionView)
        Me.CustomersView.MoveCurrentToFirst()
    End Sub

#End Region

The shared method named RegisterService.ServiceLocator here is registering 2 instances for both the CustomerDataService and OrderDataService service classes. Notice that the generic parameter of RegisterService is not a class but the interface. This is the reason why we need to write interfaces and then implement them. The GetService method, exposed by the base class, retrieves the instance of the service classes. The class related to orders is at class level because we will use it inside command methods. The first usage example is the For Each loop that populates the field storing the list of customers; in fact you can notice how the code invokes the GetAllCustomers method exposed by the service class. Here is the cool thing: the ViewModel is getting a list of data but it does not know that such data are coming from anEntity Data Model.

Let's go ahead with command methods

Now it's time to implement command methods, like CanXXXExecute and XXXExecute, where XXX is the name of the action to execute. Nothing difficult, I discussed this topic here. Anyway, it is worth pointing out a couple of things. Every execution method invokes the related one exposed by the service class. So, while in different scenarios we saw how to execute actions against data directly from within such methods, here we are asking the service class to do the work for us. In this way the ViewModel is still able of working against data but being abstracted from the Data Access Layer, which is in this particular case is the Entity Framework that will receive instructions by the OrderDataService class. This means that some day I can change my data store (although with some slight modifications) but my ViewModel remains substantially unchanged. There is also another interesting detail that I will discuss after the code:

#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

The last method, ViewDetailsExecute, uses the Messenger class. The NotifyColleagues method is sending the message defined in the VIEW_DETAILS_EXECUTE constant so that when the message is intercepted by the View, the Order Details window will be opened. I will retake this technique in the last post of this series, when talking about Views.

Selection changes

The last step for this OrdersViewModel is handling an event. Since we need to understand when the user chooses a different customer via the UI, we can handle the CurrentChanged event in the CollectionView which exposes the list of Customers. When the event is raised, orders related to the newly selected customer are loaded. All is made by invoking methods from the OrderDataService service class. The code:

#Region " Event handlers"
    '_customersView is the backing field for the CustomersView property
    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

Now let's switch to the Order Details ViewModel.

The OrderDetailsViewModel

The ViewModel for managing Order Details works similarly like the previous one but with minor functionalities. In our application we just need to show a list of order details but we will also implement methods for saving data and closing the view with messages because this can be useful in the future. This ViewModel will also use an instance of the OrderDataService class the same way as for the previous one. All the techniques describe here are not new, so you can check the previous section if something appears difficult. The following is the code for the new OrderDetailsViewModel that you add to the project folder called 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

It's worth mentioning that here the Messenger.NotifyColleagues method is also invoked for closing the object. This can be useful in case we want to let the caller know that the associated View is closing. Now we have an infrastructure that works against data, via a service layer that allows ViewModels to interact with data without knowing who's on the other side. It's cool, isn't it?

End of the story

This is the end of this blog post, after a quite long work. Basically we did the following:

  1. placed a declaration for the instance of the Messenger class
  2. implemented a ViewModelBase class
  3. implemented two ViewModels, one for showing the customer/orders relationship and one for showing the list of order details

The last step is defining Views. But this is something that I will not cover in next post, since I will do this in part 10. In part 9 I will talk about something very interesting: first some code refactoring and, most of all, writing unit test so that we can test code blocks understanding one of the most important benefits provided by the MVVM pattern.

Alessandro

posted @ 9:02 PM | Feedback (2)

Thursday, August 05, 2010 #

I would like to let you know that you can download for free the "web-only" chapters from my book "Visual Basic 2010 Unleashed". These are chapters that we could not include in the printed book for space reasons. They are available in PDF format from the following links:

Chapter 56: Advanced IDE Features.

Chapter 57: Introducing the Visual Studio Extensibility.

Chapter 58: Advanced Analysis Tools.

Chapter 59: Testing Code with Unit Tests, Test-Driven Development, and Code Contracts.

In these chapters I discuss the Visual Studio extensibility, advanced IDE features (such as code snippets and templates), analyisis tools like IntelliTrace and Code Analysis but also unit testing, test driven development and the new Code Contracts library. All against Visual Basic 2010 applications. I hope they are useful for your work!

Alessandro

posted @ 12:11 PM | Feedback (5)

Monday, August 02, 2010 #

Let's retake our journey through the MVVM pattern in WPF 4, with Visual Basic 2010, against ADO.NET Entity Framework. Last time we saw how to implement a Messenger class, describing what it is for although we'll see such a class in action in my next post. In this article we will write some code for data access which is important to understand how complex can be building applications based on the MVVM but also how many benefits it can bring into your developer life.

Problem of the day: does the ViewModel know what's the data source?

I confessed that I had to delay the blog post series about MVVM and the Entity Framework because while I was studying the pattern I understood that there was something wrong in my approach and that I was making some mistakes. We are at the point in which we have some data, a data access layer, an object model that allows working with the data. In theory it would be enough writing a ViewModel able of performing data access operations by implementing the appropriate commands.

But here is the real problem: if we think of abstraction, if the ViewModel could interact directly against an EDM it would strictly depend on the EDM itself. But one of the most important purposes of the MVVM is bringing the abstraction level between layers to the max and thus such an approach is not good. The ViewModel should be so indipendent that it should be able of dialing with any data source without suffering in case the data source itself is replaced. In other words, if today I work with XML and tomorrow I will decide to replace it with SQL Server, I should have a so versatile ViewModel that it will not change if my data source changes and, most of all, this approach also allows Views to work with no code edits. Can we do this? Yes, of course. We need just some more work :-)

Thinking about services

The goal is answering the following question: how do I create a ViewModel that does not know what is the underlying data source but that is able of reading and editing such a data source? The problem can be solved by adding a service layer that works like this:

  1. We define an interface named by convention as IxxxDataService, where XXX is the name of the data to represent (e.g. ICustomerDataService). Such an interface defines members that will interact with the data source like queries, insert/update/delete, etc.
  2. We declare a class, named by convention as xxxDataService, where XXX is the name of the data to represent (e.g. CustomerDataService), that implements the previous interface and that executes the actual work against data. 
  3. We write a class named ServiceLocator, which is responsible for passing to callers all the tools for working with the data source, providing separation logic.
  4. We write the ViewModel, which will invoke members from the xxxDataService class, without knowing what the underlying data source is.

What is the advantage of all the above mentioned infrastructure? Imagine that your ViewModel exposes a command for saving data. The ViewModel will expose a command named Save, that will invoke the same-named command from the service class. In this way the ViewModel does not strictly depend on the data source, because it does not know what the underlying source is. Moreover, it exposes a "conventional" command and is able of reaching the objective (that is, saving data). Maybe you are a little bit confused after my discussion. But don't worry, in next post I will begin talking about the ViewModel and everything will be clearer. Just a clarification: you should generally implement as many interfaces/classes as many models you have. We are now ready to implement a service layer.

Refactoring? Yes, of course! But in the final post!

Probably at the end of this blog post, the most experienced developers will point out that some refactoring on our code should be performed. This is true but I have in mind a specific blog post on refactoring the full project, so please don't care about this for now.

Implementing the ServiceLocator class

As I mentioned before, it's necessary implementing a class named by convention as ServiceLocator which offers the possibility of registering interfaces from the service layer and that will also allow getting the instances of the service classes. Such a class is also explained inside the Prism documentation, so you can check this out for details. At the moment I just need to describe the class implementation and its methods. With that said let's add a new class named ServiceLocator to the Services project subfolder. This is the code:

Public Class ServiceLocator
    Implements IServiceProvider
 
    
Private services As New Dictionary(Of TypeObject)()
 
    
Public Function GetService(Of T)() As T
        Return CType(GetService(GetType(T)), T)
    
End Function
 
    
Public Function RegisterService(Of T)(ByVal service As TByVal overwriteIfExists As BooleanAs Boolean
        SyncLock services
            
If Not services.ContainsKey(GetType(T)) Then
                services.Add(GetType(T), service)
                
Return True
            ElseIf overwriteIfExists Then
                services(GetType(T)) = service
                
Return True
            End If
        End SyncLock
        Return False
    End Function
 
    
Public Function RegisterService(Of T)(ByVal service As TAs Boolean
        Return RegisterService(Of T)(service, True)
    
End Function
 
    
Public Function GetService(ByVal serviceType As TypeAs Object Implements IServiceProvider.GetService
        
SyncLock services
            
If services.ContainsKey(serviceType) Then
                Return services(serviceType)
            
End If
        End SyncLock
        Return Nothing
    End Function
End Class

Basically the class implements the IServiceProvider interface and exposes two methods: RegisterService, which registers a type as a service provider, and GetService which retrieves the instance of the service class that makes the actual work against data. Once we have this class, we can define interfaces. There is just one minor step that we need to do before: instantiating the ObjectContext.

Declaring  the ObjectContext

We need a place where to create the instance of the ObjectContext which is named NorthwindEntities in the current project. This cannot be done inside the ViewModel in order to preserve the abstraction logic. This could be done at the service layer level, but that is not the perfect choice. We can consider declaring a project level variable instead; so let's add a new module named Helper to the Helpers subfolder and then let's write the following code:

Module Helper
    Public Northwind As New NorthwindEntities
End Module

We are now really ready for providing interfaces.

Implementing IDataService interfaces

If you remember the figure where I showed how the application will look like at the end of the post series, you may remember how on the left side there is the customers list that the code retrieves from the Customers table in the database. With regard to customers, we don't need anything else so we just need to simply implement an interface defining one method that retrieves the list of customers. Let's add a new interface named ICustomerDataService to the Services subfolder. This is the code:

Public Interface ICustomerDataService

    
Function GetAllCustomers() As IQueryable(Of Customer
)
End Interface

As you can see the interface defines a simple method called GetAllCustomers that, once implemented, will return the full list of customers under the form of an IQueryable(Of Customer). Now it's time for a couple of considerations: the method returns simply an IQueryable instead of an ObjectQuery(Of T) which is typical in Entity Framework. This is appropriate because the result does not depend on the underlying data source. Moreover, one day I could replace the Customer type exposed by my EDM with a custom Customer business object; with the current approach, the ViewModel that receives the result will not be affected at all.

As I mentioned before, for each ViewModel you should implement a service interface. We are working with customers and ordes, so now we need an interface for the latter data type. The new interface is quite interesting because we need to implement:

  1. a method that retrieves the orders list. We will provide two overloads, one for getting the list as an IQueryable and one for getting the list of orders for a given Customer under the form of an ObservableCollection because we are also interested in editing data.
  2. a method for saving data
  3. a method for adding new orders
  4. a method for removing existing orders
  5. methods for navigating between orders

With that said, the new interface is called IOrderDataService and we add it to the Services project subfolder. This is the code:

Imports System.Collections.ObjectModel
Public Interface IOrderDataService
    Function GetOrderDetailsByOrderId(ByVal ID As IntegerAs ObservableCollection(Of Order_Detail)

    
Function GetAllOrders() As IQueryable(Of Order
)
    
Function GetAllOrders(ByVal customerID As StringAs ObservableCollection(Of Order
)
    
Sub
 Save()

    
Sub Delete(ByVal dataSource As Object
)
    
Sub Insert(ByVal dataSource As ObjectByVal selectedCustomer As Customer
)
    
Sub MoveToNext(ByVal dataSource As Object
)
    
Sub MoveToPrevious(ByVal dataSource As Object
)
End Interface

I will better explain some details while implementing both interfaces; for now I would like to underline that the first overload of GetAllOrders, which returns IQueryable, is just implemented for future utilization even if actually we will not use it inside the current application. Other methods receive, as an argument, the data source to work with. Such a data source is passed as an Object. This is useful so that methods can accept different data sources and the appropriate conversions are performed inside service classes.

Implementing service classes

Once the interfaces are defined, we need some classes that implement those interfaces. First of all, let's create a new class named CustomerDataService that implements the related interface. Such a class should be added to the Services subfolder and is made of the following code:

Public Class CustomerDataService
    Implements ICustomerDataService
      Public Function GetAllCustomers() As IQueryable(Of CustomerImplements ICustomerDataService.GetAllCustomers
        
Return Northwind.Customers.Include("Orders"
)
    
End Function
End Class

The method here returns a pure .NET type, which is IQueryable and that will be received by the ViewModel. The method body now returns the result of a query executed against an Entity Data Model, but in the future you might want to replace the data source and return the result of a similar query executed against an XML document. With this approach the ViewModel will continue receiving an IQueryable without knowing what changes occurred at the data source level. While this class is pretty simple, the service class that implements IOrderDataService is more complex although the basic idea is the same. With that said let's add a new class named OrderDataService whose code is the following:

Imports System.Data, System.Windows.Data
Imports System.Data.Objects
Imports System.ComponentModel
Imports System.Collections.ObjectModel
 
Public Class OrderDataService
    Implements IOrderDataService
 
    
Public Function GetOrderDetailsByOrderID(ByVal ID As IntegerAs ObservableCollection(Of Order_DetailImplements IOrderDataService.GetOrderDetailsByOrderId
        
Dim OrderDetailsQuery As System.Data.Objects.ObjectQuery(Of Order_Detail) = CType((From det In Northwind.Order_Details
                                                                                   
Where det.OrderID = ID
                                                                                   
Select det),
                                                                                   
Global.System.Data.Objects.ObjectQuery(Of Order_Detail))
 
        
Return New ObservableCollection(Of Order_Detail)(OrderDetailsQuery)
    
End Function
 
    
Public Sub Insert(ByVal dataSource As ObjectByVal customer As CustomerImplements IOrderDataService.Insert
        
Dim newOrder As Order
        Dim tp = dataSource.GetType
 
        
Select Case tp.Name
            
Case Is = "ListCollectionView"
                Dim source = CType(dataSource, ListCollectionView)
 
                newOrder = 
CType(source.AddNew, Order)
                newOrder.Customer = customer
 
                
'Add a new order so that the data binding
                'engine will invoke the ErrorTemplate
                source.CommitNew()
            
Case Is = "BindingListCollectionView"
                Dim source = CType(dataSource, BindingListCollectionView)
 
                newOrder = 
CType(source.AddNew, Order)
                newOrder.Customer = customer
 
                'Add a new order so that the data binding
                'engine will invoke the ErrorTemplate
                source.CommitNew()
            
Case Else
                Throw New InvalidOperationException("Data source is of a type CollectionView which does not support adding items")
        
End Select
 
    
End Sub
 
    
Public Sub Save() Implements IOrderDataService.Save
        
Try
            Northwind.SaveChanges()
        
Catch ex As OptimisticConcurrencyException
            'Handling concurrency
            Northwind.Refresh(Objects.RefreshMode.ClientWins,
                                        Northwind.Orders)
            Northwind.SaveChanges()
        
Catch ex As Exception
            Throw
        End Try
    End Sub
 
    
Public Function GetAllOrders() As IQueryable(Of OrderImplements IOrderDataService.GetAllOrders
        
Return Northwind.Orders
    
End Function
 
    
Public Function GetAllOrders(ByVal customerID As StringAs ObservableCollection(Of OrderImplements IOrderDataService.GetAllOrders
        
Dim query = From ord In Northwind.Orders.Include("Customer")
                  
Where ord.CustomerID = customerID
                  
Select ord
 
        
Return New ObservableCollection(Of Order)(query)
    
End Function
 
    
Public Sub MoveToNext(ByVal dataSource As ObjectImplements IOrderDataService.MoveToNext
        
CType(dataSource, CollectionViewSource).View.MoveCurrentToNext()
    
End Sub
 
    
Public Sub MoveToPrevious(ByVal dataSource As ObjectImplements IOrderDataService.MoveToPrevious
        
CType(dataSource, CollectionViewSource).View.MoveCurrentToPrevious()
    
End Sub
 
    
Public Sub Delete(ByVal dataSource As ObjectImplements IOrderDataService.Delete
        
Dim tp = dataSource.GetType
 
        
Select Case tp.Name
            
Case Is = "ListCollectionView"
                Dim source = CType(dataSource, ListCollectionView)
                source.Remove(source.CurrentItem)
            
Case Is = "BindingListCollectionView"
                Dim source = CType(dataSource, BindingListCollectionView)
                source.Remove(source.CurrentItem)
            
Case Else
                Throw New InvalidOperationException("Data source is of a type that does not support removing items")
        
End Select
 
    
End Sub
End Class

Let's make the following considerations:

  1. The GetOrderDetailsByOrderId method returns the list of Order Details for the specified order. You should actually implement a method like this inside a specific service related to Order Details, but since this is the one and only operation that we make against such data, we can place it here. The result is an ObservableCollection so that we can have full data-binding support.
  2. The Insert method allows adding a new order to the existing data. The first argument is the data source that will store the new element. It is of type Object but the method body analyzes the actual type and performs the appropriate conversion into a ListCollectionView or BindingListCollectionView depending on the caller. This is because we already know that we will specifically work with this kind of objects coming from the ViewModel. Of course you could extend the code by implementing analysis and conversion to ObservableCollection but this is left to you as an exercise. Working with View objects is convenient because they offer members for working with data, such as browsing, grouping and filtering. The second argument is just the instance of the customer that the new order is associated to.
  3. The overloads implementations for GetAllOrders is quite simple. The second one queries the list of orders belonging to the specifdied customer and returns the result under the form of an ObservableCollection. This overload will be used in our ViewModel.
  4. The Save method is easy. It saves data to the database, checks for concurrency and throws exceptions when required.
  5. The Delete method works like Insert but it differs in that it removes an order from the supplied data source.
  6. The MoveToNext and MoveToPrevious methods allow browsing the orders list and they assume that the data source is browsable, thus of type ICollectionView. For this reason there is a direct conversion and invocation to the appropriate methods.

Refactoring the code is required, but this will be done later. Today we have written a lot of code and learned a lot of new concepts.

End of the story

Part 7 has been really hard. We have:

  1. described how the ViewModel must have a high level of abstraction if compared to the data source, acting against data without knowing the actual type of the data source
  2. described how this is possible by implementing a service layer
  3. implemented a ServiceLocator class that will be responsible of registering services and retrieving their instances
  4. implemented service interfaces and classes that will allow working with data, sending the result to the ViewModel

In next post I will definitely begin discussing ViewModels. We are quite near to the end of the work.

Alessandro

posted @ 12:04 AM | Feedback (3)

Tuesday, July 27, 2010 #

ln my previous post I began describing how to use the MVVM pattern against a model based on the ADO.NET Entity Framework inside WPF apps built with Visual Basic 2010.

Quick recap

Last time we: 

  1. created the Entity Data Model
  2. discussed how this can constitute our Model, representing the data
  3. implemented data validation rules on the Model side taking advantage of IDataErrorInfo

In this new post you will see some more code strictly related to the MVVM pattern, although the most complex work has to come yet. By the way, you will also find a solution to an important problem that I will describe in the last part of the article.

Relaying the command logic

Some time ago we discussed the command logic in MVVM and how you relay the command logic via the RelayCommand class and its generic flavor. In the current scenario things remain absolutely unchanged; because of this, inside the Commands project subfolder let's add a new code file name RelayCommand.vb and paste the code for both implementations of the class, as follows:

Public Class RelayCommand

    Implements ICommand

 

    Private ReadOnly _execute As Action(Of Object)

    Private ReadOnly _canExecute As Predicate(Of Object)

 

    Public Sub New(ByVal execute As Action(Of Object))

        Me.New(execute, Nothing)

    End Sub

 

    Public Sub New(ByVal execute As Action(Of Object), ByVal canExecute As Predicate(Of Object))

        If execute Is Nothing Then

            Throw New ArgumentNullException("execute")

        End If

 

        _execute = execute

        _canExecute = canExecute

    End Sub

 

    <DebuggerStepThrough()> _

    Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute

        Return If(_canExecute Is Nothing, True, _canExecute(parameter))

    End Function

 

    Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged

        AddHandler(ByVal value As EventHandler)

            AddHandler CommandManager.RequerySuggested, value

        End AddHandler

        RemoveHandler(ByVal value As EventHandler)

            RemoveHandler CommandManager.RequerySuggested, value

        End RemoveHandler

        RaiseEvent(ByVal sender As System.Object, ByVal e As System.EventArgs)

        End RaiseEvent

    End Event

 

    Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute

        _execute(parameter)

    End Sub

 

End Class

 

Public Class RelayCommand(Of T)

    Implements ICommand

 

    Private ReadOnly _execute As Action(Of T)

    Private ReadOnly _canExecute As Predicate(Of T)

 

    Public Sub New(ByVal execute As Action(Of T))

        Me.New(execute, Nothing)

    End Sub

 

    Public Sub New(ByVal execute As Action(Of T), ByVal canExecute As Predicate(Of T))

        If execute Is Nothing Then

            Throw New ArgumentNullException("execute")

        End If

 

        _execute = execute

        _canExecute = canExecute

    End Sub

 

    <DebuggerStepThrough()> _

    Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute

        Return If(_canExecute Is Nothing, True, _canExecute(CType(parameter, T)))

    End Function

 

    Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged

        AddHandler(ByVal value As EventHandler)

            AddHandler CommandManager.RequerySuggested, value

        End AddHandler

        RemoveHandler(ByVal value As EventHandler)

            RemoveHandler CommandManager.RequerySuggested, value

        End RemoveHandler

        RaiseEvent(ByVal sender As System.Object, ByVal e As System.EventArgs)

        End RaiseEvent

    End Event

 

    Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute

        _execute(CType(parameter, T))

    End Sub

End Class

A huge problem: launching new windows

If you remember the figure shown in my previous post which shows the full version of the app we're building, you can notice a button named View Details whose job is starting a new Window that will list all details for the order selected in the main window. In a non-MVVM context, there's nothing simpler: I can handle the button's click event, then I istantiate a new Window, I show it, I dispose it. Instead, in a MVVM scenario everything is more complex. I cannot handle any Click event because the button is bound to a command exposed by the ViewModel. Moreover, if I invoke such a command I cannot instantiate a View from within a ViewModel and show it from there because this is completely incorrect according to the pattern principles because a ViewModel cannot handle the UI and Views must store only code that manages the UI. And finally: how could I send back to the view the result of a second window via the ViewModel?

The solution is pretty interesting and is based on a messaging system that sends, intercepts and manages messages. For a better understanding, think of classic events: you click a button on the View, the bound command in the ViewModel raises an event. The View intercepts the event and at this point launches a secondary window. The same thing could happen when closing the secondary window. In the reality of things, this approach works ok but it's not the best approach. MVVM gurus use to implement a pattern known as Mediator that works like this:

  1. when launched, the application defines some messages that will be sent to objects
  2. Views (typically they're not the only ones) subscribe the messaging service so that they will be notified when messages are sent 
  3. ViewModels send the desired messages when commands are invoked 
  4. Views are notified when a message is sent and they take the appropriate action

Talking about our sample application, the main View subscribes the messaging service in order to know when the View Details button gets clicked. When this is clicked, the appropriate command in the ViewModel is invoked and will send the appropriate message. When the View receives a notification about the message, it creates an instance of the new dialog window via a delegate. Don't worry my friend, it's simpler than you can imagine but I'll show you this later.

The Messenger class

Although the root idea is the Mediator pattern, there are a lot of different implementations and differences, which is not uncommon in MVVM. I personally use the implementation offered by Josh Smith and Karl Shifflett, who are MVVM and WPF gurus. The messaging system is implemented through a class named Messenger which acts like a so-called message broker. Such a class provides a number of members but we are interested into just the most important two of them:

  1. method: Register
  2. method: NotifyToColleagues

The first method is invoked to subscribe the messaging system in order to receive the specified message, while the second one is invoked to send the message. I need to show you the complete code for the class but I won't discuss it completely. With regard to this you can find information on the blog of the original author, Karl Shifflett, who provides the class inside his MVVM framework known as Ocean. Here's the code:

Imports System.Reflection

 

''' <summary>

''' Provides loosely-coupled messaging between

''' various colleague objects. All references to objects

''' are stored weakly, to prevent memory leaks.

''' </summary>

Public Class Messenger

 

    Public Sub New()

    End Sub

 

    ''' <summary>

    ''' Registers a callback method to be invoked when a specific message is broadcasted.

    ''' </summary>

    ''' <param name="message">The message to register for.</param>

    ''' <param name="callback">The callback to be called when this message is broadcasted.</param>

    Public Sub Register(ByVal message As String, ByVal callback As [Delegate])

 

        If String.IsNullOrEmpty(message) Then

            Throw New ArgumentException("'message' cannot be null or empty.")

        End If

 

        If callback Is Nothing Then

            Throw New ArgumentNullException("callback")

        End If

 

        Dim parameters As ParameterInfo() = callback.Method.GetParameters()

 

        If parameters IsNot Nothing AndAlso parameters.Length > 1 Then

            Throw New InvalidOperationException("The registered delegate can have no more than one parameter.")

        End If

 

        Dim parameterType As Type = If((parameters Is Nothing OrElse parameters.Length = 0), Nothing, parameters(0).ParameterType)

        _messageToActionsMap.AddAction(message, callback.Target, callback.Method, parameterType)

    End Sub

 

    ''' <summary>

    ''' Notifies all registered parties that a message is being broadcasted.

    ''' </summary>

    ''' <param name="message">The message to broadcast.</param>

    Public Sub NotifyColleagues(ByVal message As String)

 

        If String.IsNullOrEmpty(message) Then

            Throw New ArgumentException("'message' cannot be null or empty.")

        End If

 

        Dim actions = _messageToActionsMap.GetActions(message)

 

        If actions IsNot Nothing Then

            actions.ForEach(Function(action) action.DynamicInvoke())

        End If

 

    End Sub

 

    ''' <summary>

    ''' Notifies all registered parties that a message is being broadcasted.

    ''' </summary>

    ''' <param name="message">The message to broadcast</param>

    ''' <param name="parameter">The parameter to pass together with the message</param>

    Public Sub NotifyColleagues(ByVal message As String, ByVal parameter As Object)

 

        If String.IsNullOrEmpty(message) Then

            Throw New ArgumentException("'message' cannot be null or empty.")

        End If

 

        Dim actions = _messageToActionsMap.GetActions(message)

 

        If actions IsNot Nothing Then

            actions.ForEach(Function(action) action.DynamicInvoke(parameter))

        End If

 

    End Sub

 

    ''' <summary>

    ''' This class is an implementation detail of the Messenger class.

    ''' </summary>

    Private Class MessageToActionsMap

        ' Stores a hash where the key is the message and the value is the list of callbacks to invoke.

        ReadOnly _map As New Dictionary(Of String, List(Of WeakAction))()

 

        Friend Sub New()

        End Sub

 

        ''' <summary>

        ''' Adds an action to the list.

        ''' </summary>

        ''' <param name="message">The message to register.</param>

        ''' <param name="target">The target object to invoke, or null.</param>

        ''' <param name="method">The method to invoke.</param>

        ''' <param name="actionType">The type of the Action delegate.</param>

        Friend Sub AddAction(ByVal message As String, ByVal target As Object, ByVal method As MethodInfo, ByVal actionType As Type)

 

            If message Is Nothing Then

                Throw New ArgumentNullException("message")

            End If

 

            If method Is Nothing Then

                Throw New ArgumentNullException("method")

            End If

 

            SyncLock _map

 

                If Not _map.ContainsKey(message) Then

                    _map(message) = New List(Of WeakAction)()

                End If

 

                _map(message).Add(New WeakAction(target, method, actionType))

            End SyncLock

        End Sub

 

        ''' <summary>

        ''' Gets the list of actions to be invoked for the specified message

        ''' </summary>

        ''' <param name="message">The message to get the actions for</param>

        ''' <returns>Returns a list of actions that are registered to the specified message</returns>

        Friend Function GetActions(ByVal message As String) As List(Of [Delegate])

 

            If message Is Nothing Then

                Throw New ArgumentNullException("message")

            End If

 

            Dim actions As List(Of [Delegate])

            SyncLock _map

 

                If Not _map.ContainsKey(message) Then

                    Return Nothing

                End If

 

                Dim weakActions As List(Of WeakAction) = _map(message)

                actions = New List(Of [Delegate])(weakActions.Count)

 

                For i As Integer = weakActions.Count - 1 To -1 + 1 Step -1

 

                    Dim weakAction As WeakAction = weakActions(i)

 

                    If weakAction Is Nothing Then

                        Continue For

                    End If

 

                    Dim action As [Delegate] = weakAction.CreateAction()

 

                    If action IsNot Nothing Then

                        actions.Add(action)

 

                    Else

                        ' The target object is dead, so get rid of the weak action.

                        weakActions.Remove(weakAction)

                    End If

 

                Next

 

                ' Delete the list from the map if it is now empty.

                If weakActions.Count = 0 Then

                    _map.Remove(message)

                End If

 

            End SyncLock

            Return actions

        End Function

 

    End Class

 

    ''' <summary>

    ''' This class is an implementation detail of the MessageToActionsMap class.

    ''' </summary>

    Private Class WeakAction

        ReadOnly _delegateType As Type

        ReadOnly _method As MethodInfo

        ReadOnly _targetRef As WeakReference

 

        ''' <summary>

        ''' Constructs a WeakAction.

        ''' </summary>

        ''' <param name="target">The object on which the target method is invoked, or null if the method is static.</param>

        ''' <param name="method">The MethodInfo used to create the Action.</param>

        ''' <param name="parameterType">The type of parameter to be passed to the action. Pass null if there is no parameter.</param>

        Friend Sub New(ByVal target As Object, ByVal method As MethodInfo, ByVal parameterType As Type)

 

            If target Is Nothing Then

                _targetRef = Nothing

 

            Else

                _targetRef = New WeakReference(target)

            End If

 

            _method = method

 

            If parameterType Is Nothing Then

                _delegateType = GetType(Action)

 

            Else

                _delegateType = GetType(Action(Of )).MakeGenericType(parameterType)

            End If

 

        End Sub

 

        ''' <summary>

        ''' Creates a "throw away" delegate to invoke the method on the target, or null if the target object is dead.

        ''' </summary>

        Friend Function CreateAction() As [Delegate]

 

            ' Rehydrate into a real Action object, so that the method can be invoked.

            If _targetRef Is Nothing Then

                Return [Delegate].CreateDelegate(_delegateType, _method)

 

            Else

 

                Try

 

                    Dim target As Object = _targetRef.Target

 

                    If target IsNot Nothing Then

                        Return [Delegate].CreateDelegate(_delegateType, target, _method)

                    End If

 

                Catch

                End Try

 

            End If

 

            Return Nothing

        End Function

 

    End Class

 

    ReadOnly _messageToActionsMap As New MessageToActionsMap()

End Class

Basically the Register method receives as an argument the message that will be sent and a delegate that will execute the specified action whereas NotifyToColleagues actually sends the message to all objects listening to the service. I will explain this in practice in next posts, so don't be afraid if something is not clear. 

The end of part 6

Today we implemented a Messenger to exchange messages between "colleague" objects and the RelayCommand class. We need to do a lot of work in order to create a well structured MVVM application and in next post I will begin discussing a service layer that will allow to a ViewModel to interact with data without knowing that the underlying provider is the Entity Framework. It's interesting, stay tuned! 

Alessandro

posted @ 9:36 AM | Feedback (798)