Properties, Dependency Properties, and WPF

Windows Presentation Foundation implements two complementary programming interfaces, letting you write entire WPF applications using a .NET-compliant programming language (or at least parts of it) using XAML.


March 12, 2007
URL:http://www.drdobbs.com/windows/properties-dependency-properties-and-wpf/198000276

Charles has been writing about Windows application programming for over 20 years. His most recent book is Applications = Code + Markup: A Guide to the Microsoft Windows Presentation Foundation, and he is currently working on a book about 3D graphics under the WPF. He can be contacted at www.charlespetzold.com.


Throughout the 20-odd years of its existence, Microsoft Windows has always exhibited a fairly high degree of backward compatibility. With relatively few examples, programs written for one version of Windows continue to run on the next.

Windows Vista continues this trend: It runs programs written for the Win32 API, for the Microsoft Foundation Classes (MFC), for Visual Basic, and for the Windows Forms library of .NET 1.0 and 2.0.

Vista also implements a new application programming interface called the "Windows Presentation Foundation" (WPF), previously known under the codename "Avalon." The WPF is also available under Windows XP with the .NET Framework 3.0 installed. (Alternatively, you can think of WPF as part of .NET 3.0, which can be installed under XP but which is built into Vista.)

Compared with the previous mainstream APIs for Windows, the WPF breaks new ground by implementing two complementary programming interfaces. You can write entire WPF applications in your favorite .NET-compliant programming language such as C# or Visual Basic .NET, but you can also write at least parts of the application using the XML-based Extensible Application Markup Language (XAML, pronounced "zammel").

Although XAML has been classified as a declarative programming language, it is mostly restricted to instantiation and initialization of visual objects. In simple scenarios, you use XAML to define the layout of controls on your windows, and you use code to implement event handlers for these controls. You compile the XAML and code together into an executable.

However, several features of the WPF—such as data binding and animation—let you write interesting XAML files that stand by themselves. These standalone XAML files can be launched under Vista or .NET 3.0 just like other executables, and they run in the web browser.

Instantiation and Initialization

Let's look at some C# code first. Here is some code to create and initialize a ScrollBar control, perhaps in the constructor of a Window class where the ScrollBar appears:


ScrollBar scr = new ScrollBar();
scr.Orientation =      Orientation.Vertical;
scr.Minimum = 10;
scr.Maximum = 100;

Orientation, Minimum, and Maximum look like fields of the ScrollBar class, but they are actually properties. The Orientation property is set to a member of the Orientation enumeration; the other properties are of type double. Properties were introduced in .NET 1.0 as a nice clean syntax to replace pairs of Get and Set methods; for example, GetMinimum and SetMinimum.

Very often, a .NET property provides a public interface to a private field. A property such as Minimum would be associated with a private field initialized to a default value:


private double min = 0;

The definition of the Minimum property starts out looking like a method, but without any parentheses or argument list. Within the property definition are two accessors, labeled get and set with code that might otherwise be found in the corresponding GetMinimum and SetMinimum methods:


public double Minimum
{
    get
    {
        return min; 
    }
    set
    {
        min = value;
        ...
    }
}

The get accessor must terminate with a return instruction with a value of the proper type. Within the set accessor, the word value indicates the value the property is being set to. Notice that ominous ellipsis. Traditionally, what happens next depends on how the ScrollBar class is structured. The object might need to redraw itself based on the new value of Minimum. Perhaps the Value property of ScrollBar is no longer within the range between Minimum and Maximum. Value might then need to be reset, which might trigger an event.

Properties are sometimes called "smart fields" because code is evoked when a property is set or retrieved. The object has the opportunity to react in some way, and that's just not the case with fields. An object doesn't know a field has been set until it checks the value.

My illustration of the Minimum property is actually not how properties like these are defined in the Windows Presentation Foundation. Some properties are, but many are not. Many WPF properties are actually a somewhat different kind of animal called "dependency properties," and the reasons for the extra overhead in the WPF is one of the themes of this article.

Originally, .NET properties seemed to do little more than provide an elegant syntax to replace Set and Get methods. A bonus was revealed when it was time to represent classes in XML-based files, including XAML. The property names simply became XML attributes. Here's the equivalent XAML of the ScrollBar creation and initialization logic:

     
<ScrollBar
  Orientation="Horizontal"
  Minimum="0"
  Maximum="150"
/>

The problem with this particular ScrollBar is that it doesn't have any effect on anything else in the program. In the general case, you'll want an event handler that's triggered whenever the Value property of the ScrollBar changes. You indicate the method you want to use as the handler for the ValueChanged event in C# code like this:


scr.ValueChanged += 
  ScrollBarValueChanged;

In XAML, you can indicate the event handler like so:


