How do I use WPF bindings with RelativeSource?
Asked Answered
P

14

670

How do I use RelativeSource with WPF bindings and what are the different use-cases?

Plenish answered 17/9, 2008 at 15:10 Comment(0)
S
868

If you want to bind to another property on the object:

{Binding Path=PathToProperty, RelativeSource={RelativeSource Self}}

If you want to get a property on an ancestor:

{Binding Path=PathToProperty,
    RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}

If you want to get a property on the templated parent (so you can do 2 way bindings in a ControlTemplate)

{Binding Path=PathToProperty, RelativeSource={RelativeSource TemplatedParent}}

or, shorter (this only works for OneWay bindings):

{TemplateBinding Path=PathToProperty}
Seyler answered 17/9, 2008 at 15:14 Comment(7)
For this one "{Binding Path=PathToProperty, RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}", it looks like it needs to have "Mode=FindAncestor," before "AncestorType"Megen
For what technology? In WPF, that is inferred when you specify an AncestorType.Seyler
I agree with @EdwardM. When I omit FindAncestor, before AncestorType, I get the following error: "RelativeSource is not in FindAncestor mode". (In VS2013, Community version)Lobe
@kmote, this has worked for me since .net 3.0, and I once again verified that it works this way in kaxaml... Again, what technology are you using? The XAML processor is different for WPF/Silverlight/UWP, so you may have different results on different technologies. You also mentioned VS Community, so maybe it is an IDE warning, but works at runtime?Seyler
Rebuilding in VS Community 2018 makes these errors (and a lot like it) go away.Fewness
Just wanted to note here that if you want to bind to a property in the DataContext of the RelativeSource then you must explicitly specify it: {Binding Path=DataContext.SomeProperty, RelativeSource=.... This was somewhat unexpected for me as a newbie when I was trying to bind to a parent's DataContext within a DataTemplate.Fanaticize
@kmote: I'm getting the same error, VS2017 with WPF/.NET 4.7.1 project.Joel
C
141
Binding RelativeSource={
    RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...

The default attribute of RelativeSource is the Mode property. A complete set of valid values is given here (from MSDN):

  • PreviousData Allows you to bind the previous data item (not that control that contains the data item) in the list of data items being displayed.

  • TemplatedParent Refers to the element to which the template (in which the data-bound element exists) is applied. This is similar to setting a TemplateBindingExtension and is only applicable if the Binding is within a template.

  • Self Refers to the element on which you are setting the binding and allows you to bind one property of that element to another property on the same element.

  • FindAncestor Refers to the ancestor in the parent chain of the data-bound element. You can use this to bind to an ancestor of a specific type or its subclasses. This is the mode you use if you want to specify AncestorType and/or AncestorLevel.

Cony answered 3/3, 2009 at 9:24 Comment(0)
D
138

Here's a more visual explanation in the context of a MVVM architecture:

enter image description here

Deemphasize answered 16/3, 2011 at 2:35 Comment(9)
did I miss something? How can you consider that a simple and clear graphic? 1: the boxes on the left's meaning aren't really related to the ones on the right (why is there a .cs file inside the ViewModel?) 2: at what do these DataContext arrows point? 3: why is the Message property not in the ViewModel1? and most importantly 5: Why do you need a RelativeSource Binding to get to the Window's DataContext if the TextBlock already has that same DataContext? I'm clearly missing something here so either I'm pretty dumb or this graphic isn't as simple and clear as everyone thinks! Please enlighten meWraf
@MarkusHütter The diagram is showing a group a nested Views and corresponding ViewModels. The DataContext of View1 is ViewModel1, but it wants to bind to a property of BaseViewModel. Because BaseViewModel is the DataContext of BaseView (which is a Window), it can do so by finding the first parent container which is a Window and taking its DataContext.Uniaxial
@MatthewCargille I know very well what it's supposed to mean, that wasn't my point. But put yourself in the position of someone who doesn't know XAML and MVVM well and you will see that this is not simple and clear.Wraf
I have to agree with @MarkusHütter, by the way, the binding on the left could be as simple as this: {Binding Message} (a bit more simple...)Delirium
@Delirium I don't think so, at least for my use case. I have a DataTemplate that needs to refer to the MainWindow's DataContext (my viewmodel class) to get a list of options for a dropdown menu (loaded from a database). The DataTemplate is bound to a model object that is also loaded from the database, but it only has access to the selected option. I had to explicitly set Path=DataContext.Message to get the binding to work. This makes sense, given that you can do relative bindings to width/height/etc. of a control.Fanaticize
@Fanaticize never encountered such a need while using bindings, but sounds useful to know!Delirium
@Delirium To bind to a DataContext? Yeah, it seems very specific. Most of the examples in answers for this question are dealing with bindings to properties of the controls themselves, but if you want to bind to a property in the DataContext you have to have that in the path (Window's don't have a Message property AFAIK). The thing that drove my need for this was to use a DataTemplate for duplicate entries of a given type on my UI (each one has many fields). In that case its binding must be to the object instance it maps to, but to get the enums I had to use the relative binding.Fanaticize
@Fanaticize Yes I do know that, the only thing that was new for me is the case that {Binding DataContext.DataContextProperty, ElementName=...} works while {Binding DataContextProperty} not. (I’m familiar with the use of the MVVM model and the concept of relative or data context bindings.) Window does not have such a property, indeed, had I assumed so?Delirium
The important part of this answer that I was gooling 10 mins for is that you need DataContext.YourProperty, since the relative source just binds you to the ancestor node (ie. window, grid etc).Tyronetyrosinase
A
59

Bechir Bejaoui exposes the use cases of the RelativeSources in WPF in his article here:

The RelativeSource is a markup extension that is used in particular binding cases when we try to bind a property of a given object to another property of the object itself, when we try to bind a property of a object to another one of its relative parents, when binding a dependency property value to a piece of XAML in case of custom control development and finally in case of using a differential of a series of a bound data. All of those situations are expressed as relative source modes. I will expose all of those cases one by one.

  1. Mode Self:

Imagine this case, a rectangle that we want that its height is always equal to its width, a square let's say. We can do this using the element name

<Rectangle Fill="Red" Name="rectangle" 
                Height="100" Stroke="Black" 
                Canvas.Top="100" Canvas.Left="100"
                Width="{Binding ElementName=rectangle,
                Path=Height}"/>

But in this above case we are obliged to indicate the name of the binding object, namely the rectangle. We can reach the same purpose differently using the RelativeSource

<Rectangle Fill="Red" Height="100" 
               Stroke="Black" 
               Width="{Binding RelativeSource={RelativeSource Self},
               Path=Height}"/>

