|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Services
Chapters
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
Preface And ThanksI 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 orginal 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 also goes out Karl Shifflett (AKA 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). Karls new series will be excellent and I urge you all to encourage Karl on this series. Its not easy obligating ones self to write an entire series in one language let alone 2. Karls 1st article is located right here, have a look for yourself. Personally I love it. IntroductionThis article is the 4th in my series of beginners 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, is 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 CLR properties are really just safe wrappers around a 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 acheived by the use of DPs.
Dependency PropertiesLike 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 inheritence/participation in binding/ participation in animation/participation in Styles/participation in Templates. Much the same way as there is an
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 what the registered callbacks are for. All of WPF's built in property wrapper 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 precedenceOne 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
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 beginners guide series. Dependency Property value inheritenceUsing DPs it is also possible to inherit values from a DP that is declared on a completely different element. In order to undestand 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 So that's 1/2 the mystery solved, the So starting out with the source of the
And if we check out the
MSDN page for the
In order to understand this further, lets continue our investigation. So far
we now know that the As it turns out
The reason that the 3rd So that's how the
Not that exciting right! But that's what you want when you are trying to learn something new, a small digestable chunk of information. So this demo app should do quite nicely. Lets start the disection. There are 2 important parts to this application, the code behind, where we actually declare the inherited DP, and the actual UI which uses the DP. Lets 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 2 classes, one is a subclass of
a 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 Ok so that's the code side of things lets 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 <local:MyStackPanel x:Name="myStackPanel" MinDate="{x:Static sys:DateTime.Now}">
And as the <local:MyCustomButton
Content="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=MinDate}"
Height="20"/>
I hope that little whirlwind tour helps you understand how property value inheritence 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 oustide of the current class. Recall
That's all good, but we can think of many uses for Attached Properties. For example here are some ideas/links that other folk 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, lets see what we need to do
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 the 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 Dependency Property metadata The But what does this class actually do for us. Well the answer is that whenever
we define Register/Add or Attach a
So it can be seen that by using
By the use of a single 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 aint over yet, we have some more principles to go
through. Which are the the principles 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 3 DPs:
CurrentReading value
is checked against both MinReading and the MaxReading
DPs, and Coerced if nesessary. 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 changesThe 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 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 it ensure that the ValidateValue, to determine if a DP value is validRecall that 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 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 DoneThere 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
History10/02/08
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||