Click here to Skip to main content
Click here to Skip to main content

WPF: A Beginner's Guide - Part 4 of n

, 11 Mar 2008 CPOL
Rate this:
Please Sign up or sign in to vote.
An introduction into WPF Dependancy Properties.

Preface and Thanks

I am a .NET programmer, but a busy one. I do VB.NET and C#, ASP.NET / WinForms / WPF / WCF, Flash, Silverlight, the lot. Basically, I keep my toe in. But when I started writing this article series, I naturally chose my favourite language (which happens to be C#). I since got an email from an individual who requested that I publish this series with source code in VB.NET and C#. I simply stated I didn't have time. So this individual (Robert Ranck) volunteered to help out, and do the translation to VB.NET based on my original C# projects.

So for that and the subsequent VB.NET projects that you will find here, I ask you to thank Robert Ranck. Cheers Robert, your contributions will surely make this series more open to all .NET developers.

And another thanks goes out to Karl Shifflett (A.K.A. the blog/article machine, also known as the Molenator) for answering my dumb VB.NET questions. And I'd also like to mention that Karl has just started a more advanced series of WPF articles (which at present will be in VB.NET, but will hopefully appear in C# as well). Karl's new series is excellent, and I urge you all to encourage Karl on this series. It's not easy obligating oneself to write an entire series in one language let alone two. Karl's first article is located right here; have a look for yourself. Personally I love it.

Introduction

This article is the fourth in my series of beginner's articles for WPF. In this article, we will discuss Dependency Properties. The proposed schedule for this series will still be roughly as follows:

In this article, I'm aiming to cover a brief introduction into the following:

The difference between CLR properties and Dependency Properties

Note: What I mean by this picture is that a CLR property is fairly cool, but is fairly weak when compared with the awesome might of the Hulk. My boss looked at this and went, "man Spiderman is cool and elegant, the Hulk is retarded and can't even speak and breaks stuff. Really bad analogy man". But I like it, so it stays. I hope you see what my point was. Anyways, I like the Hulk, he is well cool in my opinion. I mean, being able to smash a tank with your bear hands, I would call that elegant. Truly I would.

To be honest, the picture probably isn't that wrong. The difference between what a normal CLR property with its simple get/set arrangement to be able to retrieve and update a private member, and what DPs offer is quite staggering.

CLR properties are really just safe wrappers around a private member variable, such that the correct access modifiers can be applied to the property, so we could have a Read or a Read/Write or just a Write property. But basically, that's it. This is all CLR properties really do for us.

For those of you that are not familiar with CLR properties, they are defined like this:

private int x;
public int X
{
    get { return x; }
    set { x = value; }
}

And the VB.NET equivalent would be:

Private x As Integer

Public Property X() As Integer
Get
    Return x
End Get

Set(ByVal Value As Integer)
    x = value
End Set
End Property

Easy enough. Now with DPs, the story changes (more than a lot). With DPs, we can do a whole lot more than just provide get/set wrapper arrangement.

The following table illustrates some of the things that can be achieved by the use of DPs:

Achievable items thanks to DPs
Change notification
Callbacks
Property value validation
Property value inheritance *
Participation in animations *
Participation in Styles *
Participation in Templates *
Databinding
Layout changes *
Overriding default data values *

So as you can see, DPs are more than just simple CLR properties. They are like Josh Smith once said "Properties On Steroids".

Note: The items marked with a "*" would not be possible at all if it were not for DPs. A CLR property would not cut the mustard, and besides which, Visual Studio or whatever IDE you use would probably throw an Exception if you tried to use a CLR property instead of a DP for these items.

Dependency Properties

Like I just stated, DPs are beefed up versions of their small, weaker CLR property cousins. But why do they exist? Why do we need them? Well, during the development of WPF, the Microsoft WPF team decided that the property system needed to be changed to allow things like change notification / value validation / value inheritance / participation in binding / participation in animation / participation in Styles / participation in Templates.

Much the same way as there is a RoutedEvents sub system in WPF, there is also a DependencyProperty sub system, and simliar to the RoutedEvents sub system, there are a number of stages that go with defining your own Dependency Properties. The steps are basically as follows:

  • Declare a DependencyProperty (always public static readonly)
  • Initialise the DependencyProperty, either using DependencyProperty.RegisterAttached/ DependencyProperty.Register/ DependencyProperty.RegisterReadOnly/ DependencyProperty.RegisterAttachedReadOnly
  • Declare get/set property wrappers (see note below)