<ScrollBar 
  Orientation="Horizontal"    
  Minimum="0" Maximum="150"
  ValueChanged="ScrollBarValueChanged"
/>

However, the handler itself needs to be written in code.

There's another way the ScrollBar could be used, and this alternative approach doesn't require an event handler. It's a data binding. Basically, you establish a link between the Value property of the ScrollBar and a property of some other object. You can establish this binding in code, but it's actually easier to do it in XAML.

A Stack of Text

In a WPF program, properties can be set in a variety of ways. They can be set explicitly through code or markup, they can be set through data bindings, they can be set through animations or styles, and they can be set in other ways beyond the scope of this article. Listing One is a XAML file named "PropertySettingsDemo.xaml" that provides a glimpse into this flexibility. If you're running Windows Vista or the .NET Framework 3.0 under Windows XP, you can simply launch this file as if it were a program, and it runs in your web browser. It contains two animations, so what you'll see might look something like Figure 1.

<!-- PropertySettingsDemo.xaml by Charles Petzold -->

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      WindowTitle="Property Settings Demo"
      Title="Property Settings Demo"
      FontSize="36pt">
    <!-- Resources -->
    <Page.Resources>
        <Style x:Key="styleTextBlock" TargetType="TextBlock">
            <Setter Property="FontSize" Value="24" />
            <Setter Property="Foreground" Value="Red" />
            <Setter Property="HorizontalAlignment" Value="Center" />
        </Style>
    </Page.Resources>

    <DockPanel>
        <ScrollBar Name="scroll"
                   DockPanel.Dock="Left"
                   Orientation="Vertical"
                   Minimum="10" Maximum="100" Value="20" />

        <!-- StackPanel with six TextBlock elements -->
        <StackPanel>
            <TextBlock Text="Hello XAML #1" FontSize="16" 
                       HorizontalAlignment="Center" />
            <TextBlock Text="Hello XAML #2" FontSize="12"
                       Name="txtblk2" HorizontalAlignment="Center" />
            <TextBlock Text="Hello XAML #3"
                       HorizontalAlignment="Center" />
            <TextBlock Text="Hello XAML #4" Name="txtblk4">
                <TextBlock.LayoutTransform>
                    <RotateTransform />
                </TextBlock.LayoutTransform>
            </TextBlock>
            <TextBlock Text="Hello XAML #5"
                   FontSize="{Binding ElementName=scroll, Path=Value}"
                       HorizontalAlignment="Center" />
            <TextBlock Text="Hello XAML #6" 
                       Style="{StaticResource styleTextBlock}" />
        </StackPanel>
    </DockPanel>

    <!-- Animations -->
    <Page.Triggers>
        <EventTrigger RoutedEvent="Page.Loaded">
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetName="txtblk2"
                                Storyboard.TargetProperty="FontSize"
                                To="48" Duration="0:0:2"
                                AutoReverse="True" 
                                RepeatBehavior="Forever" />
                    <DoubleAnimation Storyboard.TargetName="txtblk4"
                                 Storyboard.TargetProperty=
                                     "LayoutTransform.Angle"
                                     To="360" Duration="0:0:10"
                                     RepeatBehavior="Forever" />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Page.Triggers>
</Page>
Listing One

Figure 1: Animations generated by PropertySettingsDemo.xaml.

In the center of the PropertySettingsDemo.xaml file are six TextBlock elements, five of which have their FontSize properties set in five different ways. (The rotating TextBlock is just a gratuitous bonus, but I use it to make a point.) TextBlock is a relatively simple element used to display chunks of text. The six TextBlock elements are grouped as children in a StackPanel element, which stacks its children vertically. The StackPanel is itself a child (along with a ScrollBar) of a DockPanel, which is a child of the top-level element, a Page.

Notice first that the whole program contains no explicit coordinate positions or sizes except for the FontSize property itself. Each TextBlock is entirely capable of determining what size it needs to be, and the StackPanel can arrange these six elements based on those sizes. Also notice that StackPanel's arrangement of the elements dynamically accommodates the animations.

Measure and Arrange

The WPF was conceived early on as supporting automatic sizing and dynamic layout, so it's not surprising to see this mechanism implemented as a fundamental part of WPF architecture.

Layout occurs in two passes, and begins with the highest level element, in this example, the Page. Every element can have zero or more children. Elements that have multiple children go by the generic name of "panel" and are the primary layout mechanisms. The Panel class has several descendants, including DockPanel, StackPanel, WrapPanel, Grid, UniformGrid, and Canvas, which is the panel that lets you position controls and elements with explicit two-dimensional coordinates.

