Click here to Skip to main content
13,198,322 members (52,582 online)
Click here to Skip to main content
Add your own
alternative version

Stats

9.3K views
277 downloads
26 bookmarked
Posted 6 Dec 2015

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

, 6 Dec 2015
Rate this:
Please Sign up or sign in to vote.
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

  1. Dependency Properties (DPs)
  2. Attached Properties (APs)
  3. Bindings

 

In the future I also plan to describe other very important but more complex concepts like

  1. MultiBindings
  2. Styles
  3. Data Templates
  4. Routed and Attached Events
  5. Behaviors
  6. 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:

  1. DPs have a default value, which is returned when a DP is not set on an object.
  2. 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 <code>Green</code>
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 -

  1. You have to write explicitly 'Path=' if front of the path expression (for usual properties and DPs one can omit 'Path=')
  2. 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:

  1. If no source object is specified, the Binding assumes that the source object is given by DataContext DP on the Binding's target object. (I call it 0.5 of a way to define the source object for the binding)
  2. The Binding's source object can be defined using ElementName property of the binding (it should be set to a name of an element tag within the same XAML file).
  3. 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 a Binding within a ControlTemplate).
  4. The Binding's source object can also be defined using Source property of the Binding. One can use the Source property to either get a WPF resource (using StaticResource markup extension) or a static property on some class (using x:Static markup extension) to be the source object for the Binding.

 

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:

  1. If the Binding's source object is not specified, DataContext of the target object is used as the source objects. (0.5 points)
  2. ElementName can be used to specify another named object within the same XAML file as the Binding's source.
  3. RelativeSource is used to specify the Binding's source within the same Visual Tree hierarchy. In particular
    1. Using AncestorType (and AncestorLevel) one can choose another object up the Visual Tree of a specified type to be the Binding's source.
    2. Using Self the source object will be the same as the target object.
    3. TemplatedParent can only be used in ControlTemplates. It will make the object whose template we are currently located within to be the source object.
  4. Binding's Source property allows to specify any resource or static object to be the source for the Binding.

 

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:

 

  1. Open the Snippets Manager by going to the "Tools" menu in Visual Studio and clicking on "Code Snippets Manager" menu item:
  2. Within the Snippets Manager, change the language to CSharp (using the drop down at the top) and click on "NetFX30" folder:
  3. Copy the snippet folder location from the Location field within the Snippets Manager. Paste this location into File Explorer.
  4. Copy the propdp and propa files that comes with this code into the snippet location.
  5. Restart your Visual Studio.

 

License

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

Share

About the Author

Nick Polyak
Architect AWebPros
United States United States
I have 15 years of experience developing enterprise software, starting from C++ and Java on UNIX and moving towards C# on Windows platforms.
I am fascinated by the new .NET technologies especially WPF, Silverlight and LINQ.
Recently I decided to make a move and start my own contracting consulting and mentoring company AWebPros.
I can be contacted via my web site awebpros.com or through my blog at nickssoftwareblog.com

You may also be interested in...

Pro
Pro

Comments and Discussions

 
-- There are no messages in this forum --
Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.171020.1 | Last Updated 6 Dec 2015
Article Copyright 2015 by Nick Polyak
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid