Events in XAML-Based Applications
Introduction
As a developer, I began to use the .NET Framework from its very beginning. It was 2002 when I started to play with .NET Framework 1.0 and Visual Studio .NET 2002. In those years, life was very hard for developers using Microsoft Technologies. Visual Basic 6.0 was beginning to show its limits, and Visual C++ 4.0 was considered too complex and not productive enough for businesses. The Java programming language was almost always the preferred choice for companies that were beginning to focus on software development as an important factor in competitiveness.
The situation began to change when experts started to recognize the innovative architecture of ASP.NET technology and the power of server controls. In those years, business applications began to move to the Web, giving this approach a lot of advantages in term of deployment of applications and centralization (on the server) of business information. Previously the Web was considered primarily a platform for text and images, and using it for data management was a big challenge. ASP.NET was the Microsoft response to this challenge. The idea of the page's postback was the first attempt to apply the data-centric behaviors of desktop applications to the content-oriented characteristics of the web platform.
With ASP.NET, when a user interacts with some controls on the page, the page is posted back to the server, and some event handler is executed. If code inside the handler modifies the state of the page, an updated version of that page is returned to the user. The intent was to reproduce the behavior of WinForm applications, where each user action generates an event that invokes event handlers installed for it. Even if the idea behind the page's postback had been successful for the transition from websites to web applications, the limits imposed by the HTML standard sometimes forced developers to perform real stunts to implement some functionalities. (Remember that HTML was designed with content in mind!)
Life became easier with the advent of AJAX and JavaScript libraries in general, even though they weren't always easy to implement. On the other hand, the growth of multimedia contents over time also increased the expectations of the desktop user. Along came Windows Presentation Foundation (WPF) and Silverlight (actually in its fourth version). In my opinion, this change represented a real milestone in relation to the new multimedia requirements; above all, this was a step toward further integration between desktop and data-centric web applications. In general, it seems destined to be the all-in-one technology for the presentation layers of desktop, web, and mobile applications.
As a WinForm and ASP.NET developer, I was very excited when I started to understand the benefits introduced by the Extensible Application Markup Language (XAML), both when developing desktop applications (using WPF) and in web applications (using Silverlight).
WPF and Silverlight architectures are based mainly on the DependencyObject class, a class from which all the UIElement classes (which controls classes) and animation-related classes derive. The introduction of DependencyObjects brings a lot of new and interesting features, the Binding subsystem being the most important when dealing with data management.
Binding in XAML applications deeply changed the way in which software has to be designed. The concept of n-tier architecture, very common in pre-XAML applications, is being replaced by patterns like Model-View-ViewModel (MVVM), based on a clear distinction between the user interface (the View) and the data objects (the Model). The need for it derives from the new graphics capabilities of XAML-based applications. The design of the user interface with XAML has become a job for graphic designers rather than software developers. Designers feel comfortable with fonts, palettes, and animations, but in general they prefer to have nothing to do with code; they prefer Expression Blend to Visual Studio. So the separation of XAML from business logic code has become an important aspect when architecting a new application.
While the separation of the Model from the View doesn't require new concepts for developers (a conventional class with properties that describe the purpose of the data object, sometimes referred as "plain old CLR object" or POCO, is enough as the source for data binding), implementing all the functionality related to the user interaction with the View requires some new concepts that are the subject of this article.
The ICommand Interface
Traditionally, in WinForm and ASP.NET programming, for each user action we develop an event handler that we install for the event raised. Normally, we put the event handler's code in the code behind a Windows Form or ASP.NET web page, but this approach is inadequate for XAML-based applications. It brings a strong coupling between the user interface and the code behind our solution, thus diminishing the power of XAML.
We need to separate the XAML code from all the other code and use Binding as the "gateway" between the data, actions on the data, and the presentation layer. For this to happen, we need to try our best to use properties rather than methods. In fact, only properties can act as source of Binding.
A good approach is to use the Command and CommandParameter properties defined for all the controls that permit a mouse click. Command and CommandParameter are defined in the ButtonBase, MenuItem, and Hyperlink classes. Command is a property that implements the ICommand interface, defined as follows:
bool CanExecute(object parameter) void Execute(object parameter) event EventHandler CanExecuteChanged
The CanExecute(...) method permits you to check whether the command can be executed. As input argument, it accepts the value assigned to the CommandParameter property. It has a return value of type Boolean. When the method returns true, the command can be executed. When it returns false, it cannot. Normally, in this case, the control to which the command belongs (very often a Button) appears disabled.
The Execute(...) method is where we write the code that will be executed when the command is invoked. Even in this case, the value assigned to the CommandParameter property is made accessible to us as the method's argument.
The CanExecuteChanged event tracks the availability of the command. The CanExecute(...) method is invoked when the command is created. If something changes during the lifetime of its owner, the CanExecuteChanged event is fired and the CanExecute(...) method is reevaluated, thus updating the state of the owner. Note that you don't need to assign an event handler to that event; it's automatically installed by the owner during its creation, and you only invoke it when needed.
Let's look at an example. Suppose that we have a Button that performs some action when data is available. We can build our command as shown in Listing 1.
Listing 1ICommand interface implementation.
public class ProcessDataCommand:ICommand { bool _areDataAvailable; public event EventHandler CanExecuteChanged; /// <summary> /// State if data are available /// </summary> public bool AreDataAvailable { get {return _areDataAvailable;} set {_areDataAvailable = value; if (CanExecuteChanged != null) CanExecuteChanged(this,new EventArgs()); } } /// <summary> /// CanExecute method /// </summary> /// <returns></returns> public bool CanExecute(object parameter) { return AreDataAvailable; } /// <summary> /// Execute method /// </summary> public void Execute(object parameter) { DoSomething(parameter); } }
ProcessDataCommand defines a property, AreDataAvailable, which tracks data availability. When the ProcessDataCommand object is created, its value is set to false, so the Button appears disabled. When data reaches the application, we set AreDataAvailable to true. This causes the CanExecute(...) method to reevaluate. In this case, the value returned by the method is true, so the Button will be enabled.
This automatic refresh of the Button's state happens only if we use the "magic wand" of Binding. Our XAML code should be something like Listing 2, where OurDemoApplication is the namespace given to our application.
Listing 2ICommand-derived object usage.
<UserControl ... ... xmlns:local="clr-namespace:OurDemoApplication"> <UserControl.Resources> <local:ProcessDataCommand x:Key="ProcessDataCommand" /> </UserControl.Resources> <Button Content="Process Data" Command="{Binding Source={StaticResource ProcessDataCommand}}" />
Routed Commands
If you're using WPF instead of Silverlight, you have more options. As a source of Binding, you can use objects of type RoutedUICommand or RoutedCommand (the RoutedUICommand class simply adds the Text property to the base RoutedCommand class). RoutedUICommand implements the ICommand interface, but in this case the .NET Framework doesn't allow you to override the CanExecute(...) and Execute(...) methods, so you have to use the CommandBindings property of the Window object, as shown in Listing 3.
Listing 3RoutedUICommand usage.
<Window.Resources> <RoutedUICommand x:Key="CustomCommand" Text="Process" /> </Window.Resources> <Window.CommandBindings> <CommandBinding Command="{StaticResource CustomCommand}" CanExecute=" CustomCommand_CanExecute" Executed=" CustomCommand_Executed" /> </Window.CommandBindings> <Window.InputBindings> <KeyBinding Command="{StaticResource CustomCommand}" Key="P" Modifiers="Alt" /> </Window.InputBindings> <Grid DataContext="{StaticResource CustomCommand}"> <Grid.RowDefinitions> <RowDefinition Height="auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Menu> <MenuItem Header = "{Binding Path=Text}" Command="{Binding}" Grid.Row="0" /> </Menu> <Button Content = "{Binding Path=Text}" Command="{Binding}" Grid.Row="1" /> </Grid>
In Listing 3 we have created an object of type RoutedUICommand in the Resource section of the Window object, and we added it to the Window's CommandBinding collection. At this stage, we declare the two handlers for the CanExecute(...) and Execute(...) methods.
RoutedUICommand also allows us to invoke a command by using the keyboard. To do so, in the InputBindings collection of the Window object, we've added a KeyBinding object. By pressing Alt-P on the keyboard, the command is invoked as if we had clicked it.
Finally, we've declared Button and MenuItem objects and bound the new command to them. Now we're able to invoke the Execute(...) method by clicking the Button, clicking the MenuItem, or pressing Alt-P on the keyboard.
RoutedUICommands are so important in WPF that the PresentationCore.dll and PresentationFramework.dll assemblies define a complete set of prebuilt RoutedUICommands. They're grouped under five distinct classes:
- ApplicationCommands
- ComponentCommands
- MediaCommands
- NavigationCommands
- EditingCommands
Listing 4 shows how to add a "Close" RoutedUICommand to the CommandBindings collection of a Window object.
Listing 4Predefined RoutedUICommand usage.
<Window.CommandBindings> <CommandBinding Command="Close" CanExecute="Close_CanExecute" Executed="Close_Executed" /> </Window.CommandBindings> <Button Content = "Close" Command="Close" />
Very often, RoutedUICommands are considered the preferred choice when working with WPF. Their main disadvantage is that they can't bring a full separation of XAML code from the business logic's code. Event handlers need to be implemented in the code behind a WPF control; we can't move them into a ViewModel class.
So why are they preferred? One reason is surely the ease of their implementation, as we've seen in Listing 4. But the main reason relies on the different nature of ICommand-derived objects and RoutedUICommands. These last, in fact, are specific to XAML-based applications, and they were introduced in order to take full advantage of the tree-based relationship between controls disposition. When we install an event handler for a control, all the controls inside its elements tree are able to fire the same event. We say that the event has been routed from an element to its container.
This behavior very often represents not an optimization, but a real need. Suppose we have a Button with its Content property set to a simple TextBlock object. Then suppose that we want to perform some tasks when the user moves the mouse inside the button surface. What really is the button surface? Traditionally we consider a button a clickable rectangle with a caption inside it. In XAML-based applications, this is no longer true. The content that we see inside the rectangle is an object itself. In our example, it's a TextBlock object. TextBlock derives from FrameworkElement, and then from UIElement. A portion of the rectangle's visible area therefore belongs to it, and it has its own MouseMove event, too. If we move the mouse inside the TextBlock area, it's the TextBlock, not the Button, that receives the MouseMove event. So how do we obtain the same result for legacy Buttons? The response is to route the TextBlock's MouseMove event to the Button. Consider the example in Listing 5, where Button_MouseMove is defined as in Listing 6.
Listing 5Routed events in XAML.
<Button x:Name="MyButton" MouseMove="Button_MouseMove" > <Button.Content> <TextBlock Text="Click this Button !" /> </Button.Content> </Button>
Listing 6Routed eventscode behind.
private void Button_MouseMove(object sender, MouseEventArgs e) { Title = e.Source.ToString(); }
To the Title property of the Window is assigned the type of the object source of the event. When we move the mouse inside the region defined by the Button borders, the Title of the Window displays the value "System.Windows.Controls.Button" unless we enter the TextBlock region. In this case, the Title becomes "System.Windows.Controls.TextBlock". The TextBlock has routed the event to the Button. To prove this is the case, we add an event handler to the TextBlock, as shown in Listing 7, where the TextBlock_MouseMove event is given by Listing 8.
Listing 7Prevent routing in routed eventsXAML.
<Button x:Name="MyButton" MouseMove="Button_MouseMove" > <Button.Content> <TextBlock Text="Click this Button !" MouseMove="TextBlock_MouseMove" /> </Button.Content> </Button>
Listing 8Prevent routing in routed eventscode behind part 1.
private void TextBlock_MouseMove(object sender, MouseEventArgs e) { Title = ""; }
Nothing changes. Why? After that, the TextBlock_MouseMove event is executed, the event is routed to the Button, and so the Button_MouseMove is fired, bringing us to a situation identical to the previous one. To prevent this from happening, we need to instruct the TextBlock_MouseMove event handler that its job is complete, and nothing has to be done. In other words, we need to prevent the event from being routed. We do this by using the Handled property of the argument. The Handled property is a Boolean property defined in the RoutedEventArgs class, so it's available to all the routed events. Each of them, in fact, has as its second parameter an object whose class is given by or that derives from RoutedEventArgs. We rewrite the event handler as in Listing 9.
Listing 9Prevent routing in routed eventscode behind part 2.
private void TextBlock_MouseMove(object sender, MouseEventArgs e) { Title = ""; e.Handled = true; }
In this case, when the mouse pointer moves inside the TextBlock area, the Window's Title disappears, as we expected.
Grouping Events
Another common approach when using routed events relies on the possibility of installing an event handler on a container when two or more controls inside it are able to fire the same event. Suppose that we have two distinct Buttons with content given by a TextBlock, and we want to manage the MouseMove event in both of them. We can group the two buttons; for example, in a StackPanel, and install the event handler on it. StackPanel derives indirectly from the UIElement class, so it supports the MouseMove event as the Button and TextBlock classes do. Take a look at the example in Listing 10.
Listing 10Install routed events for multiple controls.
<StackPanel MouseMove="Button_MouseMove"> <Button> <Button.Content> <TextBlock Text="Click this Button !" /> </Button.Content> </Button> <Button> <Button.Content> <TextBlock Text="Click this Button !" /> </Button.Content> </Button> </StackPanel>
When we move the mouse pointer inside one of the two TextBlock objects, the event is routed to its container (the Button to which the TextBlock belongs); at the same time, it routes the event to the StackPanel. In this case, the source of the event is the TextBlock that has the mouse pointer moving inside its surface, so the window will display the value "System.Windows.Controls.TextBlock" as the title. If we move the mouse pointer inside one of the two buttons, the Window's title will became "System.Windows.Controls.Button", as expected.
But what if we need to install event handlers for the Click events of the two Buttons? The Click event, as for the Command property, doesn't belong to the UIElement class or the FrameworkElement class. It's defined in the ButtonBase class, so StackPanel doesn't have it. If we have some reason not to bind the two Command properties of the two Buttons to a single object that implements the ICommand interface, we can use Styles instead. As you probably know, XAML-based applications permit you to group common properties' values into a single Style class that then can be applied to controls. The Style class can also group events by using the EventSetter class. In this case, our previous example changes as shown in Listing 11.
Listing 11EventSetter usage.
<Window.Resources> <Style x:Key="ButtonStyle" TargetType="Button"> <EventSetter Event="Click" Handler="Button_Click" /> </Style> </Window.Resources> <StackPanel> <Button Style="{StaticResource ButtonStyle}"> <Button.Content> <TextBlock Text="Click this Button !" /> </Button.Content> </Button> <Button Style="{StaticResource ButtonStyle}"> <Button.Content> <TextBlock Text="Click this Button !" /> </Button.Content> </Button> </StackPanel>
We've created a Style, with the key given by ButtonStyle, and we used the EventSetter class to install the Button_Click event handler to the Click event. Then we added the created Style to the Style property of the two buttons.
Using Triggers
Styles not only permit us to group common related properties and events; they also allow controls to which the Style is applied to modify the state of the Style when some condition occurs. This is done with Triggers. Even if they're not strictly events (in fact, they're related to the user interface design), I would like to end this article with them because, prior to XAML, what Triggers do was possible only with events.
Suppose you want to change the Foreground property of a Button when the mouse pointer hovers over it. Traditionally, we would install an event handler for the MouseOver event of the Button and write some C# or Visual Basic .NET code. In WPF applications, we can use a Trigger instead, as shown in Listing 12.
Listing 12WPF's Trigger usage.
<Window.Resources> <Style x:Key="ButtonStyle" TargetType="Button"> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Foreground" Value="Beige" /> </Trigger> </Style.Triggers> </Style> </Window.Resources> <Button Style="{StaticResource ButtonStyle}"> <Button.Content> <TextBlock Text="Click this Button !" /> </Button.Content> </Button>
In this example, the IsMouseOver property is monitored by the Trigger. When it assumes the value True, the Setter is applied. When its value changes back to False, the Setter is removed. We're synchronizing the IsMouseOver property of the Button with its Foreground property without writing a line of C# or Visual Basic .NET code. Multiple Trigger conditions can be applied. In this case, we need to use the MultiTrigger object. Suppose that we want to change the Foreground color of the Button when the mouse is over it, and the Button is the default button of the Window. We need to modify the previous example as in Listing 13.
Listing 13WPF's MultiTrigger usage.
<Window.Resources> <Style x:Key="ButtonStyle" TargetType="Button"> <Style.Triggers> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsMouseOver" Value="True" /> <Condition Property="IsDefault" Value="True" /> </MultiTrigger.Conditions> <Setter Property="Foreground" Value="Beige" /> </MultiTrigger> </Style.Triggers> </Style> </Window.Resources> <Button Style="{StaticResource ButtonStyle}"> <Button.Content> <TextBlock Text="Click this Button !" /> </Button.Content> </Button>
We've defined a MultiTrigger object and set two conditions for it: the mouse pointer had to be over the Button, and the Button needed to be the default button of the Window.
The Triggers that we've seen so far are property-based. The Trigger condition is always applied to properties of some controls. With Triggers it's possible to monitor the data's current values, too. To do so, we need to use DataTriggers and MultiDataTriggers. In this case, the Property attribute is replaced by a Binding to the data object to monitor.
Suppose that we have a ViewModel class that exposes a List<String> object to the View. Then suppose that we have a Button that performs some operation on it and we want to disable the Button if the list is empty. We can write something like Listing 14.
Listing 14WPF's DataTrigger usage.
<Window ... ... xmlns:local="clr-namespace:OurDemoApplication"> <Window.Resources> <local:VmSomeData x:Key="SomeData" /> <Style x:Key="ButtonStyle" TargetType="Button"> <Style.Triggers> <DataTrigger Binding="{Binding Source={StaticResource SomeData}, Path=MyData.Count}" Value="0"> <Setter Property="IsEnabled" Value="False" /> </DataTrigger> </Style.Triggers> </Style> </Window.Resources> <Button Style="{StaticResource ButtonStyle}"> <Button.Content> <TextBlock Text="Click this Button !" /> </Button.Content> </Button>
In the resource section of the Window, we've declared a static resource related to the ViewModel class (VmSomeData). After that, we created a Style object and added a DataTrigger to its TriggersCollection. The DataTrigger monitors the Count property of the MyData (the List<String>) object. When the value is 0, the Setter puts the Button into a disabled state. If we add some data to the list, the Button becomes enabled again.
Another type of Trigger exists. The EventTrigger monitors changes in event conditions. It's used in XAML-based animations, and I leave you on your own to investigate how it works.
Note that, while WPF has full support for Triggers, using Triggers in Silverlight requires further steps. We need to add to our solution a reference to the System.Windows.Interactivity.dll assembly. It's part of the Expression Blend SDK. Triggers in XAML are very different from Triggers in WPF. To use them, we need to create a TriggerAction<T> derived class, where T is the type of the object to which the Trigger applies. Then we use it in our XAML code. We can rewrite Listing 12 as shown in Listing 15.
Listing 15Silverlight's Trigger usageXAML.
<UserControl ... ... xmlns:i="clr-namespace:System.Windows.Interactivity; assembly=System.Windows.Interactivity "> <Button Content="Click this Button !"> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseMove"> <local:MouseMoveTriggerAction /> </i:EventTrigger> <i:EventTrigger EventName="MouseLeave"> <local:MouseLeaveTriggerAction /> </i:EventTrigger> </i:Interaction.Triggers> </Button>
We've associated two Trigger conditions to the Button. The first is executed when the mouse pointer is over the Button, and the second when it leaves the Button. We need to manage the two cases (MouseMove and MouseLeave), because Triggers in Silverlight don't benefit from synchronization. The MouseMoveTriggerAction and MouseLeaveTriggerAction are defined as shown in Listing 16.
Listing 16Silverlight's Trigger usagecode behind.
public class MouseMoveTriggerAction : TriggerAction<Control> { protected override void Invoke(object parameter) { base.AssociatedObject.Foreground = new SolidColorBrush(Colors.Blue); } } public class MouseLeaveTriggerAction : TriggerAction<Control> { protected override void Invoke(object parameter) { base.AssociatedObject.Foreground = new SolidColorBrush(SystemColors.ControlTextColor); } }
They both derive from the TriggerAction<Control> class. We've used Control instead of Button because the property we need, Foreground, is defined inside the Control class. In this way, we're able to use our MouseMoveTriggerAction and MouseLeaveTriggerAction in all the controls inside our solution, not just the Buttons. TriggerAction<T> defines an AssociatedObject property, which is the property to which the Trigger applies; in our case, the Button. We've set its Foreground property as needed.
As you can see, even if Triggers in Silverlight are not as powerful as those in WPF, with their help, we're able to separate the XAML code from the code behind. We need only to define the appropriate TriggerAction<T> derived class in a suitable place inside our solution.
Summary
I've introduced you briefly to what WPF and Silverlight are and how they differ from ASP.NET or WinForm programming. In my opinion, not only is XAML a new way to present data to users, but it involves new architectural concepts like those expressed by MVVM. This change influences from the ground up not only the visual part of the application, but the way in which the application has to be developed in its entirety. The transition to these new models isn't always easy; however, after a little experimentation, the real advantages of XAML-based applications become evident. I hope that this article will help to make this transition more enjoyable for you.