Export (0) Print
Expand All

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 Composite Application 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. Figure 1 illustrates the QuickStart's main window.

Ff647166.506951b2-b0ea-4c0a-9c2b-54986bca7911(en-us,PandP.10).png

Figure 1

Commanding QuickStart — Silverlight version

Building and Running the QuickStart

The QuickStart ships as source code—this means you must compile it before you run it. This QuickStart does not have any prerequisites.

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

The following procedure provides the steps to explore the business scenario in the Commanding QuickStart.

To explore the business scenario

  1. In Visual Studio, open the solution file Quickstarts\Commanding\Commanding.sln.
  2. Make sure that the desired version of the QuickStart is set as the startup project. For more information about this, see "Building and Running the QuickStart" earlier in this topic.
  3. On the Build menu, click Rebuild Solution.
  4. Press F5 to run the QuickStart. The main window shows a list of orders that can be filled with data and submitted, as illustrated in Figure 2.

    Ff647166.85210b7f-1adc-4251-8074-2a66e3838621(en-us,PandP.10).png

    Figure 2

    QuickStart main window — WPF version
  5. 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 Figure 3.

    Ff647166.5add801b-1ffe-42ac-bec4-c2a0b60eb147(en-us,PandP.10).png

    Figure 3

    Order information filled
  6. 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. Figure 4 shows the application screen after order 1 is saved.

    Ff647166.5d383adb-cbb7-46d9-8f88-40aa80e49cf8(en-us,PandP.10).png

    Figure 4

    Screen for order 2
  7. 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 Figure 5.

    Ff647166.917d3f48-796e-4ddf-b414-a6c2647a9155(en-us,PandP.10).png

    Figure 5

    Screen showing the Save All Orders button enabled
  8. Click the Save All Orders button. All orders will be saved and removed from the orders list, as shown in Figure 6.

    Ff647166.3b76b6fb-b806-46d5-a7b7-bbe6f53c1ceb(en-us,PandP.10).png

    Figure 6

    The empty orders list

Implementation Details

The QuickStart highlights the key implementation details of an application that uses commands. Figure 7 illustrates the key artifacts in the application.

Ff647166.cd9fe59b-ac02-43e7-a22e-f7ac1b9e6597(en-us,PandP.10).png

Figure 7

Commanding QuickStart conceptual view

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()
{
    SaveOrderCommand = new DelegateCommand<object>(this.Save, this.CanSave);
    DeliveryDate = DateTime.Now;
    PropertyChanged += this.OnPropertyChangedEvent;
    Validate();
}

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

private bool CanSave(object arg)
{
    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 OnPropertyChangedEvent 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 OnPropertyChangedEvent(object sender, PropertyChangedEventArgs e)
{
    string propertyName = e.PropertyName;
    if (propertyName == "Price" || propertyName == "Quantity" || propertyName == "Shipping")
    {
        this.OnPropertyChanged("Total");
    }
    Validate();
    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" cal:Click.Command="{Binding Path=SaveOrderCommand}" />

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

xmlns:cal="clr-namespace:Microsoft.Practices.Composite.Presentation.Commands;assembly=Microsoft.Practices.Composite.Presentation"

Composite Commands

A CompositeCommand is a command that has multiple child commands. A CompositeCommand is used in the Commanding QuickStart for the SaveAll button on the main toolbar. When you click the SaveAll 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()
{
    foreach (Services.Order order in this.ordersRepository.GetOrdersToEdit())
    {
        var orderPresentationModel = new OrderPresentationModel()
        {
            OrderName = order.Name,
            DeliveryDate = order.DeliveryDate
        };
        orderPresentationModel.Saved += this.OrderSaved;
        this.commandProxy.SaveAllOrdersCommand.RegisterCommand( orderPresentationModel.SaveOrderCommand);
        this.Orders.Add(orderPresentationModel);
    }

    this.SelectedOrder = this.Orders[0];
}

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" cal: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.

Ff647166.note(en-us,PandP.10).gifNote:
For more information about creating globally available commands and considerations when using Silverlight, see How to: Create Globally Available Commands.

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.

Some acceptance tests were developed using the testing framework White. To run these tests, you need to have White installed. For more information about White, including download information, see White on CodePlex.

Ff647166.note(en-us,PandP.10).gifNote:
The acceptance tests have been developed and verified with the White 0.1.5.0 release. Although other releases of White might work, it is recommended to use this release to avoid any issues when running the tests.

To run the Commanding QuickStart acceptance tests

  1. Place the assemblies required by White in the folder Source\Lib\White. The files are the following:
    • Bricks.dll
    • Bricks.RuntimeFramework.dll
    • Castle.Core.dll
    • Castle.DynamicProxy2.dll
    • Core.dll
    • log4net.config
    • log4net.dll
    • nunit.framework.dll
    • White.NUnit.dll
    • Xstream.Core.dll
  2. In Visual Studio, open the solution file QuickStarts\Commanding\Commanding.Tests.AcceptanceTest\Commanding.Tests.AcceptanceTest.sln.
  3. Right-click Commanding.Tests.AcceptanceTest, and then click Set as StartUp Project.
  4. Press F5.

Outcome

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

More Information

To learn about other QuickStarts included with the Composite Application Guidance, see the following topics:



Home page on MSDN | Community site

Show:
© 2014 Microsoft