For that case we are not obliged to mention the name of the binding object and the Width will be always equal to the Height whenever the height is changed.

If you want to parameter the Width to be the half of the height then you can do this by adding a converter to the Binding markup extension. Let's imagine another case now:

 <TextBlock Width="{Binding RelativeSource={RelativeSource Self},
               Path=Parent.ActualWidth}"/>

The above case is used to tie a given property of a given element to one of its direct parent ones as this element holds a property that is called Parent. This leads us to another relative source mode which is the FindAncestor one.

  1. Mode FindAncestor

In this case, a property of a given element will be tied to one of its parents, Of Corse. The main difference with the above case is the fact that, it's up to you to determine the ancestor type and the ancestor rank in the hierarchy to tie the property. By the way try to play with this piece of XAML

<Canvas Name="Parent0">
    <Border Name="Parent1"
             Width="{Binding RelativeSource={RelativeSource Self},
             Path=Parent.ActualWidth}"
             Height="{Binding RelativeSource={RelativeSource Self},
             Path=Parent.ActualHeight}">
        <Canvas Name="Parent2">
            <Border Name="Parent3"
            Width="{Binding RelativeSource={RelativeSource Self},
           Path=Parent.ActualWidth}"
           Height="{Binding RelativeSource={RelativeSource Self},
              Path=Parent.ActualHeight}">
               <Canvas Name="Parent4">
               <TextBlock FontSize="16" 
               Margin="5" Text="Display the name of the ancestor"/>
               <TextBlock FontSize="16" 
                 Margin="50" 
            Text="{Binding RelativeSource={RelativeSource  
                       FindAncestor,
                       AncestorType={x:Type Border}, 
                       AncestorLevel=2},Path=Name}" 
                       Width="200"/>
                </Canvas>
            </Border>
        </Canvas>
     </Border>
   </Canvas>

The above situation is of two TextBlock elements those are embedded within a series of borders and canvas elements those represent their hierarchical parents. The second TextBlock will display the name of the given parent at the relative source level.

So try to change AncestorLevel=2 to AncestorLevel=1 and see what happens. Then try to change the type of the ancestor from AncestorType=Border to AncestorType=Canvas and see what's happens.

