OneWayToSource Binding for ReadOnly Dependency Property

Have you ever tried to do something like this?

<TextBlock Name="myTextBlock"
           ActualWidth="{Binding Path=Width, Mode=OneWayToSource}"
           ActualHeight="{Binding Path=Height, Mode=OneWayToSource}"/>

If you did, you probably received the following error

error MC3065: ‘ActualWidth’ property is read-only and cannot be set from markup.

A Binding can’t be set on a ReadOnly Dependency Property. Fair enough. But why can’t it do a OneWayToSource Binding? This should only affect the Source and not the Target. Apperently, this is by design and the problem is reported here on connect:
OneWayToSource binding from a readonly dependency property

The Solution
Various solutions and workarounds to this have been posted across the Internet and here is my take on it.

The aim was to create an intuitive solution which

• Adds support for a “PushBinding” on any Dependency Property in the Target

• Works with DataContext, ElementName, RelativeSource and Source Bindings

• Can specify any number of “PushBindings” on any FrameworkElement

And here is the result

<TextBlock Name="myTextBlock">
    <pb:PushBindingManager.PushBindings>
        <pb:PushBinding TargetProperty="ActualHeight" Path="Height"/>
        <pb:PushBinding TargetProperty="ActualWidth" Path="Width"/>
    </pb:PushBindingManager.PushBindings>
</TextBlock>

The attached project contains source code and demo usage
Download Project Here

Implemention

PushBindings is an Attached Property of type PushBindingCollection which can be set on any FrameworkElement.

PushBinding won’t be in the Visual or Logical tree so it will need an Inheritance Context to be able to do DataContext, ElementName and RelativeSource Bindings. One way to get an Inheritance Context is to inherit from Freezable (More details of why this works can be found here and here). PushBindingCollection needs Inheritance Context as well so it derives from FreezableCollection.

PushBinding has two Dependency Properties

• Listener – Binds OneWay to the TargetProperty and calls SetValue on Mirror whenever it changes through the PropertyChangedCallback

• Mirror – Binds OneWayToSource to whatever is specified in the Binding (DataContext, RelativeSource etc.)

public class PushBinding : FreezableBinding
{
    #region Dependency Properties

    public static DependencyProperty TargetPropertyMirrorProperty = ...
    public static DependencyProperty TargetPropertyListenerProperty = ...

    public string TargetProperty{get; set;}

    public void SetupTargetBinding(FrameworkElement targetObject)
    {
        Binding listenerBinding = new Binding
        {
            Source = targetObject,
            Path = new PropertyPath(TargetProperty),
            Mode = BindingMode.OneWay
        };
        BindingOperations.SetBinding(this,
                                     TargetPropertyListenerProperty,
                                     listenerBinding);

        BindingOperations.SetBinding(this,
                                     TargetPropertyMirrorProperty,
                                     Binding);
    }

    private void TargetPropertyValueChanged()
    {
        object targetPropertyValue = GetValue(TargetPropertyListenerProperty);
        this.SetValue(TargetPropertyMirrorProperty, targetPropertyValue);
    }
}

At first, I used Reflection to get the specified DependencyProperty and used it to subscribe to ValueChanged of DependencyPropertyDescriptor. The problem with this was that it leaked memory and I had a hard time to find out when it was time to call RemoveValueChanged. Instead I introduced the Listener Dependency Property and now it’s leak-free.

Update: Using PushBinding in a Style

Initially, there were some problem when trying to use PushBindings in a Style.

• The workaround used to be able to add items directly to the attached property PushBindings in Xaml (without having to create a new instance of PushBindingsCollection) doesn’t work in a Style. Instead, we just get an exception.

• A FreezableCollection created in Xaml is Freezed. This means that the PushBindings in the collection becomes Freezed as well and a Freezed object can’t be edited. So when trying to setup the Bindings in PushBinding we’ll get another exception.

• Unlike a regular Binding, a PushBinding can only be used for one object. This means that each PushBinding in a Style have to be cloned before it can be used.