That's the most basic pattern you will see for declaring DPs. However, in order to provide all these different options, there is a fair bit of DP syntax to get to grips with, so we will be looking at each of these areas individually in the sub-sections below. But before we do that, it's important to understand what value precedence is all about. So we will look at that first.

Very important note... Be aware

"Although the XAML compiler depends on the wrapper at compile time, at run time, WPF calls the underlying GetValue and SetValue methods directly! Therefore, to maintain parity between setting a property in XAML and procedural code, it's crucial that property wrappers do not contain any logic in addition to the GetValue/SetValue calls. If you want to add custom logic, that's what the registered callbacks are for. All of WPF's built in property wrappers abide by this rule, so this warning is for anyone writing a custom class with its own Dependency Properties."

- Windows Presentation Foundation Unleashed. Adam Nathan. Sams. 2007

Dependency Property value precedence

One of the side effects of the new WPF property system that has been developed to work with DPs, is that a value for a DP can actually come from many different places. For example, there could be a default value for the current DP, but then there could be an animation also applied to the current DP. What value should the DP take? The answer to this is defined by DP value precedence ordering, which is defined as follows:

In order of precedence, highest first:

  1. Property system coercion. Say from an active animation or base type coercion (where a DP may have be defined with a CoerceValueCallback (we will be covering this in the Dependency Property callbacks section), which will even overwrite a running animation.
  2. Active animations, or animations with a Hold behavior (where they should stay at the last animated value).
  3. Local value. A local value might be set through the convenience of the "wrapper" property, which also equates to setting as an attribute or property element in XAML, or by a call to the SetValue() method.
  4. TemplatedParent template properties. An element has a TemplatedParent if it was created as part of a template (a ControlTemplate or DataTemplate). Within the template, the following precedence applies:
    • Triggers from the TemplatedParent template.
    • Property sets (typically through XAML attributes) in the TemplatedParent template.
  5. Implicit style.
  6. Style triggers.
  7. Template triggers. Any trigger from a template within a style, or a directly applied template.
  8. Style setters.
  9. Default (theme) style. Within a default style, the following order of precedence applies:
    • Active triggers in the theme style.
    • Setters in the theme style.
  10. Inheritance. A few Dependency Properties inherit their values from parent element to child elements, such that they need not be set specifically on each element throughout an application. See Dependency Property value inheritance.
  11. Default value from Dependency Property metadata. Any given Dependency Property may have a default value as established by the property system registration of that particular property. See Dependency Property metadata.

Now don't worry if you don't get some of these, we will be covering some of these in this article, and some more of these in subsequent articles in this WPF beginner's guide series.

Dependency Property value inheritance

Using DPs, it is also possible to inherit values from a DP that is declared on a completely different element. In order to understand this a bit better, I have included in the attached demo application (at the top of this article) a project entitled "Using_Inhertied_DPs", which when run looks like the following image:

And if you look at the XAML code (this is all the code there is for this project), we can see something like the following figure:

So it can be seen that if we declare a TextElement.FontSize property at Window level, any Control that doesn't declare its own version of the TextElement.FontSize will inherit the TextElement.FontSize property value that is declared by the Window. But how can this be? TextElement isn't a Window or a Label, so how the heck does that all work? Well, the WPF property sub system is a tricky beast, and actually allows DPs to be declared in a manner which means they can actually be used on other classes that are not the same as where the DP is being declared. These are known as Attached Properties. Don't worry, we will be covering those in more detail in the Attached Properties section.

So that's half the mystery solved, the Window class in this example is using an Attached Property. Fine. But what about the inheritance part? How does that work? Well, that's another thing that the WPF property subsystem allows us to to do when we declare a DP. I think to fully understand this, we need to look at some shots from the excellent .NET de-compiler Reflector. So let's do that now, shall we? We will trawl through the classes that we are using in this example to keep things in context.

We will start out with the source of the TextElement.FontSize property, which is actually System.Windows.Documents.TextElement within PresentationFramework.dll. This is where the FontSize DP is first declared. Notice that it is declared as Attached. That means that other classes can now refer to this DP using the syntax TextElement.FontSize, even though they have nothing to do with the TextElement class.

And if we check out the MSDN page for the TextElement.FontSize property, we can see it's actually declared with the Inherits metadata, which allows controls that have this Attached Property used to inherit a value from further up the Visual Tree event if they do not declare a new value for this property.

In order to understand this further, let's continue our investigation. We now know that the TextElement does indeed declare a DP called FontSize, and that this is an Attached Property, and that it's marked as Inherits. So what about controls that may use this Attached Property" Like System.Windows.Controls.Label, as in our example. Well, let's have a look, shall we?

As it turns out, System.Windows.Controls.Label doesn't actually declare a DP for FontSize. We have to trawl the OO inheritance tree until we get to System.Windows.Controls.Control, which is where we find the declaration for FontSize. So let's see that.

The reason that the third System.Windows.Controls.Label in the screenshot of the demo project has a different FontSize is that it opts out of the property inheritance (the one coming from the TextElement.FontSize property being set by Window1), by explicitly declaring its own value, therefore overriding the inherited value it would have received from Window1.

So that's how the TextElement.FontSize property is implemented. But let's take a look at implementing our own inherited property. It just so happens that I have created another demo project, as part of the overall solution (available at the top of this article). The project is entitled "DP_Custom_Inherited_Properties", and when run, simply looks like this:

Not that's exciting, right?

But that's what you want when you are trying to learn something new, a small digestible chunk of information. So this demo app should do quite nicely. Let's start the dissection. There are two important parts to this application: the code-behind, where we actually declare the inherited DP, and the actual UI which uses the DP. Let's start with the code-behind:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Custom_Inherited_DPs
{
    /// <summary>
    /// This is a simple Button subclass that inherits the MinDate DP
    /// </summary>
    public class MyCustomButton : Button
    {

        /// <summary>
        /// Constructs a new MyCustomButton object, and adds the MyCustomButton
        /// class as a child of the MyStackPanel.MinDate DP
        /// </summary>
        static MyCustomButton()
        {
            MinDateProperty = 
              MyStackPanel.MinDateProperty.AddOwner(typeof(MyCustomButton),
              new FrameworkPropertyMetadata(DateTime.MinValue, 
              FrameworkPropertyMetadataOptions.Inherits));
        }

        #region Inherited DP declaration
        /// <summary>
        /// MinDate DP declaration
        /// </summary>
        public static readonly DependencyProperty MinDateProperty;

        public DateTime MinDate
        {
            get { return (DateTime)GetValue(MinDateProperty); }
            set { SetValue(MinDateProperty, value); }
        }
        #endregion
    }

    /// <summary>
    /// This is a simple StackPanel subclass that is the source for the 
    /// inherited MinDate DP
    /// </summary>
    public class MyStackPanel : StackPanel
    {
        /// <summary>
        /// Constructs a new MyStackPanel object, and registers the MinDate DP
        /// </summary>
        static MyStackPanel()
        {
            MinDateProperty = DependencyProperty.Register("MinDate",
            typeof(DateTime),
            typeof(MyStackPanel),
            new FrameworkPropertyMetadata(DateTime.MinValue, 
            FrameworkPropertyMetadataOptions.Inherits));
        }

        #region Source for Inherited MinDate DP declaration
        /// <summary>
        /// MinDate DP declaration
        /// </summary>
        public static readonly DependencyProperty MinDateProperty;

        public DateTime MinDate
        {
            get { return (DateTime)GetValue(MinDateProperty); }
            set { SetValue(MinDateProperty, value); }
        }
        #endregion
    }
}

And the VB.NET version of this is as follows:

Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Data
Imports System.Windows.Documents
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Imaging
Imports System.Windows.Navigation
Imports System.Windows.Shapes

    ''' <summary> 
    ''' This is a simple Button subclass that inherits the MinDate DP 
    ''' </summary> 
    Public Class MyCustomButton
        Inherits Button

        ''' <summary> 
        ''' Constructs a new MyCustomButton object, and adds the MyCustomButton 
        ''' class as a child of the MyStackPanel.MinDate DP 
        ''' </summary> 
        Shared Sub New()
            MinDateProperty = _
             MyStackPanel.MinDateProperty.AddOwner(GetType(MyCustomButton), _
             New FrameworkPropertyMetadata(DateTime.MinValue, _
             FrameworkPropertyMetadataOptions.[Inherits]))
        End Sub

#Region "Inherited DP declaration"
        ''' <summary> 
        ''' MinDate DP declaration 
        ''' </summary> 
        Public Shared ReadOnly MinDateProperty As DependencyProperty

        Public Property MinDate() As DateTime
            Get
                Return DirectCast(GetValue(MinDateProperty), DateTime)
            End Get
            Set(ByVal value As DateTime)
                SetValue(MinDateProperty, value)
            End Set
        End Property
#End Region
    End Class

    ''' <summary> 
    ''' This is a simple StackPanel subclass that is the source for the 
    ''' inherited MinDate DP 
    ''' </summary> 
    Public Class MyStackPanel
        Inherits StackPanel
        ''' <summary> 
        ''' Constructs a new MyStackPanel object, and registers the MinDate DP 
        ''' </summary> 
        Shared Sub New()
            MinDateProperty = DependencyProperty.Register("MinDate", _ 
             GetType(DateTime), GetType(MyStackPanel), _
             New FrameworkPropertyMetadata(DateTime.MinValue, _
             FrameworkPropertyMetadataOptions.[Inherits]))
        End Sub

#Region "Source for Inherited MinDate DP declaration"
        ''' <summary> 
        ''' MinDate DP declaration 
        ''' </summary> 
        Public Shared ReadOnly MinDateProperty As DependencyProperty

        Public Property MinDate() As DateTime
            Get
                Return DirectCast(GetValue(MinDateProperty), DateTime)
            End Get
            Set(ByVal value As DateTime)
                SetValue(MinDateProperty, value)
            End Set
        End Property
#End Region
End Class

So from here, we can see that I have declared two classes: one is a subclass of a StackPanel called MyStackPanel, which actually provides the source for the inherited DP MinDate. The other class is a subclass of Button called MyButton, which declares the inherited property. The important thing to note is how the MyButton class declares the DP. Let's have another quick look at that.

MinDateProperty = MyStackPanel.MinDateProperty.AddOwner(typeof(MyCustomButton),
  new FrameworkPropertyMetadata(DateTime.MinValue, 
  FrameworkPropertyMetadataOptions.Inherits));

And in VB.NET:

MinDateProperty = MyStackPanel.MinDateProperty.AddOwner(GetType(MyCustomButton), _
  New FrameworkPropertyMetadata(DateTime.MinValue, _
  FrameworkPropertyMetadataOptions.[Inherits]))

What can be seen is that the MyStackPanel's own MinDate DP is actually used to add an owner of type MyButton.

OK, so that's the code side of things. Let's see the XAML markup now:

<Window x:Class="Custom_Inherited_DPs.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Custom_Inherited_DPs" 
    xmlns:sys="clr-namespace:System;assembly=mscorlib"   
    WindowStartupLocation="CenterScreen" 
    Title="Using custom inherited DPs" 
    Height="400" Width="400">
   <Grid>
        <local:MyStackPanel x:Name="myStackPanel" 
                  MinDate="{x:Static sys:DateTime.Now}">
            <!-- Show a Content Presenter which uses the myStackPanel.MinDate DP -->
            <ContentPresenter Content="{Binding Path=MinDate, ElementName=myStackPanel}"/>
            <!-- Show a Button which uses the inherited myStackPanel.MinDate DP -->
            <local:MyCustomButton 
                    Content="{Binding RelativeSource={x:Static RelativeSource.Self},
                    Path=MinDate}" 
                Height="20"/>
        </local:MyStackPanel>
   </Grid>
