Set the binding value directly
Asked Answered
B

6

9

Is it possible to set the value behind a two-way binding directly, without knowing the bound property?

I have an attached property that is bound to a property like this:

<Element my:Utils.MyProperty="{Binding Something}" />

Now I want to change the value that is effectively stored in Something from the perspective of the attached property. So I cannot access the bound property directly, but only have references to the DependencyObject (i.e. the Element instance) and the DependencyProperty object itself.

The problem when simply setting it via DependencyObject.SetValue is that this effectively removes the binding, but I want to change the underlying bound property.

Using BindingOperations I can get both the Binding and the BindingExpression. Now is there a way to access the property behind it and change its value?

Bonis answered 20/6, 2012 at 13:46 Comment(3)
Did you wrote the attached-prop yourself as in can you change its PropertyMetadata? I guess not?Dwanadwane
@HellScream Yes, I am writing the property myself, so I can change the metadata if that helps.Bonis
Does DependencyObject.SetValue really remove the binding? If so, what is the binding replaced with?Robedechambre
B
7

Okay, I have solved this now myself using a few reflection tricks on the binding expression.

I basically look at the binding path and the responding data item and try to resolve the binding myself. This will probably fail for more complex binding paths but for a simple property name as in my example above, this should work fine.

BindingExpression bindingExpression = BindingOperations.GetBindingExpression(dependencyObj, dependencyProperty);
if (bindingExpression != null)
{
    PropertyInfo property = bindingExpression.DataItem.GetType().GetProperty(bindingExpression.ParentBinding.Path.Path);
    if (property != null)
        property.SetValue(bindingExpression.DataItem, newValue, null);
}
Bonis answered 20/6, 2012 at 14:55 Comment(2)
it's not working when path is complex. Because when path is something like Customer.Name, actual property doesn't exist at BindingExpression's DataItem. It's owned by an other object (in that case by Customer).Openhearted
@KubilayKara If you read my answer, you will notice that I said that it will only work for simple property names. If you have complex binding paths, you will have to parse the path first and traverse the objects to find the correct property.Bonis
D
0

Try setting a default value in the PropertyMetadata

You can find more information on MSDN - http://msdn.microsoft.com/en-us/library/system.windows.propertymetadata.aspx

Here is an example :

  public Boolean State
  {
    get { return (Boolean)this.GetValue(StateProperty); }
    set { this.SetValue(StateProperty, value); } 
  }
  public static readonly DependencyProperty StateProperty = DependencyProperty.Register(
    "State", typeof(Boolean), typeof(MyStateControl),new PropertyMetadata(myDefaultValue));
Dwanadwane answered 20/6, 2012 at 14:13 Comment(1)
The property already has a default value, but what I want is to change the value afterwards. Also I don’t think the default value is considered at all when the value of the attached property is actually set (to a binding).Bonis
D
0

The problem when simply setting it via DependencyObject.SetValue is that this effectively removes the binding, but I want to change the underlying bound property.

This is true if the Binding.Mode is set to OneWay. If it is set to TwoWay, using DependencyObject.SetValue won't remove its binding.

This is a quote from Pro WPF 4.5 in C# (page 232):

Removing a binding: If you want to remove a binding so that you can set a property in the usual way, you need the help of the ClearBinding() or ClearAllBindings() method. It isn’t enough to simply apply a new value to the property. If you’re using a two-way binding, the value you set is propagated to the linked object, and both properties remain synchronized.

So, to be able to change (and propagate) the my:Utils.MyProperty with SetValue without removing its binding:

<Element my:Utils.MyProperty="{Binding Something, Mode=TwoWay}" />
Dionysus answered 1/11, 2016 at 21:32 Comment(1)
SetValue() removes the binding for me, even if I'm in two way mode.Grand
O
0

You can pass value via a binding on a dummy object.

    public static void SetValue(BindingExpression exp, object value)
    {
        if (exp == null)
            throw new ValueCannotBeNullException(() => exp);
        Binding dummyBinding = new Binding(exp.ParentBinding.Path.Path);
        dummyBinding.Mode = BindingMode.OneWayToSource;
        dummyBinding.Source = exp.DataItem;
        SetValue(dummyBinding, value);
    }

    public static void SetValue(Binding binding, object value)
    {
        BindingDummyObject o = new BindingDummyObject();
        BindingOperations.SetBinding(o, BindingDummyObject.ValueProperty, binding);
        o.Value = value;
        BindingOperations.ClearBinding(o, BindingDummyObject.ValueProperty);
    }

