Preface and Thanks
I am a .NET programmer, but a busy one; I do VB.NET and C#, ASP.NET / WinForms / WPF / WCF, Flash, Silverlight, the lot. Basically, I keep my toe in. But when I started writing this article series, I naturally chose my favourite language (which happens to be C#). I since got an email from an individual who requested that I publish this series with source code in VB.NET and C#. I simply stated I didn't have time. So this individual (Robert Ranck) volunteered to help out and do the translation to VB.NET based on my original C# projects.
So for that and the subsequent VB.NET projects that you will find here, I ask you to thank Robert Ranck. Cheers Robert, your contributions will surely make this series more open to all .NET developers.
Introduction
This article is the third in my series of beginner's articles for WPF. In this article, we will discuss two important parts of WPF development, RoutedEvent
s and RoutedCommand
s. The proposed schedule for this series will still be roughly as follows:
In this article, I'm aiming to cover a brief introduction into the following:
RoutedEvent
s; what they are, how they work, how to consume/create them
RoutedCommand
s; what they are, how they work, how to consume/create them
- Automation Peers
- Demo Applications
RoutedEvents
What they are
Routed events are a new concept to most developers. In good old fashioned .NET 1.x/2.0, we all probably used some custom events or hooked some delegates onto some existing event, such as:
private System.Web.Forms.Button button1;
button1.click+=new EventHandler(button1_Click);
...
private void button1_Click(object sender, EventArge e)
{
}
This is all well and good, right? The System.Web.Forms.Button
exposes an OnClick
event, which we subscribe to using a standard EventHandler delegate, and we receive the event when the System.Web.Forms.Button
raises its internal OnClick
event. From here on in, this type of event subscription/notification will be referred to as CLR events. In WPF, things are a little different. There are three types of event notification methods, namely:
- Bubbling: Allows events to bubble up the VisualTree (the tree of
Visual
UI Elements), to the root element.
- Tunneling: Allows events to tunnel down the VisualTree.
- Direct: Works just like the old .NET 1.x/2.0 CLR events. Only the subscriber gets to see the event.
What actually happens is that when an event is raised, it travels up and down the VisualTree, invoking any handlers that are subscribed to the RoutedEvent
. This traversal of the VisualTree will not be the entire tree, but rather the portion of the tree that is directly related to the element that raised the event. Josh smith has an excellent blog entry about this, which you can read right here, should you prefer to look at other resources.
It is fairly common for one logical event to be represented by two actual events, one Tunneling and one Bubbling. There is a naming convention in place to aid in determining how these events were created. Tunneling events usually start with "PreviewXXX" and Bubbling events are usually just XXX. For example, PreviewKeyDown
(Tunneling) and KeyDown
(Bubbling).
How they work
In order to understand them further, I have provided as part of the overall demo solution (at the top of this article) a project entitled "Part3_RoutedEventViewer" which is a standard WPF application, that when run can be used to examine WPF commands. When the application is launched, it should resemble something like the following:
This little demo application should give you a better understanding of how routed events work. But before I show you a few more screenshots, let's just familiarise ourselves with the application's XAML code, as this is fairly important before I can discuss the routed events, that you'll see.
<Window x:Class="Part3_RoutedEventViewer.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Examining Routed Events"
Height="300" Width="300"
WindowState="Maximized">
<Grid x:Name="gridMain">
<Grid.Resources>
//Ommiited for clarity
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="100*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Left">
<Button x:Name="btnTop" Margin="10" Padding="2"
Content="Examining Routed Events" Height="auto"/>
<Button x:Name="btnClearItems" Margin="10" Padding="2"
Content="Clear Items" Height="auto"
Click="btnClearItems_Click"/>
</StackPanel>
<ListView x:Name="lvResults" Margin="0,0,0,0"
IsSynchronizedWithCurrentItem="True" Grid.Row="2" >
<ListView.View>
<GridView ColumnHeaderContainerStyle=
"{StaticResource headerContainerStyle}" >
<GridViewColumn Header="RoutedEventName" Width="150"
CellTemplate="{StaticResource RoutedEventNameTemplate}"/>
<GridViewColumn Header="SenderName" Width="100"
CellTemplate="{StaticResource SenderNameTemplate}"/>
<GridViewColumn Header="ArgsSource" Width="100"
CellTemplate="{StaticResource ArgsSourceTemplate}"/>
<GridViewColumn Header="OriginalSource" Width="100"
CellTemplate="{StaticResource OriginalSourceTemplate}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
And the C# code-behind is very simple; it basically just subscribes to a whole bunch of Tunneling and Bubbling RoutedEvent
s.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Part3_RoutedEventViewer
{
public partial class Window1 : Window
{
#region Ctor
public Window1()
{
InitializeComponent();
UIElement[] els = { this, gridMain, btnTop, lvResults };
foreach (UIElement el in els)
{
el.PreviewKeyDown += GenericHandler;
el.PreviewKeyUp += GenericHandler;
el.PreviewTextInput += GenericHandler;
el.KeyDown += GenericHandler;
el.KeyUp += GenericHandler;
el.TextInput += GenericHandler;
el.MouseDown += GenericHandler;
el.MouseUp += GenericHandler;
el.PreviewMouseDown += GenericHandler;
el.PreviewMouseUp += GenericHandler;
el.StylusDown += GenericHandler;
el.StylusUp += GenericHandler;
el.PreviewStylusDown += GenericHandler;
el.PreviewStylusUp += GenericHandler;
el.AddHandler(Button.ClickEvent,
new RoutedEventHandler(GenericHandler));
}
}
#endregion
#region Private Methods
private void GenericHandler(object sender, RoutedEventArgs e)
{
lvResults.Items.Add(new EventDemoClass()
{
RoutedEventName = e.RoutedEvent.Name,
SenderName = typeWithoutNamespace(sender),
ArgsSource = typeWithoutNamespace(e.Source),
OriginalSource = typeWithoutNamespace(e.OriginalSource)
});
}
private string typeWithoutNamespace(object obj)
{
string[] astr = obj.GetType().ToString().Split('.');
return astr[astr.Length - 1];
}
private void btnClearItems_Click(object sender, RoutedEventArgs e)
{
lvResults.Items.Clear();
}
#endregion
}
#region EventDemoClass CLASS
public class EventDemoClass
{
public string RoutedEventName { get; set; }
public string SenderName { get; set; }
public string ArgsSource { get; set; }
public string OriginalSource { get; set; }
}
#endregion
}
And here's the VB.NET version:
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Data
Imports System.Windows.Documents
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Imaging
Imports System.Windows.Navigation
Imports System.Windows.Shapes
Partial Public Class Window1
Inherits Window
#Region "Ctor"
Public Sub New()
InitializeComponent()
Dim els As UIElement() = {Me, gridMain, btnTop, lvResults}
For Each el As UIElement In els
AddHandler el.PreviewKeyDown, AddressOf GenericHandler
AddHandler el.PreviewKeyUp, AddressOf GenericHandler
AddHandler el.PreviewTextInput, AddressOf GenericHandler
AddHandler el.KeyDown, AddressOf GenericHandler
AddHandler el.KeyUp, AddressOf GenericHandler
AddHandler el.TextInput, AddressOf GenericHandler
AddHandler el.MouseDown, AddressOf GenericHandler
AddHandler el.MouseUp, AddressOf GenericHandler
AddHandler el.PreviewMouseDown, AddressOf GenericHandler
AddHandler el.PreviewMouseUp, AddressOf GenericHandler
AddHandler el.StylusDown, AddressOf GenericHandler
AddHandler el.StylusUp, AddressOf GenericHandler
AddHandler el.PreviewStylusDown, AddressOf GenericHandler
AddHandler el.PreviewStylusUp, AddressOf GenericHandler
el.AddHandler(Button.ClickEvent, _
New RoutedEventHandler(AddressOf GenericHandler))
Next
End Sub
#End Region
#Region "Private Methods"
Private Sub GenericHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
Dim eventClass As New EventDemoClass()
eventClass.RoutedEventName = e.RoutedEvent.Name
eventClass.SenderName = typeWithoutNamespace(sender)
eventClass.ArgsSource = typeWithoutNamespace(e.Source)
eventClass.OriginalSource = typeWithoutNamespace(e.OriginalSource)
lvResults.Items.Add(eventClass)
End Sub
Private Function typeWithoutNamespace(ByVal obj As Object) As String
Dim astr As String() = obj.GetType().ToString().Split(".")
Return astr(astr.Length - 1)
End Function
Private Sub btnClearItems_Click(ByVal sender As System.Object, _
ByVal e As System.Windows.RoutedEventArgs)
lvResults.Items.Clear()
End Sub
#End Region
End Class
#Region "EventDemoClass CLASS"
Public Class EventDemoClass
#Region "Instance Fields"
Private newRoutedEventName As String
Private newSenderName As String
Private newArgsSource As String
Private newOriginalSource As String
#End Region
#Region "Propeties"
Public Property RoutedEventName() As String
Get
Return newRoutedEventName
End Get
Set(ByVal value As String)
newRoutedEventName = value
End Set
End Property
Public Property SenderName() As String
Get
Return newSenderName
End Get
Set(ByVal value As String)
newSenderName = value
End Set
End Property
Public Property ArgsSource() As String
Get
Return newArgsSource
End Get
Set(ByVal value As String)
newArgsSource = value
End Set
End Property
Public Property OriginalSource() As String
Get
Return newOriginalSource
End Get
Set(ByVal value As String)
newOriginalSource = value
End Set
End Property
#End Region
End Class
#End Region
It can be seen that this Window1.xaml has a VisualTree, which is as follows:
So with this in mind, let me now show you some example screenshots that were generated using this demo project "Part3_RoutedEventViewer".
If we click the Window, we can observe the following events are seen. We only see Window level events; this is due to the Window
being the root element in the Visual Tree.
But if we click the actual button (on the left), we can observe the following events are seen, as the Button
is a sibling of the Grid
, which in turn is a sibling of Window1
.
I hope this helps you better understand how RoutedEvent
s traverse the Visual Tree.
How to consume them
Consuming RoutedEvent
s is the same as it always was; we can do it in XAML like:
<Button x:Name="btnClearItems"
Content="Clear Items" Click="btnClearItems_Click"/>
Of course, you need to have a section of code entitled "btnClearItems_Click
" in the code behind, so that the event handler has an actual delegate to a real method.
Alternatively, you can simply subscribe to a RoutedEvent
as follows in the code-behind:
Button btn = new Button();
btn.Click += new RoutedEventHandler(btn_Click);
....
....
....
void btn_Click(object sender, RoutedEventArgs e)
{
}
And the VB.NET version:
btn.AddHandler(Button.ClickEvent, New RoutedEventHandler(AddressOf btn_Click))
.....
.....
.....
Private Sub GenericHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
//seen event do something
End Sub
Or even like this (if you like anonymous delegates):
Button btn = new Button();
btn.Click += delegate(object sender, RoutedEventArgs e)
{
};
And we can even add directly to the handlers of a UI Element, such as:
Button btn = new Button();
btn.AddHandler(Button.ClickEvent, new RoutedEventHandler(GenericHandler));
Or in VB.NET:
btn.AddHandler(Button.ClickEvent, New RoutedEventHandler(AddressOf GenericHandler))
How to create them
I have attached another demo project (entitled "Part3_RoutedEventsExample") which is part of the overall solution at the top of this article. Within this project, there are two things covered.
- How to create and consume a
RoutedEvent
that uses standard RoutedEventArgs
- How to create and consume a
RoutedEvent
that uses custom RoutedEventArgs
When this application is run, it looks like the following figure:
Where there is a single UserControl
placed Window1
. The UserControl
is responsible for raising two events that Window1
subscribes to. We just discussed the different manners in which to subscribe to RoutedEvent
s, so I won't dwell on that, but shall instead show you how to raise your own RoutedEvent
.
Firstly using standard RoutedEventArgs
The first step is to create and register the event with the EventManager
, which is done like this. Note that this is one possible constructor for a RoutedEvent
; you should consult the MSDN documentation for others.
public static readonly RoutedEvent CustomClickEvent =
EventManager.RegisterRoutedEvent(
"CustomClick", RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(UserControlThatCreatesEvent));
And here is the VB.NET version:
Public Shared ReadOnly CustomClickEvent As RoutedEvent = _
EventManager.RegisterRoutedEvent("CustomClick", _
RoutingStrategy.Bubble, GetType(RoutedEventHandler), _
GetType(UserControlThatCreatesEvent))
What is important is that in the RoutedEvent
declaration, we tell the EventManager
what sort of event strategy we will be using for the event "Tunneling", "Bubbling", or "Direct", as previously discussed. And we must also specify the owning Type
that created the event, along with some other meta data. Next, we must create a handlers section (this should be the only code here, don't add any more) for the actual event, which will be used when subscribers hook/unhook to the RoutedEvent
.
public event RoutedEventHandler CustomClick
{
add { AddHandler(CustomClickEvent, value); }
remove { RemoveHandler(CustomClickEvent, value); }
}
And here is the VB.NET version:
Public Custom Event CustomClick As RoutedEventHandler
AddHandler(ByVal value As RoutedEventHandler)
Me.AddHandler(CustomClickEvent, value)
End AddHandler
RemoveHandler(ByVal value As RoutedEventHandler)
Me.RemoveHandler(CustomClickEvent, value)
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As RoutedEventArgs)
Me.RaiseEvent(e)
End RaiseEvent
End Event
And finally, we must raise the event; this is achieved as follows (the VB code will be different, so please see the attached project):
RoutedEventArgs args = new RoutedEventArgs(CustomClickEvent);
RaiseEvent(args);
And here is the VB.NET version:
Dim args As New RoutedEventArgs(CustomClickEvent)
MyBase.RaiseEvent(args)
It can be seen that we use the RaiseEvent()
method to raise the actual RoutedEvent
. Every FrameworkElement
exposes this method, that can be used for raising any RoutedEvent
. But as we will find out later, we may not always want to use this; but more on this later.
Second using custom RoutedEventArgs
As before, we need to register the event with the EventManager
, which is done like this (VB code will be different, so please see the attached project). As before, this is one possible constructor for a RoutedEvent
; you should consult the MSDN documentation for others.
public static readonly RoutedEvent CustomClickWithCustomArgsEvent =
EventManager.RegisterRoutedEvent(
"CustomClickWithCustomArgs", RoutingStrategy.Bubble,
typeof(CustomClickWithCustomArgsEventHandler),
typeof(UserControlThatCreatesEvent));
And here is the VB.NET version:
Public Shared ReadOnly CustomClickWithCustomArgsEvent _
As RoutedEvent = EventManager.RegisterRoutedEvent("CustomClickWithCustomArgs", _
RoutingStrategy.Bubble, GetType(CustomClickWithCustomArgsEventHandler), _
GetType(UserControlThatCreatesEvent))
What's important this time is that we specify a new delegate type for the event handler; in this case, this is CustomClickWithCustomArgsEventHandler
, which is declared in the code-behind as follows:
public delegate void CustomClickWithCustomArgsEventHandler(object sender, _
CustomEventArgs e);
And here is the VB.NET version:
Public Delegate Sub CustomClickWithCustomArgsEventHandler(ByVal sender _
As Object, ByVal e As CustomEventArgs)
And as before, we must declare the event handler section, such that subscribers to the RoutedEvent
are added and removed as requested.
public event CustomClickWithCustomArgsEventHandler CustomClickWithCustomArgs
{
add { AddHandler(CustomClickWithCustomArgsEvent, value); }
remove { RemoveHandler(CustomClickWithCustomArgsEvent, value); }
}
And here is the VB.NET version:
Public Custom Event CustomClickWithCustomArgs As CustomClickWithCustomArgsEventHandler
AddHandler(ByVal value As CustomClickWithCustomArgsEventHandler)
Me.AddHandler(CustomClickWithCustomArgsEvent, value)
End AddHandler
RemoveHandler(ByVal value As CustomClickWithCustomArgsEventHandler)
Me.RemoveHandler(CustomClickWithCustomArgsEvent, value)
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As CustomEventArgs)
Me.RaiseEvent(e)
End RaiseEvent
End Event
And finally, we must raise the event using our custom RoutedEventArgs
. This is achieved as follows (VB code will be different, so please see the attached project):
CustomEventArgs args = _
new CustomEventArgs(CustomClickWithCustomArgsEvent, ++clickedCount);
RaiseEvent(args);
Where we are using these custom RoutedEventArgs
for our CustomClickWithCustomArgsEvent
RoutedEvent
.
public class CustomEventArgs : RoutedEventArgs
{
#region Instance fields
public int SomeNumber { get; private set; }
#endregion
#region Ctor
public CustomEventArgs(RoutedEvent routedEvent,
int someNumber)
: base(routedEvent)
{
this.SomeNumber = someNumber;
}
#endregion
}
And here is the VB.NET version:
ClickedCount = ClickedCount + 1
Dim args As New CustomEventArgs(CustomClickWithCustomArgsEvent, ClickedCount)
MyBase.RaiseEvent(args)
Where we are using these custom RoutedEventArgs
for our CustomClickWithCustomArgsEvent
RoutedEvent
:
Imports System
Imports System.Windows
Public Class CustomEventArgs
Inherits System.Windows.RoutedEventArgs
#Region "Instance Fields"
Private newSomeNumber As Integer
#End Region
#Region "Properties"
Public Property SomeNumber() As Integer
Get
Return newSomeNumber
End Get
Set(ByVal value As Integer)
newSomeNumber = value
End Set
End Property
#End Region
#Region "Ctor"
Public Sub New(ByVal routedEvent As System.Windows.RoutedEvent, _
ByVal someNumber As Integer)
MyBase.New(routedEvent)
Me.SomeNumber = someNumber
End Sub
#End Region
End Class
And that concludes my brief introduction/rant on RoutedEvent
s. I hope it gave you an inkling as to how they work.
RoutedCommands
What are Commands
The commanding system in WPF is based around the RoutedCommand
and the RoutedEvent
. What makes commands different from a simple event handler attached to a button or a timer is that commands separate the semantics and the originator of an action from its logic. This allows for multiple and disparate sources to invoke the same command logic, and allows the command logic to be customized for different targets. Examples of commands are the editing operations Copy, Cut, and Paste, which are found on many applications. The semantics of the command are consistent across applications and classes, but the logic of the action is specific to the particular object acted upon. The key combination CTRL+C invokes the Cut command in text classes, image classes, and web browsers, but the actual logic for performing the Cut operation is defined by the object or the application on which the cut is occurring, and not on the source that invoked the command. A text object may cut the selected text into the clipboard, while an image object may cut the selected image, but the same command source, a KeyGesture or a ToolBar Button, can be used to invoke the command on both classes. There are a number of existing commands that exist within the .NET 3.0/3.5 Frameworks that may be leveraged to perform commonly occurring tasks. For example, there are these commands already existing:
ApplicationCommands
, which contains things like Cut/Copy/Paste. A full list of the commands available within the ApplicationCommands
is available here.
MediaCommands
, which contains things like BoostBass/ChannelUp/ChannelDown/MuteVolume. A full list of the commands available within the MediaCommands
is available here.
NavigationCommands
, which contains things like BrowseBack/BrowseForward/Favorites. A full list of the commands available within the NavigationCommands
is available here.
ComponentCommands
, which contains things like MoveDown/MoveFocusPageUp/MoveToEnd. A full list of the commands available within the ComponentCommands
is available here.
EditingCommands
, which contains things like AlignCenter/Backspace/Delete. A full list of the commands available within the EditingCommands
is available here.
EditingCommands
, which contains things like AlignCenter/Backspace/Delete. A full list of the commands available within the EditingCommands
is available here.
Using these inbuilt Commands, it is possible to construct some pretty elaborate functionality, with absolutely no procedural code at all. For example, let's have a look at one of the projects supplied within the overall solution at the top of this article. This is a small project entitled "Part3_Using_Built_In_Commands" which simply uses the inbuilt EditingCommands
to create a small text editor with Cut/Paste/Copy/Undo/Redo functionality. Here is the code; this is all of it, it's just XAML:
<Window x:Class="Part3_Using_Built_In_Commands.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Simple use of Built In ApplicationCommands"
Height="500" Width="500"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen">
<StackPanel Orientation="Vertical" Width="auto">
<StackPanel Orientation="Horizontal"
Background="Gainsboro" Margin="10"
Height="40">
<Button Command="ApplicationCommands.Cut"
CommandTarget="{Binding ElementName=textBox}"
Margin="5,5,5,5" Content ="ApplicationCommands.Cut"/>
<Button Command="Copy"
CommandTarget="{Binding ElementName=textBox}"
Margin="5,5,5,5" Content="ApplicationCommands.Copy"/>
<Button Command="Paste"
CommandTarget="{Binding ElementName=textBox}"
Margin="5,5,5,5" Content="ApplicationCommands.Paste"/>
<Button Command="Undo"
CommandTarget="{Binding ElementName=textBox}"
Margin="5,5,5,5" Content="ApplicationCommands.Undo"/>
<Button Command="Redo"
CommandTarget="{Binding ElementName=textBox}"
Margin="5,5,5,5" Content="ApplicationCommands.Redo"/>
</StackPanel>
<TextBlock HorizontalAlignment="Left" Margin="5,5,5,5"
Text="This window demonstrates built in commands (standard ones),
with no procedual code at all......that's pretty neat I think.
Type into the text box and use the buttons provided to see
what it does" TextWrapping="Wrap" Height="auto"/>
<Label Content="Type in the textbox, maybe try selecting some text...
Watch the buttons become enabled"/>
<TextBox x:Name="textBox" HorizontalAlignment="Left"
Margin="5,5,5,5" MaxLines="60"
Height="300" Width="470"
Background="#FFF1FFB2"/>
</StackPanel>
</Window>
See how all we need to do is on the Button
s, provide a Command, such as Cut. And that's enough to get the Cut functionality with the currently focused item. This is what this small demo application looks like:
Well, that's pretty cool, right? I think so. But what about if we want to create our own commands? How do we go about doing that? What plumbing do we need to do? Well, it kind of goes like this.
- Declare a
RoutedCommand
- Create a
CommandBinding
for where the RoutedCommand
sinks are used
- Create and use
RoutedCommand
on some input control
As previously stated, Commands also build upon the Routed strategy that we saw with RoutedEvent
s, so this means we can separate out the command declaration from the CommandBinding/Command sinks from the input UIElement
that uses the Command. This is pretty cool when you think about it. This strategy means that we could author some pretty slick skinnable applications. As long as the UI contains a reference to the correct command and lives in the same window that has the CommandBinding/Command sinks, all the background logic will still work correctly. I'm not going to dwell on this too much, as I think this may end up being the subject of an article by Josh Smith, for his excellent Podder article series. So I don't want to steal his thunder.
OK, so as I've just stated, there are several steps required in creating and using your own RoutedCommand
s. To aid with this process, the solution at the top of this article contains a project entitled "Part3_Using_Our_Own_Commands", which contains an implementation of rolling out your own RoutedCommand
s. To better understand this demo project, let's consider the following diagram:
As can be seen from this diagram, there are three different classes involved. So let's have a look at some code for each of these three classes now, shall we?
Step 1: Declare a RoutedCommand
The first step is to define a RoutedCommand
. This is done as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
namespace Part3_Using_Our_Own_Commands
{
public class CustomCommands
{
#region Instance Fields
public static readonly RoutedCommand simpleCommand =
new RoutedCommand("simpleCommand",typeof(CustomCommands));
#endregion
}
}
And in VB.NET:
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows.Input
Public Class CustomCommands
Public Sub New()
End Sub
#Region "Instance Fields"
Public Shared ReadOnly simpleCommand
As New RoutedCommand("simpleCommand",
GetType(CustomCommands))
#End Region
End Class
RoutedCommand
s are normally declared as static; again, you should check the MSDN for other constructor overloads, as this is only one of several constructors.
Step 2: Create a CommandBinding, for where the RoutedCommand sinks are used
A CommandBinding
enables command handling of a specific command for this element, and declares the linkage between a command, its events, and the handlers attached by this element. This is typically done in XAML, though can also be done in code, but for this example, I'll be using XAML only. Let's see:
<Window.CommandBindings>
<CommandBinding Command="{x:Static local:CustomCommands.simpleCommand}"
CanExecute="simpleCommand_CanExecute"
Executed="simpleCommand_Executed"
/>
</Window.CommandBindings>
This CommandBinding
sets up the command sinks (events) that the RoutedCommand
will use to determine if it is allowed to execute, and what to do when it does execute. This is done by the use of two routed events, CanExecute
and Executed
, which in this case are wired to two code-behind methods. Let's see that.
private void simpleCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = !(string.IsNullOrEmpty(txtCantBeEmpty.Text));
}
private void simpleCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show(txtCantBeEmpty.Text);
}
And in VB.NET, we have the following:
Private Sub simpleCommand_CanExecute(ByVal sender As Object, _
ByVal e As CanExecuteRoutedEventArgs)
e.CanExecute = Not (String.IsNullOrEmpty(txtCantBeEmpty.Text))
End Sub
Private Sub simpleCommand_Executed(ByVal sender As Object, _
ByVal e As ExecutedRoutedEventArgs)
MessageBox.Show(txtCantBeEmpty.Text)
End Sub
These two events are enough to enable/disable the UIElement
that is using this RoutedCommand
. Let's have a look at the last step now, shall we? Which is to simply use the RoutedCommand
on some UIElement
. As the previous diagram shows, I am using a custom UserControl
called "UserControlThatUsesCustomCommand
", that is placed in Window1
. Let's see the section of code, shall we?
<UserControl x:Class="Part3_Using_Our_Own_Commands.UserControlThatUsesCustomCommand"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Part3_Using_Our_Own_Commands"
Height="auto" Width="auto">
<Button Margin="5,5,5,5"
Command="{x:Static local:CustomCommands.simpleCommand}"
Content="Click me if you can"/>
</UserControl>
So this Button
has the "simpleCommand
" RoutedCommand
associated with an event, but no Click RoutedEvent
. How does that work? Well, as it's using the same RoutedCommand
that the Window1
set up CommandBinding
s for, when the Button
is clicked, the simpleCommand_Executed(..)
method within Window1
is called. Similarly if the TextBox
"txtCantBeEmpty
" is empty, the Button
will be disabled, thanks to the simpleCommand_CanExecute(..)
method within Window1
.
And that's how RoutedCommand
s work for us.
Automation Peers
Recall earlier in our discussion about RoutedEvent
s, I stated that every FrameworkElement
exposes a RaiseEvent()
method. Now, imagine that occasionally we would also like to be able to raise a button's click event programmatically. As we talked about, we could simply raise a RoutedEvent
, such as a Button.Click
. In the old way of WinForms, we would simply call a Button
's PerformClick()
method.
But now you know that a Button
can actually have a RoutedCommand
associated with it, and may not have any code against the Click RoutedEvent
at all.
So in this case, if we use the RaiseEvent()
method of a Button
, this would not actually do anything with the attached RoutedCommand
. So we need to look for an alternative approach. Luckily (although somewhat obscure), .NET does provide a method of simulating a Button.Click
(just as we had the Button
's PerformClick()
method in WinForms), and that is by the use of the two namespaces and an assembly reference. Namely:
Namespaces
System.Windows.Automation.Peers
System.Windows.Automation.Provider
Assembly
The System.Windows.Automation.Peers
namespace has many automation peers, one of which is a ButtonAutomationPeer
which can be used to simulate a real button click. Josh Smith has an excellent blog entry about this. Though god knows where he found enough information to write that original post, there ain't much out there folks.
I have changed Josh's code slightly, to use the UIElementAutomationPeer.CreatePeerForElement
to return a generic AutomationPeer
. Where as Josh used a ButtonAutomationPeer
. Either way, what I want you to understand is that by using this automation peer stuff, we are able to properly simulate a button click occurring. So it doesn't matter if the button is using a RoutedCommand,
or a RoutedEvent
for its click, we will get the desired result. As by using this code, it will be the same as the actual button being clicked.
Here is a code snippet to illustrate how to programmatically click a button using the automation peers:
AutomationPeer peer = UIElementAutomationPeer.CreatePeerForElement(start);
IInvokeProvider invokeProv =
peer.GetPattern(PatternInterface.Invoke) as IInvokeProvider;
invokeProv.Invoke();
I did say it was fairly obscure. For example, what is this GetPattern
thing all about? Well, like I also said, there are many automation peers, all of which relate to different UI elements, so the PatternInterface
enumeration contains values that are used across these different UI elements. For example, a PatternInterface.Scroll
will most likely not be used with Button
, but rather a scrollable control. The image below shows all the possible values for the PatternInterface
enumeration:
And this image displays some of the available automation peers, though I urge you to explore this further, as it's quite strange stuff.
The Demo Apps
These will have already been discussed in the text above, but just a quick recap.
There are four demo applications included within the attached solution. I just thought I would go through what each of them does in a bit more detail so that you can explore them for yourself.
- Part3_RoutedEventViewer: A simple application, to allow you to view Tunneling/Bubbling events
- Part3_RoutedEventsExample: A simple application, which consumes our own custom
RoutedEvent
s, with some standard RoutedEventArg
s and custom RoutedEventArg
s
- Part3_Using_Built_In_Commands: A simple text editor which shows how to use the inbuilt
ApplicationCommand
s
- Part3_Using_Our_Own_Commands: A very simple application which demonstrates using custom
RoutedCommand
s
References
History
- 29/01/08 - First version of article.