The displayed text will change according to the Ancestor type and level. Then what's happen if the ancestor level is not suitable to the ancestor type? This is a good question, I know that you're about to ask it. The response is no exceptions will be thrown and nothings will be displayed at the TextBlock level.

  1. TemplatedParent

This mode enables tie a given ControlTemplate property to a property of the control that the ControlTemplate is applied to. To well understand the issue here is an example bellow

<Window.Resources>
<ControlTemplate x:Key="template">
        <Canvas>
            <Canvas.RenderTransform>
                <RotateTransform Angle="20"/>
                </Canvas.RenderTransform>
            <Ellipse Height="100" Width="150" 
                 Fill="{Binding 
            RelativeSource={RelativeSource TemplatedParent},
            Path=Background}">

              </Ellipse>
            <ContentPresenter Margin="35" 
                  Content="{Binding RelativeSource={RelativeSource  
                  TemplatedParent},Path=Content}"/>
        </Canvas>
    </ControlTemplate>
</Window.Resources>
    <Canvas Name="Parent0">
    <Button   Margin="50" 
              Template="{StaticResource template}" Height="0" 
              Canvas.Left="0" Canvas.Top="0" Width="0">
        <TextBlock FontSize="22">Click me</TextBlock>
    </Button>
 </Canvas>

If I want to apply the properties of a given control to its control template then I can use the TemplatedParent mode. There is also a similar one to this markup extension which is the TemplateBinding which is a kind of short hand of the first one, but the TemplateBinding is evaluated at compile time at the contrast of the TemplatedParent which is evaluated just after the first run time. As you can remark in the bellow figure, the background and the content are applied from within the button to the control template.

Aniela answered 19/10, 2013 at 20:34 Comment(1)
Very nice examples for me, used the Find Ancestor to tie to a command in the data context of a parent ListView. The parent has 2 more ListView levels below it. This helped me prevent passing data into each subsequent vm of each ListView's DataTemplateSeriate
G
41

In WPF RelativeSource binding exposes three properties to set:

1. Mode: This is an enum that could have four values:

a. PreviousData(value=0): It assigns the previous value of the property to the bound one

b. TemplatedParent(value=1): This is used when defining the templates of any control and want to bind to a value/Property of the control.

For example, define ControlTemplate:

  <ControlTemplate>
        <CheckBox IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
 </ControlTemplate>

c. Self(value=2): When we want to bind from a self or a property of self.

For example: Send checked state of checkbox as CommandParameter while setting the Command on CheckBox

<CheckBox ...... CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=IsChecked}" />

d. FindAncestor(value=3): When want to bind from a parent control in Visual Tree.

For example: Bind a checkbox in records if a grid,if header checkbox is checked

<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}, Path=DataContext.IsHeaderChecked, Mode=TwoWay}" />

2. AncestorType: when mode is FindAncestor then define what type of ancestor

RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}

3. AncestorLevel: when mode is FindAncestor then what level of ancestor (if there are two same type of parent in visual tree)

RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid, AncestorLevel=1}}

Above are all use-cases for RelativeSource binding.

Here is a reference link.

Galatia answered 21/1, 2016 at 13:41 Comment(1)
Awesome.. this worked for me: <DataGridCheckBoxColumn Header="Paid" Width="35" Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.SelectedBuyer.IsPaid, Mode=OneWay}" /> where I was trying to bind to the parent window's selectedbuyer.IsPaid propertyRemodel
S
30

I am constantly updating my research on Binding.

👉 Original Here

DataContext

DataContext is the DependencyProperty included in the FrameworkElement.
PresentationFramework.dll

namespace System.Windows
{
    public class FrameworkElement : UIElement
    {
        public static readonly DependencyProperty DataContextProperty;
        public object DataContext { get; set; }
    }
}

And, all UI Controls in WPF inherit the FrameworkElement class.

At this point in learning Binding or DataContext, you don't have to study FrameworkElement in greater depth.
However, this is to briefly mention the fact that the closest object that can encompass all UI Controls is the FrameworkElement.

DataContext is always the reference point for Binding.

Binding can directly recall values for the DataContext type format starting with the nearest DataContext.

<TextBlock Text="{Binding}" DataContext="James"/>

