Windows Presentation Foundation 4.5 Cookbook
上QQ阅读APP看书,第一时间看更新

Using an attached property

Attached properties are curious beings. There is no direct analogue to anything else in the .NET framework. The closest may be extension methods, introduced in C# 3.0. Extension methods are a way of extending a type without inheriting from it (even if that type is sealed). Attached properties are dependency properties that are defined by some type, but can be used by (almost) any other typed object. That is, they can extend a type's properties without code derivation. In this recipe, we'll see how to use an existing attached property, and in the next one we'll learn how to create a new attached property.

Getting ready

Make sure Visual Studio is up and running.

How to do it...

We'll create an application that creates a rectangle and places it inside a canvas at exact coordinates using attached properties:

  1. Create a new WPF Application named CH01.SimpleAttached.
  2. Open the MainWindow.xaml file and create the following basic layout:
    <Grid>
       <Canvas>
          <RepeatButton Content="Move" />
       </Canvas>
    </Grid>
  3. This creates a grid which hosts a Canvas and that canvas hosts a RepeatButton. Let's add a Rectangle element to the Canvas and place it in some position:
    <Canvas>
        <RepeatButton Grid.Row="1" Content="Move" />
        <Rectangle x:Name="_rect" Width="50" Height="50" 
                Fill="Red" Stroke="Black" StrokeThickness="5"
                Canvas.Left="30" Canvas.Top="40" />
    </Canvas>
  4. Run the application. You should see something like this:
    How to do it...
  5. The Canvas.Left and Canvas.Top are attached properties. They are defined by the Canvas type, but they can be applied to any element (technically, anything that derives from DependencyObject). In this case, these two properties are applied to the Rectangle, essentially "extending" its property set with two new properties. The syntax DefiningClassName.PropertyName is the way to access an attached property in XAML.
  6. Now let's try changing these properties in code. When the repeat button is clicked, let's move the rectangle a little bit to the right. First, let's name the Rectangle, so we can easily refer to it in code:
    <Rectangle x:Name="_rect" Width="50" Height="50" Fill="Red" 
               Stroke="Black" StrokeThickness="5"
               Canvas.Left="30" Canvas.Top="40" />
  7. Add a Click event handler to the RepeatButton. In the handler, add the following code:
       Canvas.SetLeft(_rect, Canvas.GetLeft(_rect) + 5);
  8. An attached property is accessed in code using a set of static methods on the class declaring the property (in this case, the Canvas). The first argument to these methods is the intended target of the property (in this case the Rectangle). Run the application. Click on the button (and hold); you should see the rectangle moving along to the right, 5 units at a time.

How it works...

An attached property is first and foremost a dependency property, meaning it supports all the capabilities of dependency properties. However, as an attached property is "attached" to an object that did not define it, a simple property like syntax is not possible – as C# does not support the concept of attached properties natively. Instead, the declaring class provides two static methods, named DeclaringType.SetPropertyName and DeclaringType.GetPropertyName, that provide a way to set or get the property value for some object passed in as the first argument (as demonstrated in the last code snippet).

In XAML, things are simpler, as the XAML parser is aware of attached properties, and converts the simpler DeclaringType.PropertyName attribute to the aforementioned Set method.

There's more...

The actual implementation of the Set/Get static methods mentioned above is to call the regular DependencyObject.SetValue/GetValue as for a regular dependency property. This means that the code to move the rectangle could have been written as follows:

_rect.SetValue(Canvas.LeftProperty,
   (double)_rect.GetValue(Canvas.LeftProperty) + 5);

Why an attached property?

One may wonder why to go to all this trouble for the Left and Top properties. Would it not be simpler to define the Left and Top properties on the (for example) UIElement class and be done with it? These properties could have been normal dependency properties and enjoy the simpler syntax they carry.

The reason is, that a Left or Top property may not always make sense. In fact, it only makes sense when the element is placed within a Canvas. What if the rectangle is inside a Grid? Or a StackPanel? The Left/Top properties wouldn't make sense. This leads to the conclusion that attached properties are a kind of contextual property – they are relevant under particular circumstances, so they can be "attached" if and when actually needed.

Does the declaring type "own" the property?

The previous example may lead to a wrong conclusion. It seems Canvas.Left and the like are only relevant when the element is inside a Canvas. Similarly, the Grid.Row and Grid.Column attached properties only make sense for elements placed inside a Grid. Is this somehow necessary from an attached property point of view?

Not at all. This is just coincidence. The above properties in fact make sense only for elements placed inside their respective declaring type, but that does not have to be the case. For example, suppose we have a button with a tool tip defined:

<Button Content="Copy" ToolTip="Copy the Selected Items" />

If the button is disabled (IsEnabled set to true), the tool tip does not appear at runtime. To make it appear even if the control is disabled, we must set the ToolTipService.ShowOnDisabled attached property to true:

<Button Content="Copy" ToolTip="Copy the Selected Items" 
 ToolTipService.ShowOnDisabled="True"/>

We set the property on the button, but it's defined in the ToolTipService class. This class is not an element (unlike the Canvas for example). In fact, it's a static class (instances of it cannot be created). So, there is no relationship between the button and the ToolTipService class, or between the ToolTip and ToolTipService classes, for that matter. The way this connection is established (so it can have some effect) will be revealed in the next recipe in this chapter.

See also

To create your own attached properties, refer to the next recipe, Creating an attached property.