This is my dummy object

   internal class BindingDummyObject : DependencyObject
{
    public object Value
    {
        get
        {
            return (object)GetValue(ValueProperty);
        }
        set
        {
            SetValue(ValueProperty, value);
        }
    }

    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(object), typeof(BindingDummyObject));
}
Openhearted answered 15/12, 2017 at 10:2 Comment(0)
S
0

I had a similar issue when trying to implement a menu that was backed by an enumeration. I wanted to be able to set the underlying property (which was an enum) to the value associated with the menu item.

In my example, I attached two properties to an MenuItem:

    public static readonly DependencyProperty EnumTargetProperty = DependencyProperty.RegisterAttached(
        "EnumTarget",
        typeof(object),
        typeof(MenuItem),
        new PropertyMetadata(null, EnumTargetChangedCallback)
        );

    public static readonly DependencyProperty EnumValueProperty = DependencyProperty.RegisterAttached(
        "EnumValue",
        typeof(object),
        typeof(MenuItem),
        new PropertyMetadata(null, EnumValueChangedCallback)
        );

And the markup looks like this:

                <MenuItem.ItemContainerStyle>
                    <Style TargetType="MenuItem">
                        <Setter Property="IsCheckable" Value="True"/>
                        <Setter Property="local:EnumMenuItem.EnumValue" Value="{Binding EnumMember}"/>
                        <Setter Property="local:EnumMenuItem.EnumTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=local:MainWindow}, Path=DataContext.Settings.AutoUpdateModel.Ring}"/>
                        <Setter Property="Header" Value="{Binding DisplayName}"/>
                        <Setter Property="ToolTip" Value="{Binding ToolTip}"/>
                    </Style>
                </MenuItem.ItemContainerStyle>

The item source for the parent menu item was bound to an MarkupExtension implementation that provided values for each member in the enum.

Now, when the menu item was checked, I used this code to set the value of the property without removing the binding.

        menuItem.Checked += (sender, args) =>
        {
            var checkedMenuItem = (MenuItem)sender;
            var targetEnum = checkedMenuItem.GetValue(EnumTargetProperty);
            var menuItemValue = checkedMenuItem.GetValue(EnumValueProperty);
            if (targetEnum != null && menuItemValue != null)
            {
                var bindingExpression = BindingOperations.GetBindingExpression(d, EnumTargetProperty);
                if (bindingExpression != null)
                {
                    var enumTargetObject = bindingExpression.ResolvedSource;
                    if (enumTargetObject != null)
                    {
                        var propertyName = bindingExpression.ResolvedSourcePropertyName;
                        if (!string.IsNullOrEmpty(propertyName))
                        {
                            var propInfo = enumTargetObject.GetType().GetProperty(propertyName);
                            if (propInfo != null)
                            {
                                propInfo.SetValue(enumTargetObject, menuItemValue);
                            }
                        }
                    }
                }
            }
        };

This seems to work fine for my scenario with a complex path.

I hope this helps out!

Selfemployed answered 1/6, 2018 at 5:15 Comment(0)
G
0

Here is how I've done for a TextBlock:

BindingExpression bindingExpression = textBlock.GetBindingExpression(TextBlock.TextProperty);
string propertyName = bindingExpression.ResolvedSourcePropertyName;
PropertyInfo propertyInfo;
Type type = bindingExpression.DataItem.GetType();
object[] indices = null;
if (propertyName.StartsWith("[") && propertyName.EndsWith("]"))
{
    // Indexed property
    propertyInfo = type.GetProperty("Item");
    indices = new object[] { propertyName.Trim('[', ']') };
} 
else
    propertyInfo = type.GetProperty(propertyName);
if (propertyInfo.PropertyType == typeof(string))
{
    propertyInfo.SetValue(bindingExpression.DataItem, text, indices);
    // To update the UI.
    bindingExpression.UpdateTarget();
}
Grand answered 29/12, 2020 at 18:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.