The value bound to Text="{Binding}" is passed directly from the nearest DataContext, TextBlock.
Therefore, the Binding result value of Text is 'James'.

  • Type integer
    When assigning a value to DataContext directly from Xaml, resource definitions are required first for value types such as Integer and Boolean. Because all strings are recognized as String.

    1. Using System mscrolib in Xaml

    Simple type variable type is not supported by standard.
    You can define it with any word, but mostly use sys words.

    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    
    2. Create YEAR resource key in xaml

    Declare the value of the type you want to create in the form of a StaticResource.

    <Window.Resources>
        <sys:Int32 x:Key="YEAR">2020</sys:Int32>
    </Window.Resources>
    ...
    <TextBlock Text="{Binding}" DataContext="{StaticResource YEAR"/>
    
  • All type of value
    There are very few cases where Value Type is binding directly into DataContext.
    Because we're going to bind an object.

    <Window.Resources>
        <sys:Boolean x:Key="IsEnabled">true</sys:Boolean>
        <sys:double x:Key="Price">7.77</sys:double>
    </Window.Resources>
    ...
    <StackPanel>
        <TextBlock Text="{Binding}" DataContext="{StaticResource IsEnabled}"/>
        <TextBlock Text="{Binding}" DataContext="{StaticResource Price}"/>
    </StackPanel>
    
  • Another type
    Not only String but also various types are possible. Because DataContext is an object type.

Finally...

In using Binding at WPF, most developers are not fully aware of the existence, function and importance of DataContext.
It may mean that Binding is being connected by luck.

Especially if you are responsible for or participating in a large WPF project, you should understand the DataContext hierarchy of the application more clearly. In addition, the introduction of WPF's various popular MVVM Framework systems without this DataContext concept will create even greater limitations in implementing functions freely.


Binding

  • DataContext Binding
  • Element Binding
  • MultiBinding
  • Self Property Binding
  • Find Ancestor Binding
  • TemplatedParent Binding
  • Static Property Binding

DataContext Binding

string property

<TextBox Text="{Binding Keywords}"/>

Element Binding

<CheckBox x:Name="usingEmail"/>
<TextBlock Text="{Binding ElementName=usingEmail, Path=IsChecked}"/>

MultiBinding

<TextBlock Margin="5,2" Text="This disappears as the control gets focus...">
  <TextBlock.Visibility>
      <MultiBinding Converter="{StaticResource TextInputToVisibilityConverter}">
          <Binding ElementName="txtUserEntry2" Path="Text.IsEmpty" />
          <Binding ElementName="txtUserEntry2" Path="IsFocused" />
      </MultiBinding>
  </TextBlock.Visibility>
</TextBlock>

### Self Property Binding
<TextBlock x:Name="txt" Text="{Binding ElementName=txt, Path=Tag}"/>

If you have to bind your own property, you can use Self Property Binding, instead of using Element Binding.
You no longer have to declare x:Name to bind your own property.

<TextBlock Text="{Binding RelativeSource={RelativeSource Self}, Path=Tag}"/>

### Find Ancestor Binding Imports based on the parent control closest to it.
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=Title}"/>

In addition to the properties of the controls found, the properties within the DataContext object can be used if it exists.

<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.Email}"/>

TemplatedParent Binding

This is a method that can be used within ControlTemplate, and you can import the control that is the owner of the ControlTemplate.

<Style TargetType="Button">
  <Setter Property="Template">
      <Setter.Value>
          <ControlTemplate TargetType="Button">
              <TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}"/>
          </ControlTemplate>
      </Setter.Value>
  </Setter>

You can access to all Property and DataContext.

<TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}"/>

Static Property Binding

You can access binding property value directly.

1. Declare static property.
namespace Exam
{
  public class ExamClass
  {
      public static string ExamText { get; set; }
  }
} 
2. Using static class in XAML.
<Window ... xmlns:exam="clr-namespace:Exam">
3. Binding property.
<TextBlock Text="{Binding exam:ExamClass.ExamText}"/>

Or, you can set Resource key like using Converter.

<Window.Resource>
  <cvt:VisibilityToBooleanConverter x:Key="VisibilityToBooleanConverter"/>
  <exam:ExamClass x:Key="ExamClass">
</Window.Resource>
...

<TextBlock Text="{Binding Source={StaticResource ExamClass}, Path=ExamText}"/>

I have never used the Static Property under normal circumstances. This is because data that deviates from its own DataContext can disrupt the flow of whole WPF applications and impair readability significantly. However, this method is actively used in the development stage to implement fast testing and functions, as well as in the DataContext (or ViewModel).


Bad Binding & Good Binding

