Click here to Skip to main content
16,016,134 members
Articles / Desktop Programming / WPF

WPF: A Beginner's guide: Part 3 of n

Rate me:
Please Sign up or sign in to vote.
4.88/5 (124 votes)
11 Mar 2008CPOL15 min read 459.5K   6.3K   292   111
An introduction into RoutedEvents / RoutedCommands.

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, RoutedEvents and RoutedCommands. 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:

  • RoutedEvents; what they are, how they work, how to consume/create them
  • RoutedCommands; 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:

C#
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
}

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:

Image 1

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.

XML
<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 RoutedEvents.

C#
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 here's the VB.NET version:

VB
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"
 ublic 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:

Image 2

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.

Image 3

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.

Image 4

I hope this helps you better understand how RoutedEvents traverse the Visual Tree.

How to consume them

Consuming RoutedEvents is the same as it always was; we can do it in XAML like:

XML
<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:

C#
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:

VB
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):

C#
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:

C#
Button btn = new Button();
btn.AddHandler(Button.ClickEvent, new RoutedEventHandler(GenericHandler));

Or in VB.NET:

VB
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.

  1. How to create and consume a RoutedEvent that uses standard RoutedEventArgs
  2. How to create and consume a RoutedEvent that uses custom RoutedEventArgs

When this application is run, it looks like the following figure:

Image 5

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 RoutedEvents, 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.

C#
//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:

VB
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.

C#
//add remove handlers
public event RoutedEventHandler CustomClick
{
    add { AddHandler(CustomClickEvent, value); }
    remove { RemoveHandler(CustomClickEvent, value); }
}

And here is the VB.NET version:

VB
'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 (the VB code will be different, so please see the attached project):

C#
//raise our custom CustomClickEvent event
RoutedEventArgs args = new RoutedEventArgs(CustomClickEvent);
RaiseEvent(args);

And here is the VB.NET version:

VB
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.

C#
//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:

VB
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:

C#
public delegate void CustomClickWithCustomArgsEventHandler(object sender, _
                                                       CustomEventArgs e);

And here is the VB.NET version:

VB
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.

C#
//add remove handlers
public event CustomClickWithCustomArgsEventHandler CustomClickWithCustomArgs
{
    add { AddHandler(CustomClickWithCustomArgsEvent, value); }
    remove { RemoveHandler(CustomClickWithCustomArgsEvent, value); }
}

And here is the VB.NET version:

VB
'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):

C#
//raise our custom CustomClickWithCustomArgs event
CustomEventArgs args = _
  new CustomEventArgs(CustomClickWithCustomArgsEvent, ++clickedCount);
RaiseEvent(args);

Where we are using these custom RoutedEventArgs for our CustomClickWithCustomArgsEvent RoutedEvent.

C#
/// <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 version:

VB
'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:

VB
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.

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:

XML
<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 that's enough to get the Cut functionality with the currently focused item. This is what this small demo application looks like:

Image 6

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 RoutedEvents, 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 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, let's consider the following diagram:

Image 7

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:

C#
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:

VB
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 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:

XML
<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.

C#
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:

VB
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?

XML
<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 CommandBindings 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 RoutedCommands work for us.

Automation Peers

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 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
  • UIAutomationProvider

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:

C#
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:

Image 8

And this image displays some of the available automation peers, though I urge you to explore this further, as it's quite strange stuff.

Image 9

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 RoutedEvents, with some standard RoutedEventArgs and custom RoutedEventArgs
  • Part3_Using_Built_In_Commands: A simple text editor which shows how to use the inbuilt ApplicationCommands
  • Part3_Using_Our_Own_Commands: A very simple application which demonstrates using custom RoutedCommands

References

History

  • 29/01/08 - First version of article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
GeneralRe: Great introduction! Pin
Sacha Barber31-Jan-08 21:24
Sacha Barber31-Jan-08 21:24 
GeneralRe: Great introduction! Pin
Philipp Sumi31-Jan-08 22:19
Philipp Sumi31-Jan-08 22:19 
GeneralRe: Great introduction! Pin
Sacha Barber31-Jan-08 22:51
Sacha Barber31-Jan-08 22:51 
GeneralRe: Great introduction! Pin
Philipp Sumi31-Jan-08 23:04
Philipp Sumi31-Jan-08 23:04 
GeneralRe: Great introduction! Pin
Sacha Barber31-Jan-08 23:52
Sacha Barber31-Jan-08 23:52 
GeneralExcellent article Pin
Daniel Vaughan30-Jan-08 21:27
Daniel Vaughan30-Jan-08 21:27 
GeneralRe: Excellent article Pin
Sacha Barber30-Jan-08 22:24
Sacha Barber30-Jan-08 22:24 
GeneralNice article Pin
Steve Hansen30-Jan-08 4:47
Steve Hansen30-Jan-08 4:47 
Haven't finished the other ones either, you're going to fast! Big Grin | :-D
One suggestion, your get type name method: object.GetType().Name does the same Smile | :)
GeneralRe: Nice article Pin
Sacha Barber30-Jan-08 5:17
Sacha Barber30-Jan-08 5:17 
GeneralGreat article as usual, but... Pin
Seishin#30-Jan-08 3:30
Seishin#30-Jan-08 3:30 
GeneralRe: Great article as usual, but... Pin
Sacha Barber30-Jan-08 3:57
Sacha Barber30-Jan-08 3:57 
GeneralIncorrect Comment About Routing Strategy Pin
User 27100930-Jan-08 10:10
User 27100930-Jan-08 10:10 
GeneralRe: Incorrect Comment About Routing Strategy Pin
Seishin#30-Jan-08 10:13
Seishin#30-Jan-08 10:13 
GeneralRe: Incorrect Comment About Routing Strategy Pin
Sacha Barber30-Jan-08 11:12
Sacha Barber30-Jan-08 11:12 
Generaloh man! Pin
TheCardinal30-Jan-08 2:07
TheCardinal30-Jan-08 2:07 
GeneralRe: oh man! Pin
Sacha Barber30-Jan-08 2:11
Sacha Barber30-Jan-08 2:11 
Generalyour articles ROCK!!! Pin
quicoli30-Jan-08 1:59
quicoli30-Jan-08 1:59 
GeneralRe: your articles ROCK!!! Pin
Sacha Barber30-Jan-08 2:11
Sacha Barber30-Jan-08 2:11 
GeneralLooking Good! Pin
Marc Clifton30-Jan-08 1:19
mvaMarc Clifton30-Jan-08 1:19 
GeneralRe: Looking Good! Pin
Sacha Barber30-Jan-08 1:31
Sacha Barber30-Jan-08 1:31 
GeneralRe: Looking Good! Pin
Marc Clifton30-Jan-08 2:03
mvaMarc Clifton30-Jan-08 2:03 
GeneralRe: Looking Good! Pin
Sacha Barber30-Jan-08 2:10
Sacha Barber30-Jan-08 2:10 
GeneralRe: Looking Good! Pin
FatGeek30-Jan-08 3:30
professionalFatGeek30-Jan-08 3:30 
GeneralRe: Looking Good! Pin
Sacha Barber30-Jan-08 4:04
Sacha Barber30-Jan-08 4:04 
GeneralRe: Looking Good! Pin
Sacha Barber12-Feb-08 11:01
Sacha Barber12-Feb-08 11:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.