WPF Sample Series – EventManager RegisterClassHandler

This is the next sample in the WPF Sample Applications Series. The purpose of the Sample Series is to provide concise code solutions for specific programming tasks. This sample provides a brief description of the problem, the solution and full source code.

I have been working on several WPF applications in parallel for some time now.  All of my business applications have one main window.  These applications also have the ability to display a modal dialog box when required.  For example displaying the About Dialog Box or to allow the user to interact with the currently displayed task in some modal way, like looking up a value in a related table or editing values in related tables.  Very common tasks in a business application.

As I was testing my application I noticed a very strange behavior in the application for the first time.  Code was running in the main window, but the modal window was the window raising the RoutedEvent.  I had always read and been taught that RoutedEvents tunnel and bubble in the current window and not other windows.  That is true, except when the RoutedEventHandler is set up using EventManager.RegisterClassHandler. 

If WPF RoutedEvents are new to you or you feel you need a short review, please check out the RoutedEvent Viewer on my blog.  It provides an interactive Routed Event Viewer application for understanding WPF Routed Events.  An understanding of RoutedEvents and the concept of events being handled is important for this short demonstration.  Additionally I recommend reading the Routed Events Overview in the MSDN documentation.

Let’s get into this.

Test Bench UI

sample1 

The UI is very straightforward.  In the above image, I have opened the included sample program and clicked the Click Me button. 

When the two buttons are clicked, the code handlers respond by displaying a message.  In the case of the New Window button being clicked, a new instance of Window2 will be created and displayed.  The CheckBox when Checked, will mark any events handled by Class Handler as Handled.

The Message: #, provides a simple way of viewing the order in which the event handlers were called by WPF.  As you use this Test Bench UI, keep track of the Message #’s to understand what is going on.

sample2 

In the above image, the New Window button was clicked.  You can see the order in which the handlers were called and a new instance of Window2 was created and displayed.

sample3

Now things get VERY interesting.  The Window 2 Button Clicked button, was clicked and handled in Window1.  Notice Message 6.  This is what caught me off guard and the purpose of this blog post.  The Class handler is assigned to handle all ClickEvents owned by the Button class.  Have a look at the below code EventManager.RegisterClassHandler line.  This line of code allows the Window1 class to monitor Button.ClickEvents in its own window and other windows in the application.  This is a great feature, unless you don’t expect it to work this way.