✔️ If the property you want to bind is included in Datacontext,
      you don't have to use ElementBinding.

      Using ElementBinding through connected control is not a functional problem,
      but it breaks the fundamental pattern of Binding.

🙁 Bad Binding
<TextBox x:Name="text" Text="{Binding UserName}"/>
...
<TextBlock Text="{Binding ElementName=text, Path=Text}"/>
😀 Good Binding
<TextBox Text="{Binding UserName}"/>
...
<TextBlock Text="{Binding UserName}"/>

✔️ Do not use ElementBinding when using property belonging to higher layers control.

🙁 Bad Binding
<Window x:Name="win">
  <TextBlock Text="{Binding ElementName=win, Path=DataContext.UserName}"/>
  ...
😀 Good Binding
<Window>
  <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.UserName}"/>
  ...
😆 Great!
<Window>
  <TextBlock DataContext="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext}" 
             Text="{Binding UserName}"/>
  ...

✔️ Do not use ElementBinding when using your own properties.

🙁 Bad Binding
<TextBlock x:Name="txt" Text="{Binding ElementName=txt, Path=Foreground}"/>
😀 Good Binding
<TextBlock Text="{Binding RelativeSource={RelativeSource Self}, Path=Foreground}"/>
Stingaree answered 16/4, 2021 at 4:41 Comment(3)
Most underappreciated answer on this forum. Good job!Cathiecathleen
Thank you. Your support warmed my heart. :)Stingaree
You're welcome, let me know if you need a tissue :)Cathiecathleen
N
23

Don't forget TemplatedParent:

<Binding RelativeSource="{RelativeSource TemplatedParent}"/>

or

{Binding RelativeSource={RelativeSource TemplatedParent}}
Nichani answered 17/9, 2008 at 15:14 Comment(0)
H
18

I created a library to simplify the binding syntax of WPF including making it easier to use RelativeSource. Here are some examples. Before:

{Binding Path=PathToProperty, RelativeSource={RelativeSource Self}}
{Binding Path=PathToProperty, RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}
{Binding Path=PathToProperty, RelativeSource={RelativeSource TemplatedParent}}
{Binding Path=Text, ElementName=MyTextBox}

After:

{BindTo PathToProperty}
{BindTo Ancestor.typeOfAncestor.PathToProperty}
{BindTo Template.PathToProperty}
{BindTo #MyTextBox.Text}

Here is an example of how method binding is simplified. Before:

// C# code
private ICommand _saveCommand;
public ICommand SaveCommand {
 get {
  if (_saveCommand == null) {
   _saveCommand = new RelayCommand(x => this.SaveObject());
  }
  return _saveCommand;
 }
}

private void SaveObject() {
 // do something
}

// XAML
{Binding Path=SaveCommand}

After:

// C# code
private void SaveObject() {
 // do something
}

// XAML
{BindTo SaveObject()}

You can find the library here: http://www.simplygoodcode.com/2012/08/simpler-wpf-binding.html

Note in the 'BEFORE' example that I use for method binding that code was already optimized by using RelayCommand which last I checked is not a native part of WPF. Without that the 'BEFORE' example would have been even longer.

Hogue answered 6/8, 2012 at 18:12 Comment(1)
These kind of hand-holding exercises demonstrate the weakness of XAML; way too complicated.Sailer
L
17

It's worthy of note that for those stumbling across this thinking of Silverlight:

Silverlight offers a reduced subset only, of these commands

Lorenzen answered 24/4, 2010 at 16:2 Comment(1)
Yep, I was looking for SL support also. Vote it up: connect.microsoft.com/VisualStudio/feedback/details/480603/…Devour
P
16

Some useful bits and pieces:

Here's how to do it mostly in code:

Binding b = new Binding();
b.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, this.GetType(), 1);
b.Path = new PropertyPath("MyElementThatNeedsBinding");
MyLabel.SetBinding(ContentProperty, b);

I largely copied this from Binding Relative Source in code Behind.

Also, the MSDN page is pretty good as far as examples go: RelativeSource Class

Polynesian answered 30/10, 2012 at 11:22 Comment(1)
My vague memory of WPF is that doing bindings in code probably isn't typically the best thing though.Polynesian
R
12

I just posted another solution for accessing the DataContext of a parent element in Silverlight that works for me. It uses Binding ElementName.

Ribosome answered 23/8, 2010 at 13:5 Comment(0)
I
10

This is an example of the use of this pattern that worked for me on empty datagrids.