</Window>

It can be seen that we only actually ever set the MinDate DP on the MyStackPanel instance, using the line:

<local:MyStackPanel x:Name="myStackPanel" MinDate="{x:Static sys:DateTime.Now}">

And as the MyCustomButtons MinDate DP inherits this value (thanks to the way we declared the MyCustomButtons MinDate DP), we don't need to declare it at all. It simply inherits from the MyStackPanel MinDate DP. And we display it by using a RelativeSource databinding as follows:

<local:MyCustomButton 
    Content="{Binding RelativeSource={x:Static RelativeSource.Self},
    Path=MinDate}" 
    Height="20"/>

I hope this little whirlwind tour helps you understand how property value inheritance actually works.

Attached Properties

Attached Properties are just another strain of DPs. Using Attached Properties, we are able to use DPs from classes that are outside of the current class. Recall Canvas.Left (from Part 1 of this series)? Well, that is an Attached Property. So why would we want to do this? Well, it would be to generally provide some common place to carry out something. For example, by using Canvas.Left, we ensure that the element that has this property applied will have its left position set as specified by the value specified.

That's all good, but we can think of many uses for Attached Properties. For example, here are some ideas/links that other folks have come up with:

And of course, I'm going to throw another idea/solution your way. The idea is this: "Imagine we want all our windows in our application to have the same common look and feel, say use a top banner and a content area". Kind of like inheriting from a partially constructed form, or like using Master Pages in ASP.NET. That is what we are trying to do. So to do this, let's see what we need to do:

  1. Create an Attached DP
  2. For all windows that want the common look, set the Attached Property accordingly

