MVVM Commanding in Silverlight with C1Toolbar
ComponentOne Studio for Silverlight includes more than just the 50 or so UI controls you see in your toolbox. It also includes many little useful components which help bridge the narrow gap between Silverlight and WPF. Among these extra helpers is a set of commanding classes similar to WPF. C1Command allows you to write commands that not only work in-line with MVVM but are designed to enhance ComponentOne menus and toolbars. In this article I delve a bit into MVVM design patterns for commanding in Silverlight and show how using C1Command with C1Toolbar makes life simple.
Background
Commanding is a very important aspect in MVVM. The idea behind MVVM is that your View (i.e. user interface) is bound to a ViewModel object. The ViewModel contains properties which are bound to the user interface, and usually provides all the coded logic between the Model (i.e. data classes) and the View. Commanding is about separating command logic (such as "Copy" or "Paste") from the objects that may invoke the command (such as buttons or menu items). When designing MVVM this means that most of your commanding logic should be placed in the ViewModel rather than the View. Normally we're used to putting our command logic inside click events. This creates, what they call, tightly coupled code and for larger applications this can result in disorderly code-behinds that are hard to test and manage. In a clean, true MVVM design each command is described in your ViewModel and bound to your menus and toolbars from inside the View. The ViewModel does not even know the View exists. Attaching commands is a bit more complex than simply setting properties and collections to one another, as you also need to provide methods which validate the enabled state at all times throughout the life span of the application. Silverlight 4 does not include a full-fledged routed commanding framework like WPF has, so you normally need to write your own custom commands to do this. Enter C1Command. The C1Command class inherits from ICommand (included in SL4). C1Toolbar has a special C1ToolbarCommand ,which inherits C1Command by adding specific toolbar features. These classes basically replace the need for writing your own "DelegateCommand" or "RelayCommand" type of class as seen in many online samples which implement commanding in Silverlight. They work with the C1.Silverlight.CommandManager class which provides command related utility methods for adding, removing and updating commands. Very similar to the classes which do exist in WPF! The ComponentOne toolbar and menu controls support the use of commands through a special Command property, just like the standard Button and Hyperlink controls. The rest of this article describes a sample using C1Toolbar and C1Command to implement commanding using the MVVM pattern. You can download the full sample below.
Getting Started with MVVM in Silverlight
There are many samples and tools on the net showing how to structure your application to be MVVM. So this section will be literally quick and does not explain nor describe everything you should know. Visually speaking, your code is modularized into 3 distinct sections and generally organized into 3 namespaces (or folders) within your project: Model, View and ViewModel. Visuals really help: For this sample we are not dealing with data so the Model folder is empty (added just for show). Each View typically has one ViewModel class to which it's bound. Here we have one View, MainPage.xaml, and its ViewModel, MainPageViewModel.cs. (Small note: MainPageViewModel inherits from ViewModelBase, which is provided given that we may want to include multiple ViewModels which can share some common functionality, such as property changed notification and disposing of UI Elements. This is not really needed in this simple sample, but it's included as it follows best practice.) To attach the ViewModel to the View you set the DataContext when the View is initialized. While not the only approach, I've found this code below makes the most sense to a beginner.
private void Application_Startup(object sender, StartupEventArgs e)
{
var mainPage = new View.MainPage();
//Set DataContext to ViewModel
mainPage.DataContext = new ViewModel.MainPageViewModel();
this.RootVisual = mainPage;
}
The View should be designed so that each changeable part that you may want to test, or is somehow linked to your data model is bound to a property in the ViewModel. See the diagram below. In this sample we bind the ChartType property on a C1Chart control to a property from our ViewModel. The coded logic used to change the ChartType is then put in the ViewModel. Any property that is marked as a dependency property can be bound in this fashion, and therefore is supported under MVVM. By doing this, our View has no direct (one to one) relation with our coded logic in the ViewModel. This design pattern lends well to testing and code reusability. We could now design multiple Views that share the same ViewModel and share code. The ViewModel is also a lot cleaner as it's completely void of all UI idiosyncrasies. These are just some of the benefits of using MVVM.
Commanding in the ViewModel
In addition to business logic, MVVM also puts command logic in the ViewModel. Commands are actually a bit more complex than just properties. With each command you have to have an execution method which runs when the command is fired. Each command may also need to validate and determine when it is enabled. For example, the Copy button in Microsoft Word becomes enabled when you select text. For this type of command there is custom logic that needs to constantly run. By using a commanding framework, like the one provided in WPF, this task is simple. In Silverlight you need to handle commanding yourself. Fortunately, the C1.Silverlight library includes the CommandManager class and the RegisterClassCommandBinding method commonly used in WPF and missing from Silverlight. These methods register commands to your UI allowing you to provide the execution and can-execute methods. Below is the code required to create a complete, single C1Command in a ViewModel class. Note that the command and its methods are static with an owner type of the ViewModel.
// ChartType command
public static readonly C1Command ChartTypeCommand =
new C1Command("ChartType", typeof(MainPageViewModel));
private static void SetChartType(object sender, ExecutedRoutedEventArgs e)
{
// get command target from the sender's DataContext
ButtonBase btn = sender as ButtonBase;
if (btn != null)
{
MainPageViewModel viewModel = btn.DataContext as MainPageViewModel;
if (viewModel != null && e.Parameter != null)
{
// handle ChartType command
viewModel.MainChartType = (ChartType)e.Parameter;
}
}
}
private static void CanSetChartType(object sender, CanExecuteRoutedEventArgs e)
{
// determine if command can be executed
// if false, command button will appear disabled
// Note: to improve performance do not use CanExecute methods where not needed
e.CanExecute = true;
}
In the constructor of the ViewModel is where we register the commands.
static MainPageViewModel()
{
// register class commands in a static constructor
// register ChartType command
CommandManager.RegisterClassCommandBinding(typeof(MainPageViewModel), new CommandBinding(ChartTypeCommand, SetChartType, CanSetChartType));
}
The command above sets the ChartType on C1Chart using CommandParameters. The Execute method (SetChartType) and CanExecute method (CanSetChartType) are registered to the command (ChartTypeCommand) through the CommandManager. Note that CanExecute handlers are optional. If your application does not need them they can be omitted. Unnecessary CanExecute methods might affect performance.
Commands in the View
So far the ChartTypeCommand is declared in our ViewModel, and our View is bound to an instance of the ViewModel. To attach commands to our toolbar buttons we need to add a reference to the namespace in XAML:
xmlns:viewmodel="clr-namespace:ProjectName.ViewModel"
And then inside our C1Toolbar we assign the c1:CommandExtensions.Command property to the public command.
<c1:C1ToolbarButton
c1:CommandExtensions.Command="viewmodel:MainPageViewModel.ChartTypeCommand" ... />
There's more to styling the button, including a label and image. We can also provide a CommandParameter to each button, so that we may share commands that are related. For instance, we may have several buttons sharing the ChartTypeCommand and each with a different parameter (ie Line, Area, Scatter and Pie). No code exists in MainPage.cs (aside from the boiler plate InitializeComponent call). Download the attached sample to see the full implementation including command parameters and even toggle buttons.
Conclusion
It's important to note that MVVM is not the solution for everything. This sample, alone, should not have been written in MVVM style because it is too simple. It would have been much faster to do this using straightforward approaches like click events. But if you like to abstract your code so as to avoid tightly coupled click events then MVVM might be for you. This sample is to show an approach in the simplest manner. Download Sample