Using RelayCommand and RelayCommand<T> instead of using logic in the XAML code behind
This is the first in a series of posts that will attempt to illustrate a way to decouple your application from the traditional code behind paradigm.
Sample code can be found here: https://bitbucket.org/jpmedina/mvvmcalculator
Note, the application implements a contrived calculator as such there is validation added that would, or might, be unnecessary using the correct input scope for the text boxes.
This code is free to use in any shape and form. However, you cannot claim that you wrote the original although you can change it in any manor you see fit including using code in published applications to the Windows Phone Store. I would appreciate any attribution.
This post uses the code in the sample project in the {View}xaml files where {View} is a variation on the Calculator. The MainPage.xaml contains buttons that will load each of these pages, the behavior is identical. [Update: The code has been refactored for the second series, although the UI is nearly identical]
Traditional Application Logic Is In the Code-Behind
Many developers have experience with WinForms, WebForms, WPF, Windows Services or ASP.NET MVC and recognize the traditional mechanism for working with business logic is by having a UI editor attached to a code-behind file. Windows Phone applications are no different. This can be seen in the default application where MainPage.xaml contains the UI (XAML declarations) and MainPage.xaml.cs or The Code-behind which houses the C# code.Often times an application's view has a Button, or a ListBox item that needs to do something when clicked, pressed, scrolled or when another UI event happens. The first and most common mechanism, in WP7 and WP8 it's the default experience, is to name the element and create a button event in the code behind for example (referring to the sample application in (CalculatorTraditional.xaml.cs):
private void OperationAddButton_Click(object sender, RoutedEventArgs e)
{
this.CalculatorResult.Text =
this.CalculatedResults(
(l, r) => l.Number + r.Number);
}
private void OperationSubtractButton_Click(object sender, RoutedEventArgs e)
{
this.CalculatorResult.Text =
this.CalculatedResults(
(l, r) => l.Number - r.Number);
}
private void OperationMultiplyButton_Click(object sender, RoutedEventArgs e)
{
this.CalculatorResult.Text =
this.CalculatedResults(
(l, r) => l.Number * r.Number);
}
private void OperationDivideButton_Click(object sender, RoutedEventArgs e)
{
this.CalculatorResult.Text =
this.CalculatedResults(
(l, r) => l.Number / r.Number);
}
Notice that these methods represent event handlers that respond to click events on the named element. For example, OperationAddButton_Click, represents the event handler for the Button.Click event of the OperationAddButton.
This is not a lot of code and at first glance it easier to keep it in the view’s code behind. That’s a great assumption, if the code you write is concise, specific and not meant to be reused across different views or applications it not only easier, but faster to keep most if not all of the event handlers in the view. However, once your application has graduated from simple to something more complex, having application logic in your view tends to make updating your application harder while increasing the time it takes to debug and release new features.
Using RelayCommand
That’s great but how do I do that? How is it possible to remove the logic while at the same time not cluttering your code with UI elements and references? While a worthwhile goal often times it is necessary to include some dependency on UI elements like Color and TextBox (in order to access specific properties that cannot be intercepted). However, the majority of the logic can be UI agnostic. One mechanism is by using the MVVM pattern and more specifically the RelayCommand and RelayCommand<T> in the MVVM Lite Toolkit: http://mvvmlight.codeplex.com/.
Get ready, here comes the trick, wait for it, wait, Data Binding!
In the CalculatorNoCodeBehind.xaml view you can see that the structure is nearly identical to the first sample. The major points are the binding syntax and this:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <Button x:Name="OperationDivideButton" Foreground="{StaticResource PhoneAccentBrush}" FontSize="48" Width="100" Height="100" Padding="0" Margin="0" Content="/"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <mvvmLite:EventToCommand x:Name="OperationDivisionCommand" Command="{Binding OperationDivisionCommand}"/> </i:EventTrigger> </i:Interaction.Triggers> </Button> |
USING INTERACTION TRIGGER
1 2 3 4 | <Button Command="{Binding ElementName=ResultClearButton, Path=DataContext.OperationReloadCommand}" CommandParameter="{Binding}" ContentTemplate="{StaticResource ResultListButtonItemTemplate}"> </Button> |
USING COMMAND DIRECTLY ON BUTTON
The obvious part, if you've used data binding before is binding the OperationDivisionCommand to the interaction trigger. The not so obvious part is the <Interaction.Triggers> element and the Command binding directly on the button (in newer release of MVVM Light). The interaction trigger and Command binding are the little gems provided by the MVVM Light toolkit in order to allow interception of UI events. This is the glue that allows us to move the logic that was contained in the code behind in the first sample to the CalculatorViewModel in the second one. This is the simpler of the two forms, it allows our user defined Action (in the C# sense) to be called, and optionally a Func that computes whether or not to execute the Action, for the given UI event. Here’s the code for the command creation, in the CalculatorViewModel constructor, for the RelayCommand shown:
this.OperationDivisionCommand = new RelayCommand(this.OperationDivisionHandler);
Alternatively we could have initialized the RelayCommand like this:
this.OperationDivisionCommand = new RelayCommand(() => this.OperationDivisionHandler());
When the calculator’s division button is clicked the event is intercepted and our OperationDivisionHandler method is called. The major advantage here is that the CalculatorViewModel is unaware of who triggered the event and who is responding to it. This is in contrast to the original code with had to be aware of the TextBox directly. Also the code and XAML can now be reused without a lot of refactoring for other pages and applications.
Using RelayCommand<T>
RelayCommand is excellent for triggering actions based on button presses that require no information from the UI to be sent to the business logic in the ViewModel. Although that’s a common scenario it’s not enough to enable some advanced applications. So then how does the UI send information to the ViewModel, or rather how does the ViewModel become aware of UI information? That is where RelayCommand<T> comes into play.
In CalculatorWithTypedRelayCommand.xaml, which is another view that can be accessed from the main page, you’ll notice that the previous TextBlock named “CalculatorResult” has been refactored to be a ListBox named “CalculatorResult” with an ItemTemplate whose DataTemplate “ResultListDataTemplate” can be found in /Resources/DataTemplates.xaml. The ListBox DataTemplate consists of two pieces, a Button and the Buttons DataTemplate “ResultListButtonItemTemplate”. Each item in the list box will be a button that shows the left value, the operand and the right value horizontally in a StackPanel. The button definition looks like this:
1 2 3 4 | <Button Command="{Binding ElementName=ResultClearButton, Path=DataContext.OperationReloadCommand}" CommandParameter="{Binding}" ContentTemplate="{StaticResource ResultListButtonItemTemplate}"> </Button> |
Notice the same “Command” binding but this time there are a couple of additions. First the location to the Command, in this case a RelayCommand<T>, has two be specified since the item being bound is not the ViewModel but each item from the list box. The location is on an element named “ResultClearButton” on its DataContext with the command of OperationReloadCommand. Second, the additional “ComandParameter” element with the value of “{Binding}”. This tells MVVM Light that the bound element needs to be used as the type for the RelayCommand<T>. The command initialization looks like this, in CalculatorViewModelTypedRelayCommand.cs:
this.OperationReloadCommand = new RelayCommand<CalculationResult>(this.OperationReloadHandler);
A point of interest is that the RelayCommand<T> is using CalculationResult, which is a class defined within the ViewModel class, as the parameter of OperationReloadHandler:
public void OperationReloadHandler(CalculationResult oldResult)
{
this.LeftNumber = oldResult.Left.ToString();
this.RightNumber = oldResult.Right.ToString();
}
This method simple reloads the left and right values from the calculation that was just clicked in order to recalculate it. A bit contrived but it allows us to see how we can get information for the ViewModel to the UI and back to the ViewModel with no code behind whatsoever. What happens if we need to access something that is not a bound data item, for example, what if we wanted to make it easier for the user to change their calculation without having to highlight the entire number? What about adding a select all text method? This is arguably one case were we can us a UI element in the ViewModel (there are other ways but as long as you keep the usages neat and tidy as well as isolated this is an easy way to do it. Let’s add another RelayCommand but this time rather than send a data item let’s send the UI element that triggered the event (GotFocus):
The command:
this.CalculationTextBoxGotFocusCommand =
new RelayCommand<RoutedEventArgs>(this.CalculationTextBoxGotFocus);
The handler:
private void CalculationTextBoxGotFocus(RoutedEventArgs e)
{
var textbox = e.OriginalSource as TextBox;
if( textbox == null )
{
return;
}
textbox.SelectAll();
}
And of course the glue, the TextBox command binding (the left number and right are analogous):
1 2 3 4 5 6 7 8 9 | <TextBlock FontSize="24" Text="Left Number"> <i:Interaction.Triggers> <i:EventTrigger EventName="GotFocus"> <mvvmLite:EventToCommand x:Name="LeftNumberFocusCommand" Command="{Binding CalculationTextBoxGotFocusCommand}" PassEventArgsToCommand="True" /> </i:EventTrigger> </i:Interaction.Triggers> </TextBlock> |
As far as I am aware there isn’t an easy way to do this without an interaction trigger, but this is a small price to pay for this flexibility. Some explanations are in order. We’ve seen most of the before, there are three elements necessary when using Relay Commands (both generic and non-generic). First, we need a command defined on our ViewModel, second we need a handler that will take care of the intercepted event. Lastly we need the binding defined in our XAML that glues the whole thing together. We have those three pieces here, the “CalculationTextBoxGotFocusCommand” command, the “CalculationTextBoxGotFocus” handler (this is my term as it Handles the event) and the interaction trigger “LeftNumberFocusCommand” and it corresponding “RightNumberFocusCommand” one. There one difference here that was not in the last RelayCommand<T> we used, the “PasEventArgsToCommand=True” attribute. Hopefull this is simple, the event, in this case a RoutedEventArgs parameter, is passed to our handler. And that’s it. We can access the UI element from this args to handle interactions that are hard to accomplish with data only RelayCommands.
And with that this is the end of the first post. Hopefully there has been enough detail to show you not only how to use the MVVM Light toolkits two RelayCommand mechanism but also given you a good reason to start thinking about using ViewModel as the business processors rather than just the traditional code behind. The next post will detail ViewModel to ViewModel communication using the traditional mechanism, IOC and dependency injection and what I believe is a looser approach, Messaging.
No comments:
Post a Comment