That's it. Shall we see some code? This is taken from the "Attached_Properties_DPs" project (which is part of the solution at the top of this article).

This is what it looks like with the Attached DP inactive (DP set to false):

This is what it looks like with the Attached DP active (DP set to true):

So here is the XAML markup:

<Window x:Class="Attached_Properties_DPs.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Attached_Properties_DPs" 
    local:AttachedPropertyChildAdder.IsMasterHeaderApplied="true"
    WindowStartupLocation="CenterScreen" 
    Title="Attached_Properties_DPs" 
    Height="400" Width="600">
        <!-- Extra content will be added here at runtime if the 
             local:AttachedPropertyChildAdder.IsMasterHeaderApplied="true" 
             is set to true try changing the value of this in 
             the top of this file, set it false and run me.
             See that there is no header applied if its false, 
             and there is if its true -->
        <Button x:Name="btn1" Content="click me" 
           Margin="10,10,10,10" Click="btn1_Click"/>
</Window>

The important line is local:AttachedPropertyChildAdder.IsMasterHeaderApplied="true" which ensures the Attached DP value is active/inactive. So now the code-behind:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Attached_Properties_DPs
{
    /// <summary>
    /// A simply show case, to demonstrate a usage of an attached DP property.
    /// This example lets Windows add a header portion to the their default content
    /// with some new Contents. Kind of like using Master Pages in ASP .NET
    /// </summary>
    public class AttachedPropertyChildAdder
    {
        #region Register IsMasterHeaderApplied DP
        public static readonly DependencyProperty IsMasterHeaderAppliedProperty =
            DependencyProperty.RegisterAttached("IsMasterHeaderApplied",
                typeof(Boolean),
                typeof(AttachedPropertyChildAdder),
                new FrameworkPropertyMetadata(IsMasterHeaderAppliedChanged));

        public static void SetIsMasterHeaderApplied(
                      DependencyObject element, Boolean value)
        {
            element.SetValue(IsMasterHeaderAppliedProperty, value);
        }
        public static Boolean GetIsMasterHeaderApplied(DependencyObject element)
        {
            return (Boolean)element.GetValue(IsMasterHeaderAppliedProperty);
        }
        #endregion

        #region PropertyChanged callback

        /// <summary>
        /// Is called whenever a user of the IsMasterHeaderApplied Attached DP changes
        /// the IsMasterHeaderApplied DP value 
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="args"></param>
        public static void IsMasterHeaderAppliedChanged(DependencyObject obj, 
                           DependencyPropertyChangedEventArgs args)
        {
            if ((bool)args.NewValue)
            {
                if (obj is Window)
                {
                    Window wnd = (Window)obj;
                    wnd.Loaded += new RoutedEventHandler(wnd_Loaded);
                }
            }
        }

        /// <summary>
        /// Hook into the Window load event to replace the Content of the Window
        /// with some custom Content, to show case exactly how cool DPs are.
        /// 
        /// In this example we are going to create a header for the Window.
        /// 
        /// So setting the IsMasterHeaderApplied will make sure the Window
        /// gets a header applied. 
        /// 
        /// Kind of like Master Pages in ASP .NET
        /// </summary>
        public static void wnd_Loaded(object sender, RoutedEventArgs e)
        {
            try
            {
                DockPanel dp = new DockPanel();
                dp.LastChildFill = true;
                StackPanel sp = new StackPanel();
                dp.Children.Add(sp);
                sp.Background = new SolidColorBrush(Colors.CornflowerBlue);
                sp.Orientation = Orientation.Vertical;
                sp.SetValue(DockPanel.DockProperty, Dock.Top);
                BitmapImage bitmap = new BitmapImage(
                  new Uri("Images/Header.png", UriKind.Relative));
                Image image = new Image();
                image.Source = bitmap;
                sp.Children.Add(image);
                UIElement el = _
                  ((DependencyObject)sender as Window).Content as UIElement;
                el.SetValue(DockPanel.DockProperty, Dock.Bottom);
                ((DependencyObject)sender as Window).Content = null;
                dp.Children.Add(el);
               ((DependencyObject)sender as Window).Content = dp;
            }
            catch (Exception ex) 
            { 
                System.Diagnostics.Debug.WriteLine(
                   string.Format("Exception : {}",ex.Message));  
            }
        }
        #endregion
    }
}

