Export (0) Print
Expand All
This topic has not yet been rated - Rate this topic

Commanding QuickStart

The Commanding QuickStart demonstrates how to build a Windows Presentation Foundation (WPF) or Silverlight user interface (UI) that uses commands provided by the Prism Library to handle UI actions in a decoupled way.

Business Scenario

The Commanding QuickStart is based on a fictitious product ordering system. The main window represents a subset of a larger system. In this window, the user can place customer orders and submit them. The following illustration shows the QuickStart's main window.

Commanding QuickStart – Silverlight version

Ff921082.245DC95BF853DE9274D8D4B2543417DC(en-us,PandP.40).png

Building and Running the QuickStart

This QuickStart requires Visual Studio 2010 to run. The Silverlight version of this QuickStart also requires Silverlight 4 and the Silverlight 4 Tools for Visual Studio 2010.

To build and run the QuickStart

  1. In Visual Studio, open the solution file Quickstarts\Commanding\Commanding.sln.
  2. Make sure the desired version of the QuickStart is set as the startup project. If it is not, right-click the desired project in Solution Explorer, and then click Set as Startup Project:
    • To build and run the WPF version of the QuickStart, the startup project should be the Commanding.Desktop project in the Desktop solution folder.
    • To build and run the Silverlight version of the QuickStart, the startup project should be the Commanding.Silverlight project in the Silverlight solution folder.
  3. On the Build menu, click Rebuild Solution.
  4. Press F5 to run the QuickStart.

Walkthrough

To explore the scenario, perform the following steps to build and run the QuickStart:

  1. The main window shows a list of orders that can be filled with data and submitted, as shown in the following illustration.

    QuickStart main window – WPF version

    Ff921082.05C93810790A34EF1B0F007F364D120A(en-us,PandP.40).png

  2. Complete the data for order 1. The fields with red border are mandatory. After you enter information into the required fields, the total is automatically calculated and the Save button is enabled, as shown in the following illustration.

    Order information filled

    Ff921082.610A3125CADE3435E657F48CE33B6F5B(en-us,PandP.40).png

  3. Click the Save button to store the order's data. This causes the saved order to be removed from the list and the following order to be selected. The following illustration shows the application screen after order 1 is saved.

    Screen for order 2

    Ff921082.E35C38BF96F7D3B3C2F76D7C1ECD95E5(en-us,PandP.40).png

  4. Complete the two remaining orders by selecting the corresponding order from the orders list (do not click the Save button). After you enter the required information for all the orders, the Save All Orders button on the toolbar will be enabled, as shown in the following illustration.

    Screen showing the Save All Orders button enabled

    Ff921082.B4BCDC50FAF4ECD79F65AC511A8571B7(en-us,PandP.40).png

  5. Click the Save All Orders button. All orders will be saved and removed from the orders list, as shown in the following illustration.

    The empty orders list

    Ff921082.32772C7D18F78CDAC1F2F833CCD6F1DD(en-us,PandP.40).png

Implementation Details

The QuickStart highlights the key implementation details of an application that uses commands. The following illustration shows the key artifacts in the application.

Commanding QuickStart conceptual view

Ff921082.EBA20354352E1B321D53A2B31060957C(en-us,PandP.40).png

Ff921082.note(en-us,PandP.40).gifNote:
The QuickStart contains a number of TODO comments to help navigate the important concepts in the code. Use the Task List window in Visual Studio to see a list of these important areas of code. If you double-click an item in the list, the code file will open in the appropriate line.

Delegate Commands

By using the DelegateCommand command, you can supply delegates for the Execute and CanExecute methods. This means that when the Execute or CanExecute methods are invoked on the command, the delegates you supplied are invoked.

In the Commanding QuickStart, the Save button on each order form is associated to a delegate command. The delegates for the Execute and CanExecute methods are the Save and CanSave methods of the OrderPresentationModel class, respectively (this class is the presentation model for an order; for the class definition, see the file Commanding.Modules.Order.{Technology}\PresentationModels\OrderPresentationModel.cs) where {Technology} can be Desktop or Silverlight.

