Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Windows Presentation Foundation 4.5 Cookbook

You're reading from   Windows Presentation Foundation 4.5 Cookbook For C# developers, this book offers a fast route to getting more closely acquainted with the ins and outs of Windows Presentation Foundation. The recipe approach smoothes out the complexities and enhances learning.

Arrow left icon
Product type Paperback
Published in Sep 2012
Publisher Packt
ISBN-13 9781849686228
Length 464 pages
Edition 1st Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Pavel Yosifovich Pavel Yosifovich
Author Profile Icon Pavel Yosifovich
Pavel Yosifovich
Arrow right icon
View More author details
Toc

Table of Contents (18) Chapters Close

Windows Presentation Foundation 4.5 Cookbook
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
1. Foundations 2. Resources FREE CHAPTER 3. Layout and Panels 4. Using Standard Controls 5. Application and Windows 6. Data Binding 7. Commands and MVVM 8. Styles, Triggers, and Control Templates 9. Graphics and Animation 10. Custom Elements 11. Threading Index

Handling routed events


Events are essentially notifications from an object to the outside world – a variation on the "observer" design pattern. Most of the time an object is told what to do via properties and methods. Events are its way of talking back to whoever is interested. The concept of events existed in .NET since its inception, but WPF has something to say about the way events are implemented. WPF introduces routed events, an enhanced infrastructure for raising and handling events, which we'll look at in this recipe.

Getting ready

Make sure Visual Studio is up and running.

How to do it...

We'll create a simple drawing application that uses routed events to handle user interaction:

  1. Create a new WPF Application named CH01.SimpleDraw. This will be a simple drawing program.

  2. Add some markup to MainWindows.xaml that includes a Canvas and some rectangle objects to select drawing brushes:

    <Canvas Background="White" Name="_root">
    </Canvas>
  3. To do some drawing, we'll handle the MouseLeftButtonDown, MouseMove, and MouseUp events on the canvas object. Within the Canvas tag, type MouseLeftButtonDown=. Intellisense will pop up, suggesting to add a default handler name. Resist the temptation, and type OnMouseDown:

    <Canvas Background="White" Name="_root" 
        MouseLeftButtonDown="OnMouseDown">
    
  4. Right-click on OnMouseDown and select Navigate to Event Handler. Visual Studio will add the required handler method in the code behind file (MainWindow.xaml.cs) and jump straight to it:

    private void OnMouseDown(object sender, 
       MouseButtonEventArgs e) {
    }
  5. Add similar handlers for the MouseMove and MouseUp events, named OnMouseMove and OnMouseUp, respectively.

  6. Let's add simple drawing logic. First, add the following fields to the MainWindow class:

          Point _pos;
          bool _isDrawing;
          Brush _stroke = Brushes.Black;
  7. Now the OnMouseDown event handler:

    void OnMouseDown(object sender, MouseButtonEventArgs e) {
       _isDrawing = true;
       _pos = e.GetPosition(_root);
       _root.CaptureMouse();
    }
  8. Next, we'll handle mouse movement, like in the following code snippet:

    void OnMouseMove(object sender, MouseEventArgs e) {
       if(_isDrawing) {
          Line line = new Line();
          line.X1 = _pos.X;
          line.Y1 = _pos.Y;
          _pos = e.GetPosition(_root);
          line.X2 = _pos.X;
          line.Y2 = _pos.Y;
          line.Stroke = _stroke;
          line.StrokeThickness = 1;
          _root.Children.Add(line);
       }
    }
  9. If we're in drawing mode, we create a Line object, set its two points locations and add it to the Canvas.

  10. Finally, when the mouse button is released, just revert things to normal:

    void OnMouseUp(object sender, MouseButtonEventArgs e) {
       _isDrawing = false;
       _root.ReleaseMouseCapture();
    }
  11. Run the application. We now have a functional little drawing program. Event handling seemed to be as simple as expected.

  12. Let's make it a little more interesting, with the ability to change drawing color. We'll add some rectangle elements in the upper part of the canvas. Clicking any of them should change the drawing brushing from that point on. First, the rectangles:

       <Rectangle Stroke="Black" Width="25" Height="25" 
                  Canvas.Left="5" Canvas.Top="5" Fill="Red" />
       <Rectangle Stroke="Black"  Width="25" Height="25" 
                  Canvas.Left="35" Canvas.Top="5" Fill="Blue" />
       <Rectangle Stroke="Black" Width="25" Height="25" 
                  Canvas.Left="65" Canvas.Top="5" Fill="Yellow" />
       <Rectangle Stroke="Black" Width="25" Height="25" 
                  Canvas.Left="95" Canvas.Top="5" Fill="Green" />
       <Rectangle Stroke="Black" Width="25" Height="25" 
                  Canvas.Left="125" Canvas.Top="5" Fill="Black" />
  13. How should we handle clicks on the rectangles? One obvious way is to attach an event handler to each and every rectangle. But that would we wasteful. Events such as MouseLeftButtonDown "bubble up" the visual tree and can be handled at any level. In this case, we'll just add code to the OnMouseDown method:

          void OnMouseDown(object sender, MouseButtonEventArgs e) {
          var rect = e.Source as Rectangle;
          if(rect != null) {
             _stroke = rect.Fill;
          }
          else {
             _isDrawing = true;
             _pos = e.GetPosition(_root);
             _root.CaptureMouse();
          }
       }
  14. Run the application and click the rectangles to change colors. Draw something nice.