And in VB.NET:

Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Shapes
Imports System.Windows.Media
Imports System.Windows.Media.Imaging

    ''' <summary> 
    ''' A simply show case, to demonstrate a usage of an attached DP property. 
    ''' This example lets Windows add
    ''' a header portion to the their default content 
    ''' with some new Contents. Kind of like
    ''' using Master Pages in ASP .NET 
    ''' </summary> 
    Public Class AttachedPropertyChildAdder
#Region "Register IsMasterHeaderApplied DP"
    Public Shared ReadOnly IsMasterHeaderAppliedProperty As DependencyProperty = _
      DependencyProperty.RegisterAttached("IsMasterHeaderApplied", _
      GetType(Boolean), GetType(AttachedPropertyChildAdder), _
      New FrameworkPropertyMetadata((AddressOf IsMasterHeaderAppliedChanged)))


    Public Shared Sub SetIsMasterHeaderApplied(ByVal element As DependencyObject, 
        ByVal value As Boolean)
        element.SetValue(IsMasterHeaderAppliedProperty, value)
    End Sub
    Public Shared Function GetIsMasterHeaderApplied(ByVal element _
                           As DependencyObject) As Boolean
        Return CBool(element.GetValue(IsMasterHeaderAppliedProperty))
    End Function
#End Region

#Region "PropertyChanged callback"

        ''' <summary> 
        ''' Is called whenever a user
        ''' of the IsMasterHeaderApplied Attached DP changes 
        ''' the IsMasterHeaderApplied DP value 
        ''' </summary> 
        ''' <param name="obj"></param> 
        ''' <param name="args"></param> 
        Public Shared Sub IsMasterHeaderAppliedChanged(ByVal obj _
               As DependencyObject, ByVal args As DependencyPropertyChangedEventArgs)
            If CBool(args.NewValue) Then
                If TypeOf obj Is Window Then
                    Dim wnd As Window = DirectCast(obj, Window)
                    AddHandler wnd.Loaded, AddressOf wnd_Loaded
                End If
            End If
        End Sub

        ''' <summary> 
        ''' Hook into the Window load event to replace the Content of the Window 
        ''' with some custom Content, to show case exactly how cool DPs are. 
        ''' 
        ''' In this example we are going to create a header for the Window. 
        ''' 
        ''' So setting the IsMasterHeaderApplied will make sure the Window 
        ''' gets a header applied. 
        ''' 
        ''' Kind of like Master Pages in ASP .NET 
        ''' </summary> 
        Public Shared Sub wnd_Loaded(ByVal sender As Object, _
                                     ByVal e As RoutedEventArgs)
            Try
                Dim dp As New DockPanel()
                dp.LastChildFill = True
                Dim sp As New StackPanel()
                dp.Children.Add(sp)
                sp.Background = New SolidColorBrush(Colors.CornflowerBlue)
                sp.Orientation = Orientation.Vertical
                sp.SetValue(DockPanel.DockProperty, Dock.Top)
                Dim bitmap As New BitmapImage(New Uri("Images/Header.png", _
                                                      UriKind.Relative))
                Dim image As New Image()
                image.Source = bitmap
                sp.Children.Add(image)
                Dim el As UIElement = TryCast(TryCast(
                    DirectCast(sender, DependencyObject), Window).Content, UIElement)
                el.SetValue(DockPanel.DockProperty, Dock.Bottom)
                TryCast(DirectCast(sender, DependencyObject), Window).Content = Nothing
                dp.Children.Add(el)
                TryCast(DirectCast(sender, DependencyObject), Window).Content = dp
            Catch ex As Exception
                System.Diagnostics.Debug.WriteLine(String.Format("Exception : {}", _
                                                   ex.Message))
            End Try
        End Sub
#End Region
End Class

I think that's pretty neat. In one line of code within any Window, we can decide if we want it to be styled with a header or not. Of course, this is a very simple example, but using some of our previous RoutedCommands knowledge (as seen in Part3), we could imagine that this header also contains all the menus used by the application, and we can get all the windows to have the same menu system by setting one property.

Dependency Property metadata

The FrameworkPropertyMetadata class derives from PropertyMetadata, and for most WPF framework-level application development purposes, FrameworkPropertyMetadata is the type used for Dependency Property metadata, rather than the base metadata type's PropertyMetadata or UIPropertyMetadata. This is true both for existing Dependency Properties and for most custom Dependency Property scenarios.

But what does this class actually do for us? Well, the answer is that whenever we define Register/Add or Attach a DependencyProperty, we need to supply a FrameworkPropertyMetadata instance. By supplying a FrameworkPropertyMetadata instance, we are able to instruct the WPF property system to do special things with the DP that is using the FrameworkPropertyMetadata. For example, if we examine the constructors for the FrameworkPropertyMetadata class, it may become clearer as to its purpose in the scheme of the property system (sorry for the small image), but if you want a bigger picture, you can go to the MSDN documentation.

So it can be seen that by using FrameworkPropertyMetadata, we are able to provide information to the property system such as:

  • Default values
  • Provide one of the FrameworkPropertyMetadataOptions values, such as AffectsMeasure/ AffectsArrange/ AffectsRender/ Inherits etc.
  • Property changed callback delegates
  • Coercion values
  • Make a property un-animatable
  • Provide one of the UpdateSourceTrigger, such as PropertyChanged/ LostFocus/ Explicit etc.

By the use of a single FrameworkPropertyMetadata instance, we have very tight control about a lot of metadata for a DependencyProperty. We are now going to see some of this in action below, where we discuss callbacks and validation.

Dependency Property callbacks/validation and Coerce values

As I think you may be gathering, DPs are fairly complicated and powerful (Hulk like even). But the story isn't over yet, we have some more principles to go through. Which are the the principles of:

  • Callbacks, for when the DP changes
  • CoerceValue, to alter the DP value if it is deemed unacceptable
  • ValidateValue, to determine if a DP value is valid

Most of these are achieved though the use of delegates when you register the DP in the first place. As part of the attached solution (at the top of this article), there is a project entitled "Callback_Validation_DPs" which demonstrates these principles. Essentially, there is a class called Gauge which inherits from Control which has three DPs:

  • CurrentReading
  • MinReading
  • MaxReading

Each of the three DPs check for validity, and the CurrentReading value is checked against both MinReading and the MaxReading DPs, and Coerced if necessary. Let's see an example of each of these being applied.

The demo application when run, looks as follows:

Callbacks/Coerce, for when the DP changes

The CurrentReading DP is declared as follows:

public static readonly DependencyProperty CurrentReadingProperty = 
    DependencyProperty.Register(
    "CurrentReading",
    typeof(double),
    typeof(Gauge),
    new FrameworkPropertyMetadata(
        Double.NaN,
        FrameworkPropertyMetadataOptions.None,
        new PropertyChangedCallback(OnCurrentReadingChanged),
        new CoerceValueCallback(CoerceCurrentReading)
    ),
    new ValidateValueCallback(IsValidReading)
);

And in VB.NET:

Public Shared ReadOnly CurrentReadingProperty As DependencyProperty = 
    DependencyProperty.Register("CurrentReading", 
    GetType(Double), 
    GetType(Gauge), 
    New FrameworkPropertyMetadata([Double].NaN, 
    FrameworkPropertyMetadataOptions.None, 
    New PropertyChangedCallback(AddressOf OnCurrentReadingChanged), 
    New CoerceValueCallback(AddressOf CoerceCurrentReading)), 
    New ValidateValueCallback(AddressOf IsValidReading))

Notice how it declares a CoerceValueCallback object which has a delegate which points to the CoerceCurrentReading method, which is declared as follows, where CurrentReading is checked against the Min/Max DPs and coerced if required. Also note that PropertyChangedCallback that was declared has a delegate which points to the OnCurrentReadingChanged method, which in turn ensures that the Min/Max DPs are also coerced if required. Basically ensuring Min DP is actually below Max. And likewise for the Max DP. In this example, the Min/Max DPs are never actually changed, so there isn't much point doing these extra coercions, but I just wanted to show you how to do it, should you need to.

private static void OnCurrentReadingChanged(DependencyObject d, 
                    DependencyPropertyChangedEventArgs e)
{
    d.CoerceValue(MinReadingProperty);
    //invokes the CoerceValueCallback delegate ("CoerceMinReading")
    
    d.CoerceValue(MaxReadingProperty);
    //invokes the CoerceValueCallback delegate ("CoerceMaxReading")
}
...
...
...
/// <summary>
/// Coerce CurrentReading value if not within limits
/// </summary>
private static object CoerceCurrentReading(DependencyObject d, object value)
{
    Gauge g = (Gauge)d;
    double current = (double)value;
    if (current < g.MinReading) current = g.MinReading;
    if (current > g.MaxReading) current = g.MaxReading;
    return current;
}

And in VB.NET:

Private Shared Function CoerceCurrentReading(ByVal d As _
               DependencyObject, ByVal value As Object) As Object
    Dim g As Gauge = DirectCast(d, Gauge)
    Dim current As Double = CDbl(value)
    If current < g.MinReading Then
        current = g.MinReading
    End If
    If current > g.MaxReading Then
        current = g.MaxReading
    End If
    Return current
End Function

''' <summary>
''' Coerce CurrentReading value if not within limits
''' </summary>
Private Shared Sub OnCurrentReadingChanged(ByVal d As DependencyObject, 
    ByVal e As DependencyPropertyChangedEventArgs)
    d.CoerceValue(MinReadingProperty)
    'invokes the CoerceValueCallback delegate ("CoerceMinReading") 
    d.CoerceValue(MaxReadingProperty)
    'invokes the CoerceValueCallback delegate ("CoerceMaxReading") 
