Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

Binding on a Property which is not a DependencyProperty

4.75/5 (8 votes)
8 Apr 2010Ms-PL2 min read 2  
A lot of controls expose properties which are not DependencyProperties and then you can’t put a binding on it. On some other cases, you only have a getter as accessor and you can’t put a binding on it too...

A lot of controls expose properties which are not DependencyProperties and then you can't put a binding on it. In some other cases, you only have a getter as accessor and you can't put a binding on it too…

This is for example the case for the ribbon’s group of the office ribbon or the converter’s parameter.

If you ever tried to do so, you surely had an exception thrown:

A 'Binding' cannot be set on the 'SetCEDEJDED' property of type 'Tralala'.
A 'Binding' can only be set on a DependencyProperty of a DependencyObject.

In this post, we will discover a work-around…

The main idea is to use a kind of proxy/observer (a definition can be found in this post) which will reflect every change on the source object to the target object and vice versa.

Here are the main parts of the solution.

Specification: The XAML Code We'll Use

Here is the code snippet which describes how we will use our proxy in the XAML. There will be no code-behind.

XML
<Window x:Class="BindOnNonDependencyProperty.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:us="clr-namespace:BindOnNonDependencyProperty"
    Title="BindOnNonDependencyProperty" >
  <DockPanel >
    <TextBox x:Name="myTextBox" DockPanel.Dock="Top"  />
    <TextBox x:Name="monTextBlockCible"  DockPanel.Dock="Top"  />
    <us:ExtendedBinding Source="{Binding ElementName=myTextBox,Path=Text,Mode=TwoWay}"
              Target="{Binding ElementName=monTextBlockCible,Path=Text,Mode=TwoWay}"
              />
  </DockPanel>
</Window>

The Correct Base Class for our Proxy/Observer

We will call it ExtendedBinding and it must be inheriting from DependencyObject at last to be able to own DependencyProperty. But the only way to add a DependencyObject into our XAML is to add it into a resourceDictonary.

This is a drawback because, by doing it, it will no more be in the control's tree and then it will be impossible to make a binding on one of its properties. Note that it's still possible to use it as a Target from another place in our XAML, but you can't do a Binding on one of its properties. This code will not work:

XML
<Windows.Resources>
   <MyDependencyObject x:Key="myKey" 
	MyProperty="{Binding Tralala, ElementName=myTarget}" />
</Windows.Resources>

Then to put it inside the control's tree, we only had to make it a UIElement will you say... No because in the actual version of the Framework, you won't have inheritance of the DataContext and the use of the 'ElementName' binding will be prohibited. Hopefully, there is a solution, our proxy have to inherit from FrameworkElement and everything will work fine!

The DependencyProperties

We will add two dependencyProperties, one will be the target and the second will be the source.
These DPs will be customized by using the FrameworkPropertyMetadata to enable these features:

  • Binding will be done using the TwoWay mode
  • The UpdateSourceTrigger used will be the PropertyChanged event

How It Works

The core of our proxy is to override the DependencyObject's OnPropertyChanged method. Each change on the source or the target will update its counterpart.

We have to take care not to fall into a loop: when we will update the target or the source, we'll also raise a PropertyChanged event and we must ignore this one....

Final Code

C#
public class ExtendedBinding : FrameworkElement
  {
    #region Source DP
    //We don't know what will be the Source/target type so we keep 'object'.
    public static readonly DependencyProperty SourceProperty =
      DependencyProperty.Register("Source", typeof(object), typeof(ExtendedBinding),
      new FrameworkPropertyMetadata()
      {
        BindsTwoWayByDefault = true,
        DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
      });
    public Object Source
    {
      get { return GetValue(ExtendedBinding.SourceProperty); }
      set { SetValue(ExtendedBinding.SourceProperty, value); }
    }
    #endregion
 
    #region Target DP
      //We don't know what will be the Source/target type so we keep 'object'.
    public static readonly DependencyProperty TargetProperty =
      DependencyProperty.Register("Target", typeof(object), typeof(ExtendedBinding),
      new FrameworkPropertyMetadata()
      {
        BindsTwoWayByDefault = true,
        DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
      });
    public Object Target
    {
      get { return GetValue(ExtendedBinding.TargetProperty); }
      set { SetValue(ExtendedBinding.TargetProperty, value); }
    }
    #endregion
 
    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
      base.OnPropertyChanged(e);
      if (e.Property.Name == ExtendedBinding.SourceProperty.Name)
      {
	//no loop wanted
        if (!object.ReferenceEquals(Source, Target))
          Target = Source;
      }
      else if (e.Property.Name == ExtendedBinding.TargetProperty.Name)
      {
	//no loop wanted
        if (!object.ReferenceEquals(Source, Target))
          Source = Target;
      }
    }
  }
Shout it kick it on DotNetKicks.com

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)