WPF Fundamental Concepts in Easy Samples Part 1: Dependency Properties, Attached Properties and Bindings






4.81/5 (19 votes)
WPF Fundamental Concepts Explained and Illustrated by Simple C#/XAML Samples
Introduction
WPF is an extremely powerful UI development package. Part of the reason for it power is that WPF architects came up with completely new concepts that give software development a new dimension. These concepts are not used outside of WPF and many people switching to programming WPF from other languages and packages might have difficulty understanding them in the beginning.
The purpose of this article is to teach these concepts and demonstrate their usage via very simple C#/WPF samples.
These concepts that I hope to elucidate here are
- Dependency Properties (DPs)
- Attached Properties (APs)
- Bindings
In the future I also plan to describe other very important but more complex concepts like
- MultiBindings
- Styles
- Data Templates
- Routed and Attached Events
- Behaviors
- Visual Trees
My purpose here is not to provide a full description of all the features of the described concepts, rather I'll concentrate on what I consider most important features that I myself use several times per day. The less important features can be learned by the developers on their own as they build the applications using WPF functionality.
This article is aimed at people who have some basic WPF knowledge but are looking to improve their WPF skills and practices. In particular I assume that people reading this article are familiar with XAML and to some degree have come across the above mentioned concepts, e.g. they might not have created any dependency properties themselves, but they are aware that such concept exists.
WPF Dependency Properties
Initial Discussion
Dependency properties (DPs) and Attached properties (APs) are important concepts introduced by WPF. Understanding how to uses them is central to becoming an efficient WPF developer.
First of all, when it comes to C#, the usage of DPs is not very different from using the usual properties aside from the following facts:
- DPs have a default value, which is returned when a DP is not set on an object.
- DPs can have a notification callback that fires every time a DP changes on an object.
Defining and Using DPs in C#
DPs usage in C# is demonstrated in DependencyPropertyCSharpUsageSample
project. The DP MouseOverBorderBrush
is defined in MyButton
class that extends Button
class. (Note that DPs and APs can only be set on the subclasses of DependencObject
class, and all the WPF controls and elements are descendants of DependencyObject
class):
The reason for extending the Button
class is the following: WPF Button
has three properties of type Brush
- Background
, BorderBrush
and Foreground
. These properties allow the user of Button
class to specify the button's background color, border color and text color correspondingly. We want to instroduce another property that would allow us to specify another, special, color - the color of the button's border when the mouse is over the button. Our new DP defined in MyButton
subclass serves precisely that aim:
public class MyButton : Button { #region MouseOverBorderBrush Dependency Property // wrapper that allows to access the // DP as if it is a usual property. public Brush MouseOverBorderBrush { get { return (Brush)GetValue(MouseOverBorderBrushProperty); } set { SetValue(MouseOverBorderBrushProperty, value); } } // static container that maps // the objects into their DP values. public static readonly DependencyProperty MouseOverBorderBrushProperty = DependencyProperty.Register ( "MouseOverBorderBrush", // DP name typeof(Brush), // DP type typeof(MyButton), // type that defines the DP new PropertyMetadata ( Brushes.Red, // default DP value OnMouseOverBorderBrushChanged // fires when the DP Changes ) ); private static void OnMouseOverBorderBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { // d - is the object on which // the DP is changing MyButton personControl = (MyButton)d; // the previous DP value on d object object oldValue = e.OldValue; // the current DP value on d object object newValue = e.NewValue; } #endregion MouseOverBorderBrush Dependency Property }
The DP itself is defined as a static variable MouseOverBorderBrushProperty
and the property MouseOverBorderBrush
provides the C# property get and set accessors to the DP values.
The default value and the change callback are set via the last argument to DependencyProperty.Register(...)
method:
new PropertyMetadata ( Brushes.Red, // default DP value OnMouseOverBorderBrushChanged // fires when the DP Changes )
Callback OnMouseOverBorderBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
is a static method whose first argument is the dependency object on which the property changes. The second argument contains the OldValue
and NewValue
of the DP on this object.
It is important to note that the callback fires only when a DP value changes on an object (if a DP is set on an object to the same value as before, the callback will not fire).
File MainWindow.xaml.cs contains an usage example of the DP from C#:
MyButton button = new MyButton(); // before the DP MouseOverBorderBrush is set // on the object button, // it will return the default value // Brushes.Red Brush mouseOverBorderBrush = button.MouseOverBorderBrush; // the DP value can be set and retrieved // in C# as if it is a usual property button.MouseOverBorderBrush = Brushes.Green; mouseOverBorderBrush = button.MouseOverBorderBrush;
You can run the project under the debugger with breakpoints and check the DP values. You'll see that before the DP is set the first time on an object it has the default value 'Red'. If you place a breakpoint in the DP callback you can see that it fires indeed when the DP changes.
Creating DPs in Code
As you see in order to define a DP, you need to specify the static field using the large DependencyProperty.Register(...)
method and property accessors. Luckily you do not have to type it all or even remember it all if you use propdp
snippet that comes with Visual Studio. The snippet does most of the work for you - you just need to specify several fields including the DP type, class name, DP container class name and the default value.
I also have my own propdp
snippet which is an improved version of the MS one. It wraps the DP declaration code with a C# region, it arranges it more vertically so that you can see it at once and it figures out the container class name automatically (saving you a bit of typing). Installation instructions for this snippet can be found in Appendix A: Installing the Custom Snippets that Come with the Code.
Brief Notes on the DPs Implementation
The static DP field is created by static DependencyProperty.Register(...)
method (in the sample above this field was called MouseOverBorderBrushProperty
).
This static field represents a dictionary (or a map - a hashtable) that maps the objects of type MyButton
to their DP values. If the object does not exist as a key to the map, the default DP value is returned.
You see that instead of the property value occupying the space of the object, the DP values are stored as Dictionary values pointed to by the object key. This is the so called 'sparse' implementation, very memory efficient in case each object has a lot of properties but each DP has the same (default) value on most objects. The advantage comes from the fact that unlike usual C# properties, the DP value on an object does not occupy any extra space unless it is different from the DP's default value.
DPs in XAML
DependencyPropertyXAMLUsageSample
gives an example of using a DP from XAML.
This sample contains the same class MyButton
as the one we described for the previous example. Here we show how to build the XAML template for MyButton
that would wire the new DP MouseOverBorderBrush
to display the border of this color when the mouse pointer is over the button.
If we run the sample and place the mouse over the button, we'll see the green 'MouseButtonOver' border:
Here is the important part of the XAML code located within MainWindow.xaml file:
<Window.Resources>
<BooleanToVisibilityConverter x:Key="TheBooleanToVisibilityConverter"/>
</Window.Resources>
<Grid>
<local:MyButton x:Name="TheButton"
Background="Black"
MouseOverBorderBrush="Green"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="100"
Height="25">
<!-- set the button's template -->
<local:MyButton.Template>
<ControlTemplate TargetType="local:MyButton">
<Grid>
<Border x:Name="BackgroundBorder"
Background="{TemplateBinding Background}"/>
<Border x:Name="MouseOverBorder"
BorderBrush="{TemplateBinding MouseOverBorderBrush}"
BorderThickness="4"
Visibility="{Binding Path=IsMouseOver,
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource TheBooleanToVisibilityConverter}}"/>
</Grid>
</ControlTemplate>
</local:MyButton.Template>
</local:MyButton>
</Grid>
You probably noticed that the Template
of the MyButton
object is defined within the <local:MyButton> ... </local:MyButton>
tag:
<local:MyButton x:Name="TheButton" ...>
<!-- set the button's template -->
<local:MyButton.Template>
<ControlTemplate TargetType="local:MyButton">
...
</ControlTemplate>
</local:MyButton.Template>
</local:MyButton>
This is because XAML (unlike plain XML) allows compound attributes - Button
's Template
attribute is defined as a compound XAML object. In C#, the above code is equivalent to
MyButton TheButton = new MyButton { Template = new ControlTemplate{ ... }};
The best practice it to define a template within some Resources
section of XAML (perhaps even is a different file) and then use StaticResource
markup extension to connect to it:
<Button ... Template={StaticResource TheButtonTemplate} />
since, in that case, we can re-use the same template is multiple places. Here, however, we define the template in-line for the sake of clarity.
Take a closer look at what is inside the ControlTemplate
:
<Grid>
<Border x:Name="BackgroundBorder"
Background="{TemplateBinding Background}"/>
<Border x:Name="MouseOverBorder"
BorderBrush="{TemplateBinding MouseOverBorderBrush}"
BorderThickness="4"
Visibility="{Binding Path=IsMouseOver,
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource TheBooleanToVisibilityConverter}}"/>
</Grid>
We have a Grid
panel with two borders - the first border provides the Background
and the second MouseOver
border for the button.
The Grid
panel and the Border
objects inside it are automatically assuming the Button
's width and height - this is because the default HorizontalAlignment
and VerticalAlignment
properties on them are set to Stretch
.
The Background
property of the first Border
is bound to the Background
property of the Button
control via TemplateBinding
markup extension. This means, that the Background
of the border will be the same as the Background
set on the whole button.
Similarly, the BorderBrush
property of the second Border
is bound to the Button
's MouseOverBorderBrush
DP that we created.
The Visibility
property of MouesOverBordere
is controlled by IsMouseOver
property via another Binding
so that this border is only visible when the mouse pointer is over the button.
The button itself sets the Background
to Black
and MouseOverBorderBrush
to Green
:
<local:MyButton x:Name="TheButton"
Background="Black"
MouseOverBorderBrush="Green"
... >
This is why the background of the our button is black and the border (when the mouse pointer is over it) - green;
Attached Properties
APs vs DPs
Attached Properties (or APs) are very similar to the DPs, but are defined outside of the class in which they are used. Moreover AP values can be set on objects that do not know anything about those APs (the only restriction is that they derive from a DependencyObject
class).
Remember, when we talked about DP implementation, we mentioned that an object can find its DP value because there is a static Dictionary
(or Map
or Hashtable
- whichever you are familiar better with) unique for this DP, in which the objects are the keys and the DP values on those objects are the values. Setting a non-default DP value on an object means inserting a key/value pair into such static Dictionary corresponding to the DP. Retrieving an object's DP value means we check if the DP's Dictionary
has the object as a key - if it does - it returns the corresponding value, if it does not, it returns the DP's default value.
Note that nothing prevents defining such Dictionary
outside of the object's class. This is exactly how APs work.
The most interesting thing about the APs is that they are completely non-invasive - i.e. any AP value can be added to an object of any class (as long as it is a subclass of DependencyObject
) without any modifications to the object's class itself.
Defining and Using APs in C#
A sample illustrating how to use an AP in C# is located under AttachedPropertyCSharpUsageSample
project.
Static class AttachedProps
defines the AP:
public static class AttachedProps { #region MouseOverBorderBrush attached Property // static getter that allows getting // MouseOverBorderBrush value from an object public static Brush GetMouseOverBorderBrush(DependencyObject obj) { return (Brush)obj.GetValue(MouseOverBorderBrushProperty); } // static setter that allows setting // MouseOverBorderBrush value on an object public static void SetMouseOverBorderBrush(DependencyObject obj, Brush value) { obj.SetValue(MouseOverBorderBrushProperty, value); } // static container that maps // the objects into their DP values. public static readonly DependencyProperty MouseOverBorderBrushProperty = DependencyProperty.RegisterAttached ( // AP Name "MouseOverBorderBrush", // AP Type typeof(Brush), // AP is defined within AttachedProps class typeof(AttachedProps), // Default AP Brush is Red // static change callback method OnMouseOverBorderBrushChanged fires // when the value of the MouseOverBorderBrush changes. new PropertyMetadata(Brushes.Red, OnMouseOverBorderBrushChanged) ); // static AP change callback method private static void OnMouseOverBorderBrushChanged ( DependencyObject d, DependencyPropertyChangedEventArgs e ) { // object on which the Attached Property MouseOverBorderBrush changed DependencyObject objectOnWhichTheAPMouseOverBorderBrushChanges = d; // old MouseOverBorderBrush value on the d object Brush oldMouseOverBorderBrushValue = e.OldValue as Brush; // new (current) MouseOverBorderBrush value on d object Brush currentMouseOverBorderBrushValue = e.NewValue as Brush; // e.NewValue always equals to the current AP value on object d // (which can be obtained by using static method AttachedProps.GetMouseOverBorderBrush(d) bool alwaysTrue = (currentMouseOverBorderBrushValue == GetMouseOverBorderBrush(d)); } #endregion MouseOverBorderBrush attached Property }
Note that unlike the DPs (defined in the class that uses them), the AP is defined in a static class which has nothing to do with the object on which the APs are set.
The AP usage is demonstrated in MainWindow.xaml.cs file within MainWindow()
constructor:
public MainWindow() { InitializeComponent(); // gets the AP MouseOverBorderBrush default value Red // since the AP has never been set on TheButton object // before. Brush focusBrush = AttachedProps.GetMouseOverBorderBrush(TheButton); if (focusBrush != Brushes.Red) throw new Exception("It just cannot happen"); // set the MouseOverBorderBrush on TheButton object to Green AttachedProps.SetMouseOverBorderBrush(TheButton, Brushes.Green); // retrieve the new Brush (Green) from object TheButton focusBrush = AttachedProps.GetMouseOverBorderBrush(TheButton); if (focusBrush != Brushes.Green) throw new Exception("It just cannot happen"); }
Button TheButton
is a control defined within MainWindow.xaml file. Before the attached property has been set on TheButton
object, the static getter method run on this object will return Red
brush (the AP's default):
Brush focusBrush = AttachedProps.GetMouseOverBorderBrush(TheButton); if (focusBrush != Brushes.Red) throw new Exception("It just cannot happen");
Then we use the static setter method to set the MouseOverBorderBrush
AP to Green
on the same object TheButton
:
// set the MouseOverBorderBrush on TheButton object to Green
AttachedProps.SetMouseOverBorderBrush(TheButton, Brushes.Green);
Finally, we use the static getter method again to verify that the AP's value had been set to Green
:
// retrieve the new Brush (Green) from object TheButton focusBrush = AttachedProps.GetMouseOverBorderBrush(TheButton); if (focusBrush != Brushes.Green) throw new Exception("It just cannot happen");
If you place a breakpoint within the AP's static callback method AttachedProps.OnMouseOverBorderBrushChanged(...)
, you'll see that it is hit every time after the AP value changes.
Using Snippets to Create an AP in Code
Same as for DPs, creating APs in C# code is much easier using propa
snippet built into the Visual Studio.
I have my own, improved version of propa
snippet which is located under the SNIPPETS folder. For installation instructions, please check Appendix A: Installing the Custom Snippets that Come with the Code.
APs in XAML
Sample AttachedPropertyXAMLUsageSample
illustrates using APs in XAML code.
If you run the sample you'll get exactly the same behavior as in case of DP in XAML sample: we get a black button in the middle of the window and when the mouse pointer is placed over the button, it gets a thick green border.
In the DP XAML example, we had to create a class MyButton
extending the WPF Button
control. MyButton
class would define the extra DP that allows adding a brush for specifying the button's mouse-over border color.
APs, however, allow us to obtain the same result without introducing a new button class because an AP value can be set on an object non-invasively.
As in previous sample, AP MouseOverBorderBrush
is defined within static AttachedProps
class.
Here is the XAML code that uses this AP in order to create green mouse-over border:
<Window.Resources>
<BooleanToVisibilityConverter x:Key="TheBooleanToVisibilityConverter" />
</Window.Resources>
<Grid>
<Button x:Name="TheButton"
Background="Black"
local:AttachedProps.MouseOverBorderBrush="Green"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="100"
Height="25">
<!-- set the button's template -->
<Button.Template>
<ControlTemplate TargetType="Button">
<Grid>
<Border x:Name="BackgroundBorder"
Background="{TemplateBinding Background}" />
<Border x:Name="MouseOverBorder"
BorderBrush="{TemplateBinding local:AttachedProps.MouseOverBorderBrush}"
BorderThickness="4"
Visibility="{Binding Path=IsMouseOver,
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource TheBooleanToVisibilityConverter}}" />
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
We use the AP local:AttachedProps.MouseOverBorderBrush
to pass the BorderBrush
value from the Button
level to the Border
while neither the button nor the border classes are aware of the AP's code. This is true non-invasiveness!.
As was mentioned above, TemplateBinding
markup extension replaces a plain Binding
which has its RelativeSource
mode set to TemplatedParent
. There are two important things that you have to remember when you use Binding
with APs in XAML -
- You have to write explicitly 'Path=' if front of the path expression (for usual properties and DPs one can omit 'Path=')
- The AP part of the path expression should be in parenthesis:
BorderBrush="{Binding Path=(local:AttachedProps.MouseOverBorderBrush),
RelativeSource={RelativeSource TemplatedParent}}"
Bindings
Introductory Discussion
WPF Binding
creates a relationship between two properties (the source and the target property). The source property is taken with respect to the source object (it can be given by a complex path that specifies a property within a property etc... with the root of the path being the source object). The target property is always a dependency or attached property on the target object.
Most often the Binding
changes the target property on the target object when the source property changes. Sometimes there is a two way binding i.e. source or target property change causes the target or source property change correspondingly.
There are also two more binding modes - OneWayToSource
and OneTime
- they are not frequently used.
The path links of the source property can be either usual or dependency or attached properties, while the target property can only be a dependency or attached property on the target object.
If usual properties are used as source path links, they need to fire INotifiedPropertyChanged.PropertyChanged
event in order for the target property to be updated on the corresponding change.
WPF Binding
is always defined on the target object.
There are 3.5 ways to specify the source object for the WPF Binding
:
- If no source object is specified, the
Binding
assumes that the source object is given byDataContext
DP on theBinding
's target object. (I call it 0.5 of a way to define the source object for the binding) - The
Binding
's source object can be defined usingElementName
property of the binding (it should be set to a name of an element tag within the same XAML file). - The source object can be specified using
RelativeSource
property of the binding to point to either an element up the visual tree (an ancestor) or the target object itself or to the templated parent (works only for aBinding
within aControlTemplate
). - The
Binding
's source object can also be defined usingSource
property of theBinding
. One can use theSource
property to either get a WPF resource (usingStaticResource
markup extension) or a static property on some class (usingx:Static
markup extension) to be the source object for theBinding
.
The WPF Binding
tries to set the value on the target property initially whenever it is set on the target object and the source object is located. After that it forces the changes based on source and target change notifications.
Converter
property of the Binding
allows to plug in a converter for the Binding
for converting the source value to the target value (and possibly, in case of two-way binding) also vice versa. Binding
's Converter
should implement IValueConverter
interface.
Sample Covering Different Ways of Choosing the Binding's Source Object
The example detailing 3.5 methods of specifying the source object within the WPF Binding
that we discussed above is located under ChoosingBindingSourceObjectSample
project.
These methods are listed below:
- If the
Binding
's source object is not specified,DataContext
of the target object is used as the source objects. (0.5 points) ElementName
can be used to specify another named object within the same XAML file as theBinding
's source.RelativeSource
is used to specify theBinding
's source within the same Visual Tree hierarchy. In particular- Using
AncestorType
(andAncestorLevel
) one can choose another object up the Visual Tree of a specified type to be theBinding
's source. - Using
Self
the source object will be the same as the target object. TemplatedParent
can only be used inControlTemplate
s. It will make the object whose template we are currently located within to be the source object.
- Using
Binding
'sSource
property allows to specify any resource or static object to be the source for theBinding
.
Note that if we count all the sub-modes of the RelativeSource
mode, we'll come up with 3 modes (instead of 1) totaling at 5.5 (or 6 if we consider the absence of source object specification to be a full case).
If you run the sample, you'll see that it actually consists of 5 binding samples - I omitted RelativeSource
with TemplatedParent
mode because it has been described above, in the previous DP and AP examples.
The 5 samples are placed vertically one under another.
Below, I'll describe each sample separately:
Not Specifying the Binding's Source Object (defaulting to DataContext as the Source Object)
Here is the image of what you see if you run the sample:
Every time you press the button, another "Hello" will be added to the end of the string.
This is achieved with the help of a very simple class that I use as the DataContext
- DataContextSampleObject
:
public class DataContextSampleObject : INotifyPropertyChanged { #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; #endregion protected void OnPropertyChanged(string propertyName) { if (this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } #region TheString Property private string _theString = ""; public string TheString { get { return this._theString; } set { if (this._theString == value) { return; } this._theString = value; this.OnPropertyChanged("TheString"); } } #endregion TheString Property public void ExtendString() { TheString += "Hello "; } }
This class has only one property TheString
and one public method ExtendString()
that appends the string "Hello " to the end of the current string. Originally the string is set to be empty.
I create the object of this class within MainWindow
's constructor and assign it to be the data context of the grid that contains the sample's XAML:
public MainWindow() { InitializeComponent(); // create the data context object _dataContextObject = new DataContextSampleObject(); // assign the object to the grid // that contain DataContext related sample DataContextBindingGrid.DataContext = _dataContextObject; // register the handler to append the dataContext's // object's string on button click ChangeDataContextButton.Click += ChangeDataContextButton_Click; }
I also register the callback to append the string when the button is clicked:
private void ChangeDataContextButton_Click(object sender, RoutedEventArgs e) { _dataContextObject.ExtendString(); }
The DataContext
property is propagated down the Visual Tree from parents to children, so all the items contained within the DataContextBindingGrid
(including the TextBlock
we are working with) also get the same DataContext
.
And here is the relevant XAML code:
<StackPanel HorizontalAlignment="Left"
Orientation="Horizontal"
Margin="0,10,0,0">
<Button x:Name="ChangeDataContextButton"
Width="100"
Height="25"
Content="Click Me"
VerticalAlignment="Bottom"/>
<!-- binding path is specified with
respect to the data context object
of the TextBlock -->
<TextBlock Text="{Binding Path=TheString}"
Margin="10,0"
VerticalAlignment="Bottom"/>
</StackPanel>
The key part of XAML code is Text="{Binding Path=TheString}"
- we do not specify the source object, so, the Binding
knows it should look for the property specified by the path with respect to the DataContext
object of the binding's target (and the target object is, of course, the object on which the binding is defined - the TextBlock
object).
Every time we click on the button, the text of the data context object gets updated and the display text also gets updated via the binding.
Using ElementName Property for Specifying the Binding's Source Object
When you enter the text in the TextBox
you will see the same text under the TextBox
. This is achieved because the TextBlock
's Text
property is bound to the Text
property of the TextBox
object. The TextBox
is chosen to be the source object because the Binding
's ElementName
property is pointing to the TextBox
object by its name:
<StackPanel>
...
<Grid Margin="0,3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="EnterText: "
VerticalAlignment="Center"/>
<TextBox x:Name="TheTextBox"
Grid.Column="1"
BorderBrush="Black"
BorderThickness="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"/>
</Grid>
<TextBlock Text="{Binding Path=Text, ElementName=TheTextBox}"/>
</StackPanel>
The key code here is Text="{Binding Path=Text, ElementName=TheTextBox}"
and the fact that the TextBox
'es name is TheTextBox
.
Choosing the Binding's Source Object via RelativeSource Property
This is perhaps the most useful method of choosing the source for a Binding
. You can use RelativeSource
property with AncestorType
set to a certain type. In that case the WPF Binding
is looking for the first object of that type up the Visual Tree from the Binding's
target. This object becomes the source object for the binding. If AncestorLevel
is also specified, you can make the second or N-th object up the Visual Tree of the required type to be the source (but AncestorLevel
can lead to some difficult errors and it should be used sparingly).
When you run the sample - all you get is the title and "Hello World" string:
Here is the XAML code for the sample:
<StackPanel Margin="0,10,0,0"
Grid.Row="2"
Tag="Hello World">
<TextBlock Text="Choosing the Binding's Source via RelativeSource Property with AncestorType:"
FontSize="15"
FontWeight="Bold" />
<TextBlock Text="{Binding Path=Tag,
RelativeSource={RelativeSource AncestorType=StackPanel}}"
Margin="0,10,0,0"/>
</StackPanel>
We set the Tag
property on the StackPanel
to contain string "Hello World". Then we use RelativeSource={RelativeSource AncestorType=StackPanel}
to set the Binding
's source object to be the first StackPanel
up the Visual Tree and we use Path=Tag
to specify that we need to connect to the Tag
property on the source object.
Choosing the Binding's Source to be the same as the Target via RelativeSource Property with Self Mode
You remember that when the Binding
's source object is not specified, it defaults to the DataContext
property of the Binding
's target.
Sometimes, however, we want the Binding
's source to be the same as the Binding
's target. In this case, we can use RelativeSource
property with Self
attribute.
When you run the corresponding sample - you only see the title and the "0,10,0,0" margin:
The XAML code for this sample is the simplest:
<TextBlock Text="{Binding Path=Margin,
RelativeSource={RelativeSource Self}}"
Margin="0,10,0,0" />
The Binding
above connects the Text
to the Margin
property on the TextBlock
itself.
Using Binding's Source Property
This method of choosing the Binding
's source object is probably the least common, but sometimes can be useful for prototyping when e.g. some View Model objects are defined as resources.
The corresponding sample only shows the title and "Hello World" string:
In the XAML code, we refer to a resource TheObject
defined at StackPanel
level, using the Binding
's Source
property:
<StackPanel Margin="0,10,0,0"
Grid.Row="4">
<StackPanel.Resources>
<local:DataContextSampleObject x:Key="TheObject"
TheString="Hello World"/>
</StackPanel.Resources>
<TextBlock Text="Choosing the Binding's Source using Source Property:"
FontSize="15"
FontWeight="Bold" />
<TextBlock Text="{Binding Path=TheString,
Source={StaticResource TheObject}}"
Margin="0,10,0,0" />
</StackPanel>
Some Discussion About the Binding's Path
Everyone knows that in order to get a binding to react to some plain (non-DP and non-AP) property changes, the object containing that property should implement INotifyPropertyChanged
interface and the property should fire INotifyPropertyChangted.PropertyChanged
event on change.
What many people do not know, however, is that if the property is read only (never changes) the Binding
will be able to pick up its value at the time when the Binding
's source object is populated without any need to the source property to fire the PropertyChanged
event or for the object containing that property to implement INotifyPropertyChanged
interface.
SimpleBindingPathSample
project was created specifically to prove this point.
Here is what you see when you run the sample:
When you start the application you will only see "Original Value " text at the top of the window. Every time you click on the button, "Hello " string will be appended to the text.
Its DataContextSampleObject
class is almost exactly the same as that of the DataContext part of the previous sample. The only difference is that _theString
field (and corresponding TheString
property) has an initial value "Original Value ":
private string _theString = "Original Value ";
The C# code-behind file MainWindow.xaml.cs contains the code that sets the DataContext
on the whole window to the DataContextSampleObject
and it also wires the button's call back to call ExtendString()
method on the data object:
public partial class MainWindow : Window { DataContextSampleObject _dataObject = new DataContextSampleObject(); public MainWindow() { InitializeComponent(); this.DataContext = _dataObject; ExtentStringButton.Click += ExtentStringButton_Click; } private void ExtentStringButton_Click(object sender, RoutedEventArgs e) { _dataObject.ExtendString(); } }
Here is the relevant part of XAML:
<TextBlock Text="{Binding Path=TheString}"/>
<Button x:Name="ExtentStringButton"
Grid.Row="1"
Width="120"
Height="25"
Content="Extend String"/>
If we comment out the firing of PropertyChanged
event from the DataContextSampleObject
class, we will still have the original string displayed, even though nothing will change when we press the button.
Sample Converting Complex Binding Path
The source path of the WPF Binding
can contain more than one link. Besides the path links can vary - they can be plain properties, APs and DPs.
ComplexPathSample
shows how to bind to a path consisting of two links - an attached property and a plain property.
When you run this sample you will see window with "Hello World" text in it:
The sample contains a very simple class NonVisualClass
with one property TheString
:
public class NonVisualClass { public string TheString { get; set; } }
Static class AttachedProperties
defines a single attached property called TheObject
of type object
:
public static object GetTheObject(DependencyObject obj) { return (object)obj.GetValue(TheObjectProperty); } public static void SetTheObject(DependencyObject obj, object value) { obj.SetValue(TheObjectProperty, value); } public static readonly DependencyProperty TheObjectProperty = DependencyProperty.RegisterAttached ( "TheObject", typeof(object), typeof(AttachedProperties), new PropertyMetadata(null) );
The rest of the code is XAML within MainWindow.xaml file:
<Grid>
<!-- Define the attached property
TheObject on the Grid
to contain the NonVisualClass object
whose TheString property is set to
"Hello World" -->
<local:AttachedProperties.TheObject>
<local:NonVisualClass TheString="Hello World"/>
</local:AttachedProperties.TheObject>
<TextBlock Text="{Binding Path=(local:AttachedProperties.TheObject).TheString,
RelativeSource={RelativeSource AncestorType=Grid}}"/>
</Grid>
We define the AP TheObject
on the Grid
to contain a NonVisualClass
object whose TheString
property is set to "Hello World". Then we bind the TextBlock
's Text
property to it. We use RelativeSource
with AncestorType
attribute to set the Binding
's source to the Grid
object. Then we use the path (local:AttachedProperties.TheObject).TheString
to specify that we are searching for the value of TheString
property taken on the object pointed by (local:AttachedProperties.TheObject)
Attached Property. Note that the AP's path within a Binding
should always be in parenthesis.
Binding Converter Sample
Another very important feature of the Binding
is its ability to convert the source value to the value we need at the target and (in case of the two way binding or one way to source binding) also vice versa. The conversion can be between different types or within the same type.
BindingConverterSample
shows how to convert a Boolean
value to UIElement.Visibility
value (which is an enum
type).
Here is what you see when running the application and clicking the check box:
If you uncheck the checkbox, the text above it disappears.
The converter class BoolToVisibilityConverter
implements IValueConverter
interface (as every binding converter should)
public class BoolToVisibilityConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (!(value is bool)) return Visibility.Collapsed; bool boolValue = (bool)value; if (boolValue) return Visibility.Visible; else return Visibility.Collapsed; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
It checks the input value and if it is Boolean, it returns Visiblity.Visible
for true and Visibility.Collapsed
for false.
The ConvertBack
method is left unimplemented. We only need to implement it in case of TwoWay
or OneWayToSource
binding modes.
Here is how we use the conversion in XAML:
<Window.Resources>
<local:BoolToVisibilityConverter x:Key="TheBoolToVisibilityConverter"/>
</Window.Resources>
<StackPanel>
<TextBlock Text="Hello World"
Visibility="{Binding Path=IsChecked,
Converter={StaticResource TheBoolToVisibilityConverter},
ElementName=TheTextVisibilityCheckBox}"/>
<CheckBox x:Name="TheTextVisibilityCheckBox"
Content="Is Text Visible"/>
</StackPanel>
The converter converts boolean IsChecked
property to Visibility
enumeration making the text appear if the button is checked and disappear if it is unchecked.
BTW, there is a built-in WPF BooleanToVisibilityConverter
class, but I created my own imitation in order to explain the conversion code.
Binding's StringFormat Property
Many times the WPF Binding
's output is a string. For such cases, Binding
has StringFormat
property that allows to format the output string using the full potential of C# formatting for numbers, dates/times etc.
Running StringFormatSample
will show the following window:
The same number is formatted as a number with each 3 integer digits separated by commas and two decimals at the top and as a percentage number at the bottom.
Here is the corresponding XAML code:
<Window x:Class="StringFormatSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow"
Height="350"
Width="525">
<Window.Resources>
<sys:Double x:Key="TheNumber">123456.789</sys:Double>
</Window.Resources>
<StackPanel>
<TextBlock Text="{Binding Source={StaticResource TheNumber},
StringFormat='The number is \{0:#,#.##\}'}"/>
<TextBlock Text="{Binding Source={StaticResource TheNumber},
StringFormat='The % is \{0:#,#.##%\}'}" />
</StackPanel>
</Window>
Note that when we add '%' sign to the format it automatically multiplies the number by 100.
Conclusion
In this article I tried to explain and provide samples for fundamental WPF concepts like Dependency Properties, Attached Properties and Bindings.
In the next installment I plan to cover some more complex, but still very important WPF concepts, including the MultiBindings, the Styles, the DataTemplates, the Routed and Attached Events, the Behaviors and the Visual Tree Navigation.
Appendix A: Installing the Custom Snippets that Come with the Code.
You can install the custom propdp and propa snippets that comes with the code (in CODE/SNIPPETS folder) by going through the following steps:
- Open the Snippets Manager by going to the "Tools" menu in Visual Studio and clicking on "Code Snippets Manager" menu item:
- Within the Snippets Manager, change the language to CSharp (using the drop down at the top) and click on "NetFX30" folder:
- Copy the snippet folder location from the Location field within the Snippets Manager. Paste this location into File Explorer.
- Copy the propdp and propa files that comes with this code into the snippet location.
- Restart your Visual Studio.