End Sub

What this does is ensure that the CurrentReading DP value is Coerced between the MinReading and MaxReading DPs, and also that Min < Max, and that Max > Min. So we should never get out of bounds values for any of the three DPs.

ValidateValue, to determine if a DP value is valid

Recall that the CurrentReading DP was declared as follows:

public static readonly DependencyProperty 
              CurrentReadingProperty = DependencyProperty.Register(
    "CurrentReading",
    typeof(double),
    typeof(Gauge),
    new FrameworkPropertyMetadata(
        Double.NaN,
        FrameworkPropertyMetadataOptions.None,
        new PropertyChangedCallback(OnCurrentReadingChanged),
        new CoerceValueCallback(CoerceCurrentReading)
    ),
    new ValidateValueCallback(IsValidReading)
);

And in VB.NET:

Public Shared ReadOnly CurrentReadingProperty As DependencyProperty = 
    DependencyProperty.Register("CurrentReading", 
    GetType(Double), 
    GetType(Gauge), 
    New FrameworkPropertyMetadata([Double].NaN, 
    FrameworkPropertyMetadataOptions.None, 
    New PropertyChangedCallback(AddressOf OnCurrentReadingChanged), 
    New CoerceValueCallback(AddressOf CoerceCurrentReading)), 
    New ValidateValueCallback(AddressOf IsValidReading))

Well, one of the values is to do with determining if the DP contains a valid value; this is achieved using a ValidateValueCallback delegate (IsValidReading(object value), in this case), which ensures that only valid values may be applied to the DP. Let's see this method:

public static bool IsValidReading(object value)
{
    Double v = (Double)value;
    return (!v.Equals(Double.NegativeInfinity) && 
            !v.Equals(Double.PositiveInfinity));
}

And in VB.NET:

Public Shared Function IsValidReading(ByVal value As Object) As Boolean
    Dim v As Double = CDbl(value)
    Return (Not v.Equals([Double].NegativeInfinity) 
    AndAlso Not v.Equals([Double].PositiveInfinity))
End Function

We're done

There is more to DPs, but this is the basics, and that's about it for this article. Hope you enjoyed it. If you liked it, please vote for it and leave a comment, and maybe read the next article in this series. Thanks!

References

History

  • 10/02/08: First version of the article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sacha Barber
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)
 
- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence
 
Both of these at Sussex University UK.
 
Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
GeneralHulk VS Spiderman PinmemberAndrea Pierangeli14-Nov-12 0:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141220.1 | Last Updated 11 Mar 2008
Article Copyright 2008 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid