Introduction
Have you ever needed, somehow, to have some information passed in to a UserControl
(or even a native control) and found yourself messing around with a CSV string on the Tag
property of that control?
I have and, after all, it's so easy to create additional properties to any control.
For demonstration purposes, I'll use a Button
but you can use this method on any control you like.
Let’s see how.
I'll be using Silverlight 4, VS 2010 and Blend 4.
The Demonstration Page
To demonstrate how it works, start by creating a Silverlight project named “PropertiesDemo
“ and add 3 Buttons
and a TextBox
to the MainPage
.
<UserControl x:Class="PropertiesDemo.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" >
<Grid x:Name="LayoutRoot" Background="White">
<Button Content="Powered Up Button" Height="23"
HorizontalAlignment="Left" Margin="10,10,0,0"
x:Name="Button1" VerticalAlignment="Top" Width="126" />
<Button Content="Value 1" Height="23"
HorizontalAlignment="Left" Margin="10,43,0,0"
Name="Button2" VerticalAlignment="Top" Width="61" />
<Button Content="Value 2" Height="23"
HorizontalAlignment="Left" Margin="77,43,0,0"
Name="Button3" VerticalAlignment="Top" Width="59" />
<TextBox Height="23" HorizontalAlignment="Left"
Margin="157,10,0,0" Name="TextBox1"
VerticalAlignment="Top" Width="120" />
</Grid>
</UserControl>
Using the Code
First of all, in order to create that extra property, we need to create a new Class
that inherits from the Button
class and make it a base class.
Add a new Class
file to the client project named “ButtonEx
” and replace the code with the below lines.
using System.Windows;
using System.Windows.Controls;
namespace PropertiesDemo
{
public class ButtonEx : Button
{
public ButtonEx() : base() { }
}
}
Since Button1
will be the powered up button, we need to reference this new class we just created in the UserControl
tag of the MainPage XAML
.
xmlns:my="clr-namespace:PropertiesDemo"
And replace the Button1
type tag “<Button
” with “<my:ButtonEx
”.
<my:ButtonEx Content="Powered Up Button" Height="23"
HorizontalAlignment="Left" Margin="10,10,0,0" x:Name="Button1"
VerticalAlignment="Top" Width="126" />
Now, we can create that extra property on the ButtonEx
class and it will become:
using System.Windows;
using System.Windows.Controls;
namespace PropertiesDemo
{
public class ButtonEx : Button
{
private static DependencyProperty ExtraPropProperty = DependencyProperty.Register
("ExtraProp", typeof(string), typeof(ButtonEx),
new PropertyMetadata("No extra prop set"));
public string ExtraProp
{
get { return (string)GetValue(ExtraPropProperty); }
set { SetValue(ExtraPropProperty, value); }
}
public ButtonEx() : base() { }
}
}
If we build the project at this point, the property will be available as any other property of the button.
Now for the Click
events.
Button1 Click
event will pop up a MessageBox
showing the ExtraProp
property value.
Button2
and Button3
will just change the value of the new property on Button1
.
<my:ButtonEx Content="Powered Up Button" Height="23"
HorizontalAlignment="Left" Margin="10,10,0,0" x:Name="Button1"
VerticalAlignment="Top" Width="126" Click="Button1_Click"/>
<Button Content="Value 1" Height="23" HorizontalAlignment="Left"
Margin="10,43,0,0" Name="Button2" VerticalAlignment="Top"
Width="61" Click="Button2_Click" />
<Button Content="Value 2" Height="23" HorizontalAlignment="Left"
Margin="77,43,0,0" Name="Button3" VerticalAlignment="Top"
Width="59" Click="Button3_Click" />
private void Button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Button1.ExtraProp.ToString());
}
private void Button2_Click(object sender, RoutedEventArgs e)
{
Button1.ExtraProp = "Hallo from first button";
}
private void Button3_Click(object sender, RoutedEventArgs e)
{
Button1.ExtraProp = "Bye from second button";
}
Hit F5 and try it out! Great hum!
Now, the binding part.
As I stated before, this new property is available as any other property and will be listed in the Property window of VS and Blend.
Replace the TextBox1
line on the MainPage XAML
with:
<TextBox Height="23" HorizontalAlignment="Left"
Margin="157,10,0,0" Name="TextBox1" VerticalAlignment="Top"
Width="120" Text="{Binding Path=ExtraProp, Mode=TwoWay, ElementName=Button1}" />
Hit F5 and enjoy.
The TwoWay
binding is fully functional and you can change the property value by changing the TextBox
content.
You can, of course, add as many DependencyProperties
as you like and of any type you need and use it in any other type of control.
I hope this has been useful to someone.
DependencyProperty - The Key Point
The DependencyProperty
, as I use it in this example, although simple, has a few things that must be done this way.
Basically, we’ll be registering a new property on the class using the Register
method of the DependencyProperty
class.
We have three options for that method. We'll be using the second one, “Register(String, Type, Type, PropertyMetadata)
”.
private static DependencyProperty ExtraPropProperty = DependencyProperty.Register
("ExtraProp", typeof(string), typeof(ButtonEx),
new PropertyMetadata("No extra prop set"));
Naming convention states that the DependencyProperty
’s name has to be the name of the registered property plus the suffix Property
.
So, if our new property will be ExtraProp
(the String
parameter on the Register
method), the DependencyProperty
's name will be ExtraPropProperty
.
The first Type
is the property type (our case, string
) and the second one the property owner (the class where this DP is to be created, in our case it’s the name of our base class, ButtonEx
).
The PropertyMetadata
is used to set the default value of our new property when the base class is initialized.
The Property Wrapper
public string ExtraProp
{
get { return (string)GetValue(ExtraPropProperty); }
set { SetValue(ExtraPropProperty, value); }
}
There isn’t much to it except that it has the same registered name of the DP and the get;set;
reference the created DP.
Change Notification
One great thing about DependencyProperty
is that INotifyPropertyChange
is built in it so the binding on the XAML
works great and gets notified and updated on any change of the DP’s value.
You can even add additional code to execute when the value changes.
One way to do it:
public string ExtraProp
{
get { return (string)GetValue(ExtraPropProperty); }
set
{
SetValue(ExtraPropProperty, value);
}
}
Two Warnings About Change Notification
The change notification is raised when, and only when (this is important), the DependencyProperty
’s value changes.
Works great when using native types (string
, int
, bool
, etc.).
But, when using complex types or user defined types, you have to be aware that only the changes on the class members raise notification changes. This means that the value of the DP doesn’t change, only one of its members, so the DP doesn’t raise any change notification event.
If you want the DP to raise the notification event, you have to bubble it from your user defined class.
See the Items_Class
class in this article as an example where I used that technique to bubble a notification change event from an item in an ObservableCollection
.
The other thing where you’ll be struggling with is when you use a DP in a UserControl
that will be used as an ItemTemplate
of a binded collection.
You’ll find out that the additional code you placed after the SetValue()
in the set;
of the property wrapper doesn’t run. I don’t have the knowledge to explain it, but it doesn’t.
This is where I use a variation of the Register()
method and use a second form of the PropertyMetadata
class, “PropertyMetadata(Object, PropertyChangedCallback)
”, and define a callback function that executes when the property’s value changes.
private static DependencyProperty ExtraPropProperty = DependencyProperty.Register("ExtraProp", typeof(string), typeof(ButtonEx), new PropertyMetadata("No extra prop set", new PropertyChangedCallback(ExtraPropChanged)));
private static void ExtraPropChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { (d as ButtonEx).ExtraPropChanged(e); }
private void ExtraPropChanged(DependencyPropertyChangedEventArgs e)
{
MessageBox.Show("Old value: " + (e.OldValue != null ?
e.OldValue.ToString() : "null") + "\n" + "New value: " +
(e.NewValue != null ? e.NewValue.ToString() : "null"));
}
More on DependencyProperty
Check these links for a deeper DependencyProperty
knowledge:
The Full Code
MainPage.xaml
<UserControl x:Class="PropertiesDemo.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:my="clr-namespace:PropertiesDemo"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" >
<Grid x:Name="LayoutRoot" Background="White">
<my:ButtonEx Content="Powered Up Button" Height="23"
HorizontalAlignment="Left" Margin="10,10,0,0" x:Name="Button1"
VerticalAlignment="Top" Width="126" Click="Button1_Click"/>
<Button Content="Value 1" Height="23"
HorizontalAlignment="Left" Margin="10,43,0,0" Name="Button2"
VerticalAlignment="Top" Width="61" Click="Button2_Click" />
<Button Content="Value 2" Height="23"
HorizontalAlignment="Left" Margin="77,43,0,0" Name="Button3"
VerticalAlignment="Top" Width="59" Click="Button3_Click" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="157,10,0,0"
Name="TextBox1" VerticalAlignment="Top" Width="120"
Text="{Binding Path=ExtraProp, Mode=TwoWay, ElementName=Button1}" />
</Grid>
</UserControl>
MainPage.cs
using System.Windows.Controls;
using System.Windows;
namespace PropertiesDemo
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void Button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Button1.ExtraProp.ToString());
}
private void Button2_Click(object sender, RoutedEventArgs e)
{
Button1.ExtraProp = "Hallo from first button";
}
private void Button3_Click(object sender, RoutedEventArgs e)
{
Button1.ExtraProp = "Bye from second button";
}
}
}
ButtonEx.cs
using System.Windows;
using System.Windows.Controls;
namespace PropertiesDemo
{
public class ButtonEx : Button
{
private static DependencyProperty ExtraPropProperty = DependencyProperty.Register
("ExtraProp", typeof(string), typeof(ButtonEx),
new PropertyMetadata("No extra prop set", new PropertyChangedCallback(ExtraPropChanged)));
public string ExtraProp
{
get { return (string)GetValue(ExtraPropProperty); }
set { SetValue(ExtraPropProperty, value); }
}
private static void ExtraPropChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e) { (d as ButtonEx).ExtraPropChanged(e); }
private void ExtraPropChanged(DependencyPropertyChangedEventArgs e)
{
MessageBox.Show("Old value: " + (e.OldValue != null ?
e.OldValue.ToString() : "null") + "\n" +
"New value: " + (e.NewValue != null ? e.NewValue.ToString() : "null"));
}
public ButtonEx() : base() { }
}
}
Conclusion
This is a very easy way to add extra properties to your controls and brings along an easy way to learn and use DependencyProperties
.
My advice: Use and abuse of DependencyProperties
. It can save you a lot of lines of code.
Please don't forget to vote and/or comment!
History
- 29-Jan-2014 - Added some explanation about
DependencyProperties