Book Image

Windows Presentation Foundation 4.5 Cookbook

By : Pavel Yosifovich
Book Image

Windows Presentation Foundation 4.5 Cookbook

By: Pavel Yosifovich

Overview of this book

Windows Presentation Foundation (WPF) provides developers with a unified programming model for building rich Windows smart client user experiences that incorporate UI, media, and documents.WPF has become the leading technology for developing rich client applications on the Windows platform, packed with features and capabilities. However, WPF is big; in fact, it's huge, causing a steep learning curve for the beginner and even for those already using some WPF features.Windows Presentation Foundation 4.5 Cookbook provides clear recipes for common WPF tasks. It includes detailed explanations and code examples for customizing and enhancing the basic scenarios, while gaining a deep understanding of WPF mechanics and capabilities.WPF is different and requires a different mind-set and approach. This book provides recipes and insights not only in its design but also its practical implementation details.Starting from the foundations of WPF, such as dependency properties and XAML, the book touches on all major WPF aspects, such as controls and layout, resources, and digs deep into its unprecedented data binding capabilities.The book shows data and control templates in action, which allow full customizations of displayed data and controls in a declarative way. Supported by styles and resources makes data binding all the more powerful. The Model View View-Model pattern is presented as an effective way of maximizing decoupling of components, while providing an elegant way of expanding applications while maintaining a tight grip on complexity.The later parts discuss custom elements and controls ñ the ultimate customization mechanism, and looks at multithreading issues, and how .NET 4.5 task parallelism features can enhance application performance.
Table of Contents (18 chapters)
Windows Presentation Foundation 4.5 Cookbook
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
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.