How it works...

WPF events are called routed events because most can be handled by elements that are not the source of the event. In the preceding example, the MouseLeftButtonDown was handled on the Canvas element, even though the actual event may have triggered on a particular Rectangle element. This is referred to as a routing strategy of bubbling.

When the left mouse button is pressed, we make a note that the drawing has started by setting _isDrawing to true (step 7). Then, we record the current mouse position relative to the canvas (_root) by calling the MouseButtonEventArgs.GetPosition method. And finally, although not strictly required, we "capture" the mouse, so that subsequent events will be sent to the Canvas and not any other window, even if the mouse pointer technically is not over the Canvas.

To properly ascertain which element was actually the source of the event, the RoutedEventArgs.Source property should be used (and not the sender, in our example the sender is always the Canvas).

There's more...

Bubbling is not the only routing strategy WPF supports. The opposite of bubbling is called tunneling; events with a tunneling strategy are raised first on the top level element (typically a Window), and then on its child, and so on, towards the element that is the actual source of the event. After the tunneling event has finished (calling any handlers along the way), its bubbling counterpart is raised, from the source up the visual tree towards the top level element (window).

A tunneling event always has its name starting with Preview. Therefore, there is PreviewMouseLeftButtonDown and its bubbling counterpart is simply MouseLeftButtonDown.

A third routing strategy is supported, called Direct. This is the simplest strategy; the event is raised on the source element of the event and that's it. No bubbling or tunnelling occurs. By the way, only very few events use the Direct strategy (for example, MouseEnter and MouseLeave).

Stopping bubbling or tunneling

After a bubbling event is handled by some element – it continues to bubble. The bubbling can be stopped by setting the RoutedEventArgs.Handled property to true.

If the event is a tunneling one – setting Handled to true stops the tunneling, but it also prevents the buddy-bubbling event from ever firing.

Attached events

Suppose we want to write a simple calculator application:

This is a Grid that contains various Button controls.

We would like to use as few handlers as we can. For the "=" button, we can attach a specific handler and prevent further bubbling:

void OnCalculate(object sender, RoutedEventArgs e) {
   // do operation
   e.Handled = true;

}

What about the digit buttons? Again, we could add a click handler to each one, but that would be wasteful. A better approach would be to leverage the Click event's bubbling strategy and set a single handler on the container Grid.

Typing "Click=" on the Grid tag seems to fail. Intellisense won't help and in fact this won't compile. It may be obvious – a Grid has no Click event. Click is specific to buttons. Does this mean we can't set a Click handler on the Grid? Fortunately, we can.

WPF provides the notion of attached events. Such events can be handled by any element, even if that element's type does not define any such event. This is achieved through attached event syntax (similar to attached properties), such as the following code snippet:

<Grid ButtonBase.Click="OnKeyPressed">

The Click event is defined on the ButtonBase class, although Button.Click works just as well, because Button inherits from ButtonBase. Now we can look at the actual source of the click with the same RoutedEventArgs.Source described previously:

int digit;
string content = ((Button)e.Source).Content.ToString();
if(int.TryParse(content, out digit)) {
   // a digit
}

You can find the complete calculator sample in the CH01.Calculator project, available with the downloadable source for this chapter.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €18.99/month. Cancel anytime