Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
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

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.

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 $19.99/month. Cancel anytime
Banner background image