The following code shows the constructor of the OrderPresentationModel class. In the method body, a delegate command named SaveOrderCommand is created—it passes delegates for the Save and CanSave methods as parameters.

public OrderPresentationModel( Services.Order order )
{
    _order = order;

    //TODO: 01 - Each Order defines a Save command.
    this.SaveOrderCommand = new DelegateCommand<object>( this.Save, this.CanSave );

    // Track all property changes so we can validate.
    this.PropertyChanged += this.OnPropertyChanged;

    this.Validate();
}

The following code shows the implementation of the Save and CanSave methods.

private bool CanSave( object arg )
{
    //TODO: 02 - The Order Save command is enabled only when all order data is valid.
    // Can only save when there are no errors and
    // when the order quantity is greater than zero.
    return this.errors.Count == 0 && this.Quantity > 0;
}

private void Save( object obj )
{
    // Save the order here.
    Console.WriteLine( 
       String.Format( CultureInfo.InvariantCulture, "{0} saved.", this.OrderName ) );

    // Notify that the order was saved.
    this.OnSaved( new DataEventArgs<OrderPresentationModel>( this ) );
}

The following code shows the OnPropertyChanged method implementation. This method is an event handler for the PropertyChanged event, which gets raised whenever the user changes a value in the order form. This method updates the order's total, validates the data, and raises the CanExecuteChanged event of the SaveOrderCommand command to notify the command's invokers about the state change.

private void OnPropertyChanged( object sender, PropertyChangedEventArgs e )
{
    // Total is a calculated property based on price, quantity and shipping cost.
    // If any of these properties change, then notify the view.
    string propertyName = e.PropertyName;
    if ( propertyName == "Price" || propertyName == "Quantity" || propertyName == "Shipping" )
    {
        this.NotifyPropertyChanged( "Total" );
    }

    // Validate and update the enabled status of the SaveOrder
    // command whenever any property changes.
    this.Validate();
    this.SaveOrderCommand.RaiseCanExecuteChanged();
}

The following code, located in the file Commanding.Modules.Order.Desktop\Views\OrdersEditorView.xaml, shows how the Save button is bound to the SaveOrderCommand command in WPF.

<Button AutomationProperties.AutomationId="SaveButton" Grid.Row="6" Grid.Column="1" Content="Save" Command="{Binding SaveOrderCommand}"></Button>

To bind the Save button to the SaveOrderCommand command in Silverlight, use the Click.Command attached behavior as seen in the following code, located at Commanding.Modules.Order.Silverlight\Views\OrdersEditorView.xaml.

<Button Grid.Row="6" Grid.Column="1" Content="Save" prism:Click.Command="{Binding Path=SaveOrderCommand}" AutomationProperties.AutomationId="SaveButton" />

To be able to use the Click.Command attached behavior, you have to add the following namespace to a root element.

xmlns:prism=http://www.codeplex.com/prism

Composite Commands

A CompositeCommand is a command that has multiple child commands. A CompositeCommand is used in the Commanding QuickStart for the Save All button on the main toolbar. When you click the Save All button, the SaveAllOrdersCommand composite command executes, and in consequence, all its child commands—SaveOrderCommand commands—execute for each pending order.

The SaveAllOrdersCommand command is a globally available command, and it is defined in the OrdersCommands class (the class definition is located at Commanding.Modules.Order.{Technology}\OrdersCommands.cs), where {Technology} can be Desktop or Silverlight. The following code shows the implementation of the OrdersCommands static class.

public static class OrdersCommands
{
    public static CompositeCommand SaveAllOrdersCommand = new CompositeCommand();
}

The following code, extracted from the file Commanding.Modules.Order.{Technology}\PresentationModels\OrdersEditorPresentationModel.cs, where {Technology} can be Desktop or Silverlight, shows how child commands are registered with the SaveAllOrdersCommand command. In this case, a proxy class is used to access the command. For more information, see "Proxy Class for Global Commands" later in this topic.

