![]() |
Platforms, Frameworks & Libraries »
Windows Presentation Foundation »
General
Beginner
License: The Code Project Open License (CPOL)
WPF : A Beginners guide part 3 of nBy Sacha BarberAn introduction into RoutedEvents / RoutedCommands |
C# (C#3.0), .NET (.NET3.0, .NET3.5), WPF, Architect
|
||||||||||
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
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 orginal 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.
This article is the 3rd in my series of beginners articles for WPF. In this article we will discuss 2 important parts of WPF development, RoutedEvents and RoutedCommands. The proposed schedule for this series will still be roughly as follows:
In this article I'm aiming to cover is a brief introduction into the following:
private System.Web.Forms.Button button1;
button1.click+=new EventHandler(button1_Click);
...
private void button1_Click(object sender, EventArge e)
{
//Click Event seen, so something about it
}
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. Visual UI Elements), to the root element RoutedEvent. This travesal 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. It is fairly common for one logical event to be represented by 2 actual events, one Tunnelling and one Bubbling. There is a naming convention in place to aid in determining how these events were created. Tunnelling events usually start with the "PreviewXXX" and Bubbling events are usually just XXX. For example PreviewKeyDown (Tunnelling) and KeyDown (Bubbling).
This little demo application, should give you a better understanding of how routed events work. But before I show you a few more screen shots, lets just familiarise ourselves with the applications XAML code, as this is a faily 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 Tunnelling and Bubbling RoutedEvents
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
{
/// <summary>
/// Demo application that displays some data about the events
/// that were recieved by a users actions. Which allows users
/// to see the difference between tunneling/routed events
/// </summary>
public partial class Window1 : Window
{
#region Ctor
/// <summary>
/// Wires up several of the standard <see cref="FrameworkElement">
/// FrameworkElement</see> Tunneling/Bubbling <see cref="RoutedEvent">RoutedEvents</see>.
/// This demo also displays some data about the events that were recieved by
/// a users actions.
/// </summary>
public Window1()
{
InitializeComponent();
UIElement[] els = { this, gridMain, btnTop, lvResults };
foreach (UIElement el in els)
{
//keyboard
el.PreviewKeyDown += GenericHandler;
el.PreviewKeyUp += GenericHandler;
el.PreviewTextInput += GenericHandler;
el.KeyDown += GenericHandler;
el.KeyUp += GenericHandler;
el.TextInput += GenericHandler;
//Mouse
el.MouseDown += GenericHandler;
el.MouseUp += GenericHandler;
el.PreviewMouseDown += GenericHandler;
el.PreviewMouseUp += GenericHandler;
//Stylus
el.StylusDown += GenericHandler;
el.StylusUp += GenericHandler;
el.PreviewStylusDown += GenericHandler;
el.PreviewStylusUp += GenericHandler;
el.AddHandler(Button.ClickEvent, new RoutedEventHandler(GenericHandler));
}
}
#endregion
#region Private Methods
/// <summary>
/// Creates a new <see cref="EventDemoClass">EventDemoClass</see>
/// to represent the <see cref="RoutedEvent">RoutedEvent</see>.
/// And adds this new EventDemoClass to the listbox
/// </summary>
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)
});
}
/// <summary>
/// Returns the type name without the namespace portion
/// </summary>
private string typeWithoutNamespace(object obj)
{
string[] astr = obj.GetType().ToString().Split('.');
return astr[astr.Length - 1];
}
/// <summary>
/// Clears the listbox of events
/// </summary>
private void btnClearItems_Click(object sender, RoutedEventArgs e)
{
lvResults.Items.Clear();
}
#endregion
}
#region EventDemoClass CLASS
/// <summary>
/// A simpy data class that is used to display event data
/// </summary>
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 heres 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
''' <summary>
''' Demo application that displays some data about the events
''' that were recieved by a users actions. Which allows users
''' to see the difference between tunneling/routed events
''' </summary>
Partial Public Class Window1
Inherits Window
#Region "Ctor"
''' <summary>
''' Wires up several of the standard <see cref="FrameworkElement">
''' FrameworkElement</see> Tunneling/Bubbling <see cref="RoutedEvent">RoutedEvents</see>.
''' This demo also displays some data about the events that were recieved by
''' a users actions.
''' </summary>
Public Sub New()
InitializeComponent()
Dim els As UIElement() = {Me, gridMain, btnTop, lvResults}
For Each el As UIElement In els
'key board
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
'mouse
AddHandler el.MouseDown, AddressOf GenericHandler
AddHandler el.MouseUp, AddressOf GenericHandler
AddHandler el.PreviewMouseDown, AddressOf GenericHandler
AddHandler el.PreviewMouseUp, AddressOf GenericHandler
'stylus
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"
''' <summary>
''' Creates a new <see cref="EventDemoClass">EventDemoClass</see>
''' to represent the <see cref="RoutedEvent">RoutedEvent</see>.
''' And adds this new EventDemoClass to the listbox
''' </summary>
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
''' <summary>
''' Returns the type name without the namespace portion
''' </summary>
Private Function typeWithoutNamespace(ByVal obj As Object) As String
Dim astr As String() = obj.GetType().ToString().Split(".")
Return astr(astr.Length - 1)
End Function
''' <summary>
''' Clears the listbox of events
''' </summary>
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"
''' <summary>
''' A simpy data class that is used to display event data
''' </summary>
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 screen shots 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 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 who in tun is a sibling of the Window1
RoutedEvents traverse the Visual Tree.
RoutedEvents 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 code behind:
Button btn = new Button();
btn.Click += new RoutedEventHandler(btn_Click);
....
....
....
void btn_Click(object sender, RoutedEventArgs e)
{
//seen event do something
}
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 anonomous delegates. )
Button btn = new Button();
btn.Click += delegate(object sender, RoutedEventArgs e)
{
//seen event do something
};
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))
RoutedEvent that uses standard RoutedEventArgs 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 2 events that Window1 subscribes to. We just discussed the different manners in which to subscribe to RoutedEvents, so I wont 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.
//The actual event routing
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 RoutedEvent declaration we tell the EventManager, what sort of event strategy we will be using for the event "Tunnelling","Bubbling" or "Direct", as previously discussed. And we must also specify the owning Type that created he event, along with some other meta data. Next we must create a handlers section (this should be the only code here, dont add any more) for the actual event, which will be used when subscribers hook/unhook to the RoutedEvent
//add remove handlers
public event RoutedEventHandler CustomClick
{
add { AddHandler(CustomClickEvent, value); }
remove { RemoveHandler(CustomClickEvent, value); }
}
And here is the VB .NET version
'using standard event args
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 (VB code will be different so please see the attached project) :
//raise our custom CustomClickEvent event
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.
//The actual event routing
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))
Whats 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 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
//add remove handlers
public event CustomClickWithCustomArgsEventHandler CustomClickWithCustomArgs
{
add { AddHandler(CustomClickWithCustomArgsEvent, value); }
remove { RemoveHandler(CustomClickWithCustomArgsEvent, value); }
}
And here is the VB .NET version
'using custom event args
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) :
//raise our custom CustomClickWithCustomArgs event
CustomEventArgs args = new CustomEventArgs(CustomClickWithCustomArgsEvent, ++clickedCount);
RaiseEvent(args);
Where we are using these custom RoutedEventArgs for our CustomClickWithCustomArgsEvent RoutedEvent
/// <summary>
/// CustomEventArgs : a custom event argument class
/// which simply holds an int value representing
/// how many times the associated event has been fired
/// </summary>
public class CustomEventArgs : RoutedEventArgs
{
#region Instance fields
public int SomeNumber { get; private set; }
#endregion
#region Ctor
/// <summary>
/// Constructs a new CustomEventArgs object
/// using the parameters provided
/// </summary>
/// <param name="someNumber">the value for the events args</param>
public CustomEventArgs(RoutedEvent routedEvent,
int someNumber)
: base(routedEvent)
{
this.SomeNumber = someNumber;
}
#endregion
}
And here is the VB .NET versin
'Me.RaiseCustomClickWithCustomArgsEvent()
'raise our custom CustomClickWithCustomArgs event
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
''' <summary>
''' CustomEventArgs : a custom event argument class
''' which simply holds an int value representing
''' how many times the associated event has been fired
''' </summary>
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"
''' <summary>
''' Constructs a new CustomEventArgs object
'''using the parameters provided
'''</summary>
'''<param name="someNumber">the value for the events args</param>
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 RoutedEvents. I hope it gave you an inkling as to how they work.
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 :
Using these inbuilt Commands, it is possible to construct some pretty elaborate functionality, with absolutely no procedural code at all. For example lets 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, its 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 Buttons, provide a Command, such as Cut. And thats enough to get the Cut functionality with the currently foccussed item. This is what this small demo application looks like
Well thats 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.
As previously stated Commands also build apon the Routed strategy that we saw with RoutedEvents, so this means we can seperate out the command declaraion 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 RoutedCommands. 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 RoutedCommands. To better understand this demo project, lets consider the following diagram
As can be seen from this diagram, there are 3 different classes involved. So lets have a look at some code for each of these 3 classes now shall we. Step1 : 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; //for the
namespace Part3_Using_Our_Own_Commands
{
/// <summary>
/// Declare a new <see cref="RoutedCommand">RoutedCommand</see> that
/// is used by the <see cref="Window1">Window1</see> class where the
/// Command bindings and Command Sink events are declared. The Actual
/// Comman is used on a Button within the <see cref="UserControlThatUsesCustomCommand">
/// UserControlThatUsesCustomCommand</see> UserControl
/// </summary>
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
''' <summary>
''' Declare a new <see cref="RoutedCommand">RoutedCommand</see> that
''' is used by the <see cref="Window1">Window1</see> class where the
''' Command bindings and Command Sink events are declared. The Actual
''' Comman is used on a Button within the <see cref="UserControlThatUsesCustomCommand">
''' UserControlThatUsesCustomCommand</see> UserControl
''' </summary>
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
RoutedCommands are normally declared as static, again you should check the MSDN for other contructor overloads, as this only one of several constructors.
Step2 : 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. Lets 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 detremine if it is allowed to execute, and what to do when it does execute. This is done by the use of 2 routed events, CanExecute and Executed which in this case are wied to 2 code behind methods. Lets 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 2 events are enough to enable/disable the UIElement that is using this RoutedCommand. Lets 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 the Window1 Window. lets 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 event, but no Click RoutedEvent. So how does that work. Well as its using the same RoutedCommand that the Window1 set up CommandBindings for, when the Button is clicked the simpleCommand_Executed(..) method within Window1 is called. Simliarly if the TextBox "txtCantBeEmpty" is empty the Button will be disabled, thanks to the simpleCommand_CanExecute(..) method within Window1.
And thats how RoutedCommands work for us.
Recall earlier in our discussion about RoutedEvents, I stated that every FrameworkElement exposes a RaiseEvent() method. Now imagine that occasionally we would also like to be able to raise a buttons click event programatically. Now 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 Buttons PerformClick() method.
But now that 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 used 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 Buttons PerformClick() method in WinForms) and that is by the use of the 2 namespaces and one assembly reference. Namely :
Namespaces
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 right here. Though god knows wher he found enough information to right that original post, there aint much out there folks.
I have changed Joshs 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 it's 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 programatically 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 obscur. 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, thogh I urge you to explore this further, as its quite strange stuff.
These will have already been discussed in the text above, but just a quick recap.
There are 4 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
RoutedEvents, with some standard RoutedEventArgs and custom RoutedEventArgs ApplicationCommands RoutedCommands 29/01/08
General
News
Question
Answer
Joke
Rant
Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads.
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 11 Mar 2008 Editor: |
Copyright 2008 by Sacha Barber Everything else Copyright © CodeProject, 1999-2010 Web22 | Advertise on the Code Project |