<Style.Triggers>
    <DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}}" Value="0">
        <Setter Property="Background">
            <Setter.Value>
                <VisualBrush Stretch="None">
                    <VisualBrush.Visual>
                        <TextBlock Text="We did't find any matching records for your search..." FontSize="16" FontWeight="SemiBold" Foreground="LightCoral"/>
                    </VisualBrush.Visual>
                </VisualBrush>
            </Setter.Value>
        </Setter>
    </DataTrigger>
</Style.Triggers>
Indeciduous answered 29/4, 2015 at 13:59 Comment(0)
S
10

I didn't read every answer, but I just want to add this information in case of relative source command binding of a button.

When you use a relative source with Mode=FindAncestor, the binding must be like:

Command="{Binding Path=DataContext.CommandProperty, RelativeSource={...}}"

If you don't add DataContext in your path, at execution time it can't retrieve the property.

Shoplifter answered 29/7, 2016 at 8:30 Comment(0)
H
6

If an element is not part of the visual tree, then RelativeSource will never work.

In this case, you need to try a different technique, pioneered by Thomas Levesque.

He has the solution on his blog under [WPF] How to bind to data when the DataContext is not inherited. And it works absolutely brilliantly!

In the unlikely event that his blog is down, Appendix A contains a mirror copy of his article.

Please do not comment here, please comment directly on his blog post.

Appendix A: Mirror of blog post

The DataContext property in WPF is extremely handy, because it is automatically inherited by all children of the element where you assign it; therefore you don’t need to set it again on each element you want to bind. However, in some cases the DataContext is not accessible: it happens for elements that are not part of the visual or logical tree. It can be very difficult then to bind a property on those elements…

Let’s illustrate with a simple example: we want to display a list of products in a DataGrid. In the grid, we want to be able to show or hide the Price column, based on the value of a ShowPrice property exposed by the ViewModel. The obvious approach is to bind the Visibility of the column to the ShowPrice property:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding ShowPrice,
                Converter={StaticResource visibilityConverter}}"/>

Unfortunately, changing the value of ShowPrice has no effect, and the column is always visible… why? If we look at the Output window in Visual Studio, we notice the following line:

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=ShowPrice; DataItem=null; target element is ‘DataGridTextColumn’ (HashCode=32685253); target property is ‘Visibility’ (type ‘Visibility’)

The message is rather cryptic, but the meaning is actually quite simple: WPF doesn’t know which FrameworkElement to use to get the DataContext, because the column doesn’t belong to the visual or logical tree of the DataGrid.

We can try to tweak the binding to get the desired result, for instance by setting the RelativeSource to the DataGrid itself:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding DataContext.ShowPrice,
                Converter={StaticResource visibilityConverter},
                RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"/>

Or we can add a CheckBox bound to ShowPrice, and try to bind the column visibility to the IsChecked property by specifying the element name:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding IsChecked,
                Converter={StaticResource visibilityConverter},
                ElementName=chkShowPrice}"/>

But none of these workarounds seems to work, we always get the same result…

At this point, it seems that the only viable approach would be to change the column visibility in code-behind, which we usually prefer to avoid when using the MVVM pattern… But I’m not going to give up so soon, at least not while there are other options to consider 😉

The solution to our problem is actually quite simple, and takes advantage of the Freezable class. The primary purpose of this class is to define objects that have a modifiable and a read-only state, but the interesting feature in our case is that Freezable objects can inherit the DataContext even when they’re not in the visual or logical tree. I don’t know the exact mechanism that enables this behavior, but we’re going to take advantage of it to make our binding work…

The idea is to create a class (I called it BindingProxy for reasons that should become obvious very soon) that inherits Freezable and declares a Data dependency property:

public class BindingProxy : Freezable
{
    #region Overrides of Freezable
 
    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxy();
    }
 
    #endregion
 
    public object Data
    {
        get { return (object)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }
 
    // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}

We can then declare an instance of this class in the resources of the DataGrid, and bind the Data property to the current DataContext:

<DataGrid.Resources>
    <local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>

The last step is to specify this BindingProxy object (easily accessible with StaticResource) as the Source for the binding:

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding Data.ShowPrice,
                Converter={StaticResource visibilityConverter},
                Source={StaticResource proxy}}"/>

Note that the binding path has been prefixed with “Data”, since the path is now relative to the BindingProxy object.

The binding now works correctly, and the column is properly shown or hidden based on the ShowPrice property.

Hafnium answered 5/9, 2017 at 10:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.