private void PopulateOrders()
{
    _orders = new ObservableCollection<OrderPresentationModel>();

    foreach ( Services.Order order in this.ordersRepository.GetOrdersToEdit() )
    {
        // Wrap the Order object in a presentation model object.
        var orderPresentationModel = new OrderPresentationModel( order );
        _orders.Add( orderPresentationModel );

        // Subscribe to the Save event on the individual orders.
        orderPresentationModel.Saved += this.OrderSaved;

        //TODO: 04 - Each Order Save command is registered with the application's SaveAll command.
        commandProxy.SaveAllOrdersCommand.RegisterCommand( orderPresentationModel.SaveOrderCommand );
    }
}

When an order is saved, the SaveOrderCommand child command for that particular order must be unregistered. The following code shows how this is done in the implementation of the OrderSaved event handler, which executes when an order is saved.

private void OrderSaved(object sender, DataEventArgs<OrderPresentationModel> e)
{
    if (e != null && e.Value != null)
    {
        OrderPresentationModel order = e.Value;
        if (this.Orders.Contains(order))
        {
            order.Saved -= this.OrderSaved;
            this.commandProxy.SaveAllOrdersCommand.UnregisterCommand(order.SaveOrderCommand);
            this.Orders.Remove(order);
            if (this.Orders.Count > 0)
            {
                this.SelectedOrder = this.Orders[0];
            }
        }
    }
}

The following XAML markup code shows how the SaveAllOrdersCommand command is bound to the SaveAllToolBarButton button in the toolbar. This code is located at Commanding.Modules.Order.Desktop\OrdersToolBar.xaml.

<ToolBar>
  <Button AutomationProperties.AutomationId="SaveAllToolBarButton" Command="{x:Static inf:OrdersCommands.SaveAllOrdersCommand}">Save All Orders</Button>
  <Separator />
</ToolBar>

In Silverlight, to bind the SaveAllOrdersCommand command to the SaveAllToolBarButton button, you have to use the Click.Command attached behavior. Notice that in the Silverlight version of the QuickStart, there is no ToolBar control because Silverlight does not provide this control.

<Button AutomationProperties.AutomationId="SaveAllToolBarButton" prism:Click.Command="{Binding SaveAllOrdersCommand}" Content="Save All Orders" Width="120" /> 

Proxy Class for Global Commands

To create a globally available command, you typically create a static instance of a CompositeCommand class and expose it publicly through a static class. This approach is straightforward, because you can access the command instance directly from your code. However, this approach makes your classes that use the command hard to test in isolation, because your classes are tightly coupled to the command. When testability is a concern in an application, a proxy class can be used to access global commands. A proxy class can be easily replaced with mock implementations when writing unit tests.

The Commanding QuickStart implements a proxy class named OrdersCommandProxy to encapsulate the access to the SaveAllOrdersCommand (the class definition is located at Commanding.Modules.Order.{Technology}\OrdersCommands.cs), where {Technology} can be Desktop or Silverlight. The class, shown in the following code, implements a public property to return the SaveAllOrdersCommands command instance defined in the OrdersCommands class.

public class OrdersCommandProxy
{
    public virtual CompositeCommand SaveAllOrdersCommands
    {
        get { return OrdersCommands.SaveAllOrdersCommand; }
    }
}

In the preceding code, note that the SaveAllOrdersCommands property can be overwritten in a mock class to return a mock command.

For more information about creating globally available commands, see "Binding to a Globally Available Command" in Chapter 9, "Communicating Between Loosely Coupled Components."

Acceptance Tests

The Commanding QuickStart includes a separate solution that includes acceptance tests. The acceptance tests describe how the application should perform when you follow a series of steps; you can use the acceptance tests to explore the functional behavior of the application in a variety of scenarios.

To run the Commanding QuickStart acceptance tests

  1. In Visual Studio, open the solution file QuickStarts\Commanding\Commanding.Tests.AcceptanceTest\Commanding.Tests.AcceptanceTest.sln.
  2. Right-click Commanding.Tests.AcceptanceTest, and then click Set as StartUp Project.
  3. Press F5.

Outcome

You should see the QuickStart window and the tests automatically interact with the application. At the end of the test run, you should see that all tests have passed.

More Information

For more information about commands, see the following chapters:

To learn about other QuickStarts included with Prism, see the following topics:




Last built: August 28, 2012

Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft. All rights reserved.