Layout begins with a call to the virtual MeasureOverride method in the top-level element. (This method is defined in FrameworkElement and it has a rather odd name because it essentially replaces a similar method in the UIElement class MeasureCore.) The parameter to this method is an available size. The MeasureOverride method is responsible for calling the Measure method on all its children (if any), which in turn causes a call to the child's own MeasureOverride method. This is how measuring proceeds from the top of the element tree to the bottom.

The Measure method is responsible for determining what size the element should be. When Measure returns, the parent examines the DesiredSize properties of its children and derives a composite size. For example, a StackPanel with a vertical orientation sums the desired heights of all its children.

The second layout pass begins with a call to virtual ArrangeOverride to the top-level element. The parameter indicates a final size for the element. The parent's job is to arrange all its children within that space with a call to each child's Arrange method, which then calls the child's ArrangeOverride method to ripple through the rest of the layout.

Although panels are commonly used to lay out controls and elements within a page or window, they can also be used inside buttons and other controls. For example, a Button might contain a StackPanel, which then contains text and bitmaps.

As the animations are going on inside PropertySettingsDemo.xaml, the StackPanel is continuously rearranging its children based on their changing sizes.

Retained Graphics

What's not going on in PropertySettingsDemo.xaml is a lot of redrawing—at least, by most of the TextBlock elements. In earlier Windows programming environments, when a control is moved from one part of a window to another, it needs to be redrawn. The surface of the window is invalidated, which generates repainting calls.

In contrast, the WPF has a retained graphics system. Once an element draws itself—a process that happens in the virtual OnRender method—it doesn't need to draw itself again unless something happens to cause it to require a new appearance. This is the case with the TextBlock whose FontSize is animated, and the one whose FontSize is bound to the ScrollBar. A new FontSize requires redrawing; but the others are drawn only once, including the one being rotated.

To make this program more efficient, it would be better to change the size of TextBlock elements with a ScaleTransform rather than altering the FontSize. The transform is performed on the retained graphics object rather than requiring the element to redraw itself.

Properties and Dependency Properties

The developers of the WPF realized they were designing a system where properties could be set in a variety of ways, some of them possibly interacting with each other. It was important to construct a system where these interactions would have strict levels of precedence and predictability. Their solution was to build a layer on top of the existing property mechanism called "dependency properties."

If you were privy to source code for TextBlock, you would probably find it has something like this code:


public static DependencyProperty 
  FontSizeProperty =
   DependencyProperty.Register
     ("FontSize", typeof(double), 
      typeof(TextBlock), 
       new FrameworkPropertyMetadata
        (default, flags,
         FontSizeChanged), 
          ValidateFontSize);

This is the definition of a static field named FontSizeProperty—that is, the FontSize property name followed by the word Property—of type DependencyProperty. The initialization code that follows could alternatively be in the class's static constructor. This code defines the name of the property, its type (double), its owner, and a default value, indicated by default. Let me come back to the flags parameter.

The code also references two static methods. One provides a systematic approach to validation. The ValidateFontSize method returns True if the FontSize property is being set to a value of at least 1/300 inch.


static bool ValidateFontSize
    (object obj)
{
  double fntsize = (double) obj;
  return fntsize > 1.0 / 300;
}

I don't have access to WPF source code, so I can't tell you precisely what happens in the method I've called FontSizeChanged. The method is called whenever the FontSize value changes, but it might not exist at all, and if it does, it probably only performs some preparation work, such as creating an object of type FormattedText. Anything more is unnecessary, as you'll see soon.

The TextBlock class also includes a regular FontSize property, but it only refers to the FontSizeProperty dependency property:


public double FontSize
{
  get { return (double)
     GetValue (FontSizeProperty);      }
  set { SetValue(FontSizeProperty,
     value); }
}

The GetValue and SetValue methods are defined by the DependencyObject class. Any class that implements dependency properties must derive from DependencyObject.

I haven't yet discussed the flags parameter in the dependency property initialization code, and for the FontSize property, these flags are crucial. The flags parameter consists of zero or more members of the FrameworkPropertyMetadataOptions enumeration, combined with the bitwise OR operator. For FontSize, there are three flags set:

Yes, the TextBlock element is still responsible for determining how large it needs to be and what it should look like, but all the notification mechanisms for property changes have been encapsulated in the DependencyProperty object. TextBlock doesn't have to invalidate its size or rendering information, nor does TextBlock have to worry about how property inheritance works. The retained graphics system provides that any change to these properties won't cause flickering on the screen.

Dependency properties are so important that data binding and animation don't work with anything that's not a dependency property. The data-binding and animation markup in PropertySettingsDemo.xaml seems to refer to the FontSize property, but binding and animation classes actually access the FontSizeProperty field directly.

That means that if you're writing a WPF class in which you want properties to be bindable or animatable, you'll want to make them dependency properties, and in the process save yourself from implementing a lot of custom notification logic.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.