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

Creating an attached property


An attached property can be used to somehow "enhance" or extend another object. In the case outlined in the previous recipe, Using an attached property, an element was placed at exact coordinates within a Canvas using the attached Canvas.Left and Canvas.Top properties. An attached property is a powerful tool for extending the behavior of any object without the need to inherit from the type of the object. In this task, we'll see this in action.

Getting ready

Make sure Visual Studio is up and running.

How to do it...

We'll create an attached property that would rotate any element it's attached to:

  1. Create a new WPF Application named CH01.CustomAttached.

  2. Open MainWindow.xaml. Add some elements in a Canvas (replace the default Grid) as follows:

    <Canvas>
       <Ellipse Fill="Red" Width="100" Height="60" />
       <Rectangle Fill="Blue" Width="80" Height="80"
                  Canvas.Left="100" Canvas.Top="100" />
       <Button Content="Hello" Canvas.Left="130" Canvas.Top="30" 
        FontSize="20" />
    </Canvas>
  3. Suppose we want to rotate a particular element around its center. We would have to write something like this (example for the Ellipse):

    <Ellipse Fill="Red" Width="100" Height="60" 
             RenderTransformOrigin=".5,.5">
       <Ellipse.RenderTransform>
          <RotateTransform Angle="30" />
       </Ellipse.RenderTransform>
    </Ellipse>

    Although this is certainly possible, this makes for a lot of typing. Now imagine doing something similar for other elements. Let's make it shorter by defining and using an attached property.

  4. Add a new class to the project named RotationManager.

  5. We'll register a new attached property within this class; a property any other object can use. To do that, we'll take advantage of a Visual Studio code snippet, propa (similar in concept to propdp discussed in the task Creating a dependency property in this chapter). Inside the class definition, type propa (without the quotes). This is how it should look at this point:

  6. Press Tab once, and fill in the property details as follows: the property type should be double, its name should be Angle, its owner class RotationManager, and its default value zero. You'll have to add a using statement for System.Windows namespace. The generated code should look as follows (after removing the comment and some formatting):

    class RotationManager : DependencyObject {
       public static double GetAngle(DependencyObject obj) {
          return (double)obj.GetValue(AngleProperty);
       }
       public static void SetAngle(DependencyObject obj,
          double value) {
          obj.SetValue(AngleProperty, value);
       }
     
       public static readonly DependencyProperty AngleProperty =
             DependencyProperty.RegisterAttached("Angle",
             typeof(double), typeof(RotationManager),
             new UIPropertyMetadata(0.0));
     
    }
  7. Now that we have an attached property definition, let's use it. We'll set it on our various elements. The first step is mapping an XML namespace to our namespace (as we learned in the recipe Creating custom type instances in XAML in this chapter). Open MainWindow.xaml and add a mapping on the root element, as in the following code snippet:

    xmlns:local="clr-namespace:CH01.CustomAttached"
  8. Now let's set the property with various values on the various elements. Here's an example for the Ellipse (notice the intellisense popping up to help):

     <Ellipse Fill="Red" Width="100" Height="60" 
              local:RotationManager.Angle="45"/>
    
  9. Add similar settings for the Rectangle and Button like as follows:

    <Rectangle Fill="Blue" Width="80" Height="80"
               Canvas.Left="100" Canvas.Top="100"
               local:RotationManager.Angle="30" />
    <Button Content="Hello" Canvas.Left="130" Canvas.Top="30" 
            FontSize="20" 
            local:RotationManager.Angle="90"/>
    
  10. Notice that the designer preview shows no change. If you run the application, nothing happens. And why would anything happen? We declared a property and nothing else. Let's add some behavior logic if the property is actually used. For that, we'll add a property changed handler notification. Go back to RotationManager.cs and modify the property registration as follows:

    public static readonly DependencyProperty AngleProperty =
          DependencyProperty.RegisterAttached("Angle",
          typeof(double), typeof(RotationManager),
          new UIPropertyMetadata(0.0, OnAngleChanged));
  11. The OnAngleChanged method will be called for any change in the property value on any object it's applied to. Let's add some simple logic that will rotate the element:

    private static void OnAngleChanged(DependencyObject obj, 
       DependencyPropertyChangedEventArgs e) {
       var element = obj as UIElement;
       if(element != null) {
          element.RenderTransformOrigin = new Point(.5, .5);
          element.RenderTransform = new RotateTransform(
          (double)e.NewValue);
       }
    }
  12. If we switch back to the designer, we'll see the elements rotated according to the specified angles. If we run the application, we'll see something like this:

There we have it. An easy way to rotate any element by using an attached property.

How it works...

Attached properties are registered similarly to regular dependency properties. In terms of functionality, they are dependency properties. This means they support everything a dependency property supports: data binding, animation, and so on. An attached property can be defined by any class (RotationManager in our example) and can be applied to any object whose type derives from DependencyObject.

Simply registering an attached property has no effect on its own. There must be some "extra" code that looks for that property and does something when it's applied or changed. In the example shown, this is done by specifying a property changed handler, called by WPF whenever the property is changed on any object. In the example code, we restrict using a UIElement-derived type, as this is the first type that supports RenderTransform and RenderTransformOrigin. This also shows the weakness of attached properties: it's not possible to know whether specifying the property on some object is beneficial. We could have thrown an exception if the object was not UIElement-derived to somewhat rectify this (albeit at runtime rather than compile time), but this is not typically employed (although we could have written something with Debug.WriteLine to indicate this needs attention), as there may be other code that does not consider this an invalid setting.

There's more...

The property change notification scheme is typically used by WPF with attached properties that are defined by panels, such as Canvas, DockPanel, and Grid. Note that the panels only look for the relevant attached properties on their immediate children (and not grandchildren). This is not a limitation of attached properties, it's simply the way these panels work. Although attached properties within panels are common, there are other ways these properties can be used.

One possibility is to use the existence of these property values within styles (a complete treatment of styles is in given Chapter 8) or templates (templates are discussed in Chapter 6 and Chapter 8). For now, think of a style as a grouping of related settings that can be applied as a group to an element. For example, the following style accomplishes roughly the same thing as our property change handler:

<Style TargetType="Button">
   <Setter Property="RenderTransformOrigin" Value=".5,.5" />
   <Setter Property="RenderTransform">
      <Setter.Value>
         <RotateTransform Angle="{Binding Path=(local:RotationManager.Angle), RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Button}}" />
      </Setter.Value>
   </Setter>
</Style>

Of course, this example works with buttons only because of the targeted style (style that works on buttons only), but the result is the same. Note the parentheses around the attached property name. This is essential – otherwise the XAML parser does not understand this to be an attached property; it interprets local:RotationManager as the property name (and expects Angle to be a sub-property). Also, leaving out the "Path=" (as is customary in binding expressions), causes the expression to fail (for a similar reason).

Reusing existing attached properties

An attached property is (paradoxically) a detached entity. It has no special affinity to the declaring type. This means we can use an already defined attached property if it's typed appropriately, named appropriately, and has no use in the needed situation. In our example, we need an attached property that is of type double, has an intuitive enough name (maybe something with "angle" or "rotate"), and is unused in scenarios where the use of our property makes sense.

Clearly, it's not easy finding such a property, but sometimes one may get lucky. For instance, if we elect to go for the attached property ToolTipService.HorizontalOffset (typed as double), we can achieve the same effect as previously (with a style setter) without defining a new attached property. This is not a good choice in this case, as an offset is not an angle, and clearly tooltips have nothing to do with rotation. The worse problem here is that there may be a legitimate reason to place that property on a button (to cater offsetting a tooltip), so that reusing for rotation purposes would collide with the tooltip, making only one a winner. Still, the general concept holds – any attached property can be reused.

Attached property reuse is possible in styles, templates (data template and control template), and triggers (within styles and templates).

See also

For background on dependency properties, check out the recipe Creating a dependency property in this chapter.