Commanding QuickStart
Retired Content |
---|
This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. |
The Commanding QuickStart demonstrates how to build a Windows Presentation Foundation (WPF) 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.
Figure 1
Commanding QuickStart
Building and Running the QuickStart
The QuickStart ships as source code, which means you must compile it before running it. This QuickStart does not have any prerequisites.
To build and run the QuickStart
- Run the file Open QS-Commanding Quickstart Solution.bat to open the solution in Visual Studio.
- On the Build menu, click Rebuild Solution.
- Press F5 to run the QuickStart.
Walkthrough
Perform the following steps in the Commanding QuickStart to explore the business scenario.
To explore the business scenario
Run the file Open QS-Commanding Quickstart Solution.bat to open the solution in Visual Studio.
On the Build menu, click Rebuild Solution.
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.
Figure 2
QuickStart main windowComplete the data for order 1. The fields with red border are mandatory. After entering the required fields, the total will be automatically calculated and the Save button will be enabled, as shown in Figure 3.
Figure 3
Order information filledClick the Save button to store the order's data. The saved order will be removed from the list and the following order will be selected. Figure 4 shows the application screen after order 1 is saved.
Figure 4
Screen for order 2Complete 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.
Figure 5
Screen showing the Save All Orders button enabledClick the Save All Orders button. All orders will be saved and removed from the orders list, as shown in Figure 6.
Figure 6
The empty orders list
Implementation Details
The QuickStart highlights the key implementation details of an application that leverages the usage of commands. The key artifacts in the application are illustrated in Figure 7.
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 in 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\PresentationModels\OrderPresentationModel.cs).
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>(Save, CanSave);
DeliveryDate = DateTime.Now;
PropertyChanged += OnPropertyChangedEvent;
Validate();
}
The following code shows the implementation of the Save and CanSave methods.
private bool CanSave(object arg)
{
return this.errors.Count == 0 && Quantity > 0;
}
...
private void Save(object obj)
{
// Save the order here.
Console.WriteLine(String.Format("{0} saved.", OrderName));
// Notify that the order was saved.
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")
{
Total = (Price * Quantity) + Shipping;
}
Validate();
SaveOrderCommand.RaiseCanExecuteChanged();
}
The following code, located in the file Commanding.Modules.Order\Views\OrdersEditorView.xaml, shows how the Save button is bound to the SaveOrderCommand command.
<Button AutomationProperties.AutomationId="SaveButton" Grid.Row="6" Grid.Column="1" Content="Save" Command="{Binding SaveOrderCommand}"></Button>
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 is executed, and in consequence, all its child commands—**SaveOrderCommand **commands—are executed for each pending order.
The SaveAllOrdersCommand command is a globally available command, and it is defined in the OrdersCommands class (you can find the class definition in Commanding.Modules.Order\OrdersCommands.cs). 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\PresentationModels\ OrdersEditorPresentationModel.cs, 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 the section "Proxy Class for Global Commands" later in this topic.
private void PopulateOrders()
{
foreach (OrderPresentationModel order in GetOrdersToEdit())
{
order.Saved += OrderSaved;
commandProxy.SaveAllOrdersCommands.RegisterCommand(order.SaveOrderCommand);
Orders.Add(order);
}
}
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 is executed when an order is saved.
void OrderSaved(object sender, DataEventArgs<OrderPresentationModel> e)
{
if (e != null && e.Value != null)
{
OrderPresentationModel order = e.Value;
if (Orders.Contains(order))
{
order.Saved -= OrderSaved;
commandProxy.SaveAllOrdersCommands.UnregisterCommand(order.SaveOrderCommand);
Orders.Remove(order);
}
}
}
The following XAML markup code shows how the SaveAllOrdersCommand command is bound to the SaveAllToolBarButton button in the toolbar. You can find this code at Commanding.Modules.Order\OrdersToolBar.xaml.
<ToolBar>
<Button AutomationProperties.AutomationId="SaveAllToolBarButton" Command="{x:Static inf:OrdersCommands.SaveAllOrdersCommand}">Save All Orders</Button>
<Separator />
</ToolBar>
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 **(you can find the class definition in Commanding.Modules.Order\OrdersCommands.cs). 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.
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.
Note
The acceptance tests have been developed and verified with the White 0.1.5.0 release. Although other releases of White might work too, it is recommended to use the aforementioned release to avoid any issues when running the tests.
To run the Commanding QuickStart acceptance tests
- 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
- In Visual Studio, open the solution file QuickStarts\Commanding\Commanding_AcceptanceTests.sln.
- Right-click Commanding.Tests.AcceptanceTests, and then click Set as StartUp Project.
- 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.
Retired Content |
---|
This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. |