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.
<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:
<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
public class ExtendedBinding : FrameworkElement
{
#region Source DP
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
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)
{
if (!object.ReferenceEquals(Source, Target))
Target = Source;
}
else if (e.Property.Name == ExtendedBinding.TargetProperty.Name)
{
if (!object.ReferenceEquals(Source, Target))
Source = Target;
}
}
}
CodeProject