直接设置绑定值

9

是否可能直接设置双向绑定后面的值,而不知道绑定属性是什么?

我有一个附加属性,它绑定到一个属性,如下所示:

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

现在我想从所附属性的角度更改实际存储在Something中的值。所以我不能直接访问绑定属性,只能引用DependencyObject(即元素实例)和DependencyProperty对象本身。
通过DependencyObject.SetValue简单设置它的问题是,这实际上会删除绑定,但我想更改底层绑定属性。
使用BindingOperations,我可以获取BindingBindingExpression。现在是否有一种方法可以访问其背后的属性并更改其值?

你自己写了附加属性吗?可以更改它的PropertyMetadata吗?我猜不行吧? - Tom Kerkhove
@HellScream 是的,我正在自己编写属性,所以如果有帮助的话,我可以更改元数据。 - poke
DependencyObject.SetValue是否真的会移除绑定?如果是,那么它被替换成了什么? - StayOnTarget
6个回答

7

好的,我已经使用一些关于绑定表达式的反射技巧自己解决了这个问题。

我基本上查看绑定路径和响应数据项,并尝试自行解决绑定。对于更复杂的绑定路径,这可能会失败,但对于像我上面示例中的简单属性名称,这应该可以正常工作。

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);
}

当路径比较复杂时,它无法正常工作。因为当路径类似于Customer.Name时,BindingExpression的DataItem中实际属性不存在。它是由另一个对象(在这种情况下是Customer)拥有的。 - Kubilay Kara
如果您仔细阅读我的答案,您会注意到我说它只适用于简单的属性名称。如果您有复杂的绑定路径,您将需要先解析路径并遍历对象以找到正确的属性。 - poke

0

你可以通过一个虚拟对象的绑定来传递值。

    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);
    }

这是我的虚拟对象

   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));
}

0

尝试在PropertyMetadata中设置默认值。

您可以在MSDN上找到更多信息 - http://msdn.microsoft.com/en-us/library/system.windows.propertymetadata.aspx

这里是一个例子:

  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));

该属性已经有默认值了,但我想在之后更改该值。而且当附加属性的值被实际设置(绑定)时,我认为默认值根本不会被考虑。 - poke

0
仅通过DependencyObject.SetValue设置它的问题在于,这实际上会删除绑定,但我想更改底层绑定属性。如果Binding.Mode设置为TwoWay,则此说法是正确的。使用DependencyObject.SetValue不会删除其绑定。这是来自Pro WPF 4.5 in C#(第232页)的引用:
“删除绑定:如果要删除绑定以便以通常的方式设置属性,则需要ClearBinding()或ClearAllBindings()方法的帮助。仅将新值应用于属性是不够的。如果您正在使用双向绑定,则所设置的值会传播到链接的对象,并且两个属性保持同步。”
因此,为了能够更改(并传播)my:Utils.MyProperty的SetValue而不删除其绑定:
<Element my:Utils.MyProperty="{Binding Something, Mode=TwoWay}" />

SetValue()即使我处于双向模式下,也会为我删除绑定。 - Maxence

0

这是我为完成的方式:

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();
}

0

当我尝试实现由枚举支持的菜单时,我遇到了类似的问题。我想能够将基础属性(它是一个枚举)设置为与菜单项相关联的值。

在我的例子中,我给 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)
        );

而且标记看起来像这样:

                <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>

父菜单项的项目源绑定到一个MarkupExtension实现,该实现为枚举中的每个成员提供值。

现在,当菜单项被选中时,我使用了这段代码来设置属性的值,而不需要删除绑定。

        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);
                            }
                        }
                    }
                }
            }
        };

这对于我的复杂路径场景似乎很有效。

希望这能帮到你!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接