Private Sub Window1_Loaded(ByVal sender As Object, _
            ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

    'best fully understand WPF when using this
    EventManager.RegisterClassHandler(GetType(Button), _
        Button.ClickEvent, New RoutedEventHandler(AddressOf WatchOutHandler))

    'notice that the CheckBox's Click event will be captured with
    'this event handler because the CheckBox gets its ClickEvent
    'from ButtonBase and not the CheckBox class.
    Me.AddHandler(Button.ClickEvent, _
        New RoutedEventHandler(AddressOf WindowSafeHandler))

End Sub

sample4

The above Me.AddHandler statement goes along with the above image.  The comments above the Me.AddHandler statement give the big picture.  Notice that after clicking the CheckBox, Message 7 is displayed for the Window Handler, but not the Class Handler.  Many WPF programmers like to handler RoutedEvents at a parent container level since it just makes sense to do this.  This little application teaches us, to be on the look out for other controls that derive from the same base class that can raise the event we are watching for.  So as programmers, we should not just assume that if we are listening for a Button.ClickEvent that a real button control actually raised the event.  In the above case, it was a CheckBox control that actually raised the event.

 sample5

In the above image the Click Me button was clicked, but only the Class Handler actually handled the event since it was marked as handled and the other event handlers down stream were not set up to handle, previously handled events.  This is where the Routed Event Viewer application really shines by allowing you to experiment and fully understand the concept of Handled RoutedEvents.

Window1 XAML

<Window
    x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="500">
    <StackPanel Orientation="Vertical">
        <Button Margin="10" Content="Click Me"
                ToolTip="Click to display message below"
                VerticalAlignment="Top"
                Click="Button_Click_1"/>

        <Button Margin="10" Content="New Window"
                ToolTip="Click to display a new window"
                VerticalAlignment="Top"
                Click="Button_Click" />

        <CheckBox Margin="10"
                  x:Name="chkHandleWatchOutEvents"
                  VerticalAlignment="Top"
                  Content="Handle Watch Out Event" />

        <TextBlock Margin="10" VerticalAlignment="Top"
                   x:Name="tbWatchOutMessages" />

        <TextBlock Margin="10" VerticalAlignment="Top"
                   x:Name="tbSafeMessages" />

        <TextBlock Margin="10" VerticalAlignment="Top"
                   x:Name="tbHandlerMessages" />

    </StackPanel>
</Window>

Window1 XAML VB

Class Window1

    Private _intMessageNumber As Integer

    Private Sub WatchOutHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)

        _intMessageNumber += 1
        If TypeOf e.OriginalSource Is CheckBox Then
            'this line of code will never run, but here to prove the point
            Me.tbSafeMessages.Text = String.Format( _
              "Class Handler: CheckBox clicked at {0}, Message: {1}", _
              Now.ToLongTimeString, _intMessageNumber.ToString)
        Else
            Me.tbWatchOutMessages.Text = String.Format( _
              "Class Handler: {0} was clicked at {1}, Message: {2}", _
              CType(e.OriginalSource, Button).Content.ToString, _
              Now.ToLongTimeString, _intMessageNumber.ToString)
        End If

        e.Handled = Me.chkHandleWatchOutEvents.IsChecked.Value

    End Sub

    Private Sub WindowSafeHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)

        _intMessageNumber += 1
        If TypeOf e.OriginalSource Is CheckBox Then
            Me.tbSafeMessages.Text = String.Format( _
              "Window Handler: CheckBox clicked at {0}, Message: {1}", _
              Now.ToLongTimeString, _intMessageNumber.ToString)
        Else
            Me.tbSafeMessages.Text = String.Format( _
              "Window Handler: {0} was clicked at {1}, Message: {2}", _
              CType(e.OriginalSource, Button).Content.ToString, _
              Now.ToLongTimeString, _intMessageNumber.ToString)
        End If

    End Sub

    Private Sub Window1_Loaded(ByVal sender As Object, _
                ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

        'best fully understand WPF when using this
        EventManager.RegisterClassHandler(GetType(Button), _
            Button.ClickEvent, New RoutedEventHandler(AddressOf WatchOutHandler))

        'notice that the CheckBox's Click event will be captured with
        'this event handler because the CheckBox gets its ClickEvent
        'from ButtonBase and not the CheckBox class.
        Me.AddHandler(Button.ClickEvent, _
            New RoutedEventHandler(AddressOf WindowSafeHandler))

    End Sub

    Private Sub Button_Click(ByVal sender As System.Object, _
                ByVal e As System.Windows.RoutedEventArgs)

        Dim obj As New Window2
        obj.Show()

    End Sub

    Private Sub Button_Click_1(ByVal sender As System.Object, _
                ByVal e As System.Windows.RoutedEventArgs)

        _intMessageNumber += 1
        Me.tbHandlerMessages.Text = String.Format( _
          "Button Handler: {0} was clicked at {1}, Message: {2}", _
          CType(e.OriginalSource, Button).Content.ToString, Now.ToLongTimeString, _
          _intMessageNumber.ToString)

    End Sub
End Class

Close

As WPF developers we have 4 ways of adding RoutedEvent Handlers to our applications and each was demonstrated in this Test Bench Sample.

  1. In XAML
  2. At the class level
  3. At the container level
  4. At the control level

Understanding how and when each event handler will be called is vital to writing a WPF application. WPF provides the flexibility we as developers require. However, it’s incumbent upon developers to understand and harness that intrinsic power so that we can deliver stable, super cool applications.

Source Code: After downloading the source code you MUST change the file extension from .zip.DOC to .zip. This is a requirement of WordPress.com.

Download Source Code 17KB

Hope you can learn just a little bit more about WPF from this article and the Sample Series.

Just a grain of sand on the worlds beaches.

Leave a Reply

You must be logged in to post a comment.