To overcome these issues, I created another attached property in PushBindingManager called StylePushBindings and implemented the overridable methods CreateInstanceCore and CloneCore in PushBinding and its base-class FreezableBinding. And when a PushBinding is added to StylePushBindings, it is cloned and added to the PushBindings collection.

public static void StylePushBindingsChanged(DependencyObject target,
                                        DependencyPropertyChangedEventArgs e)
{
    FrameworkElement targetObject = target as FrameworkElement;
    if (targetObject != null)
    {
        PushBindingCollection stylePushBindings =
            e.NewValue as PushBindingCollection;
        PushBindingCollection pushBindingCollection =
            GetPushBindings(targetObject);
        foreach (PushBinding pushBinding in stylePushBindings)
        {
            PushBinding pushBindingClone = pushBinding.Clone() as PushBinding;
            pushBindingCollection.Add(pushBindingClone);
        }
    }
}

So when using a PushBinding in a Style, use StylePushBindings, create a new PushBindingCollection and add the PushBindings like this

<TextBlock ...>
  <TextBlock.Style>
    <Style TargetType="TextBlock">
      <Setter Property="pb:PushBindingManager.StylePushBindings">
        <Setter.Value>
          <pb:PushBindingCollection>
            <pb:PushBinding TargetProperty="ActualHeight" Path="Height"/>
            <pb:PushBinding TargetProperty="ActualWidth" Path="Width"/>
          </pb:PushBindingCollection>
        </Setter.Value>
      </Setter>
    </Style>
  </TextBlock.Style>
</TextBlock>

I added a new demo project with sample usage for PushBindings in both Styles and DataTemplates.
It can be downloaded here

Enjoy! :)

One last note, since .NET 4.0 we are even further away from a solution to this, since a OneWayToSource Binding reads the value it set back from the Source after it has updated it so I doubt we will ever see built-in-support for this.

About these ads

13 Responses to OneWayToSource Binding for ReadOnly Dependency Property

  1. R says:

    Is it possible to use this solution from a style or datatemplate? I’m facing a scenario where I need to bind a readonly property of a dynamically generated ListViewItem.

    • Meleak says:

      Hi Per and sorry for the late reply! Yes, it is possible but it took a little more work to get it to work from a Style/DataTemplate. Unfortunately, I haven’t had time to update the post and now I won’t have access to my computer for another 5 month since me and my girlfriend is on a long vacation around the world. Sorry about that, hope you manage to solve your problem! Best regards /Fredrik

    • Meleak says:

      Finally got some time to update the post. The modified version is usable in a Style as well. Cheers

  2. Daniel Wiebe says:

    This is great. What kind of software license is this code under? I would like to use your code in a commercial application, but I cannot unless it is under a license that is compatible with my company’s. Something like the BSD, MIT, or CPOL license would work great (http://www.codeproject.com/info/Licenses.aspx). I have to ask since under US copyright law all code is implicitly under copyright unless stated otherwise (via some license).

    • Meleak says:

      Thanks for your comment Daniel. Actually, I wasn’t aware that I had to license my code at all, I just considered it to be code that anyone can use however they see fit :) Anyway, it’s in the public domain now with Unlicense so you’re free to use it anyway you like. Cheers

  3. Salim says:

    Thank you so much for this article, it helped me and saved a lot of time and headache :)

  4. Ioan Samfirescu says:

    Elegant solution! Thank you.

  5. tilonthuduc says:

    How can we use this PushBinding in code since it’s not derived from BindingBase? Sorry for my stupid question. I’m a new baby in WPF.

  6. tilonthuduc says:

    Sorry. I found my own anwser. It’s just need to set PathProperty and SetupTargetBinding to make it work. Thanks.

  7. Jerome says:

    Very nice solution.
    Thank you for this code sharing.

    Regards

  8. karan sharma says:

    Is it possible to use this in DataTrigger

  9. wpfblendperson says:

    Hi, Your downloadable project doesn’t have the StylePushBindings … Is there a way you can upload one with it too? That would be useful. THanks!

  10. Shawn says:

    Thank you so much for writing this article. I have been struggling for days to add Behaviors in a Style and this lead me in the right direction, thanks again!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: