是否可以在不知道绑定属性的情况下直接设置双向绑定后面的值?
我有一个附加属性,它绑定到这样的属性:
<Element my:Utils.MyProperty="{Binding Something}" />
现在我想从附加属性的角度更改有效存储在
Something
中的值。所以我无法直接访问绑定属性,而只能引用 DependencyObject
(即 Element 实例)和 DependencyProperty
对象本身。
简单地通过
DependencyObject.SetValue
设置它时的问题是,这有效地删除了绑定,但我想更改底层绑定属性。
使用
BindingOperations
我可以获得 Binding
和 BindingExpression
。现在有没有办法访问它背后的属性并改变它的值?
好吧,我现在已经自己在绑定表达式上使用一些反射技巧解决了这个问题。
我基本上查看绑定路径和响应数据项,并尝试自己解决绑定。对于更复杂的绑定路径,这可能会失败,但对于如上面示例中的简单属性名称,这应该可以正常工作。
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);
}
尝试在
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));
简单地通过 DependencyObject.SetValue 设置它时的问题是 这有效地消除了绑定,但我想改变 底层绑定属性。
如果 Binding.Mode 设置为 OneWay,则为 true。如果将其设置为 TwoWay,则使用 DependencyObject.SetValue 将不会删除其绑定。
这是来自 Pro WPF 4.5 in C# 的引用(第 232 页):
删除绑定:如果您想删除绑定,以便可以 按照通常的方式设置属性,你需要帮助 ClearBinding() 或 ClearAllBindings() 方法。仅仅这样是不够的 对属性应用新值。如果您使用双向绑定, 您设置的值将传播到链接的对象,并且两者 属性保持同步。
因此,能够使用 SetValue 更改(和传播) my:Utils.MyProperty 而不删除其绑定:
<Element my:Utils.MyProperty="{Binding Something, Mode=TwoWay}" />
您可以通过虚拟对象上的绑定来传递值。
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));
}
在尝试实现由枚举支持的菜单时,我遇到了类似的问题。我希望能够将底层属性(这是一个枚举)设置为与菜单项关联的值。
在我的示例中,我将两个属性附加到 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);
}
}
}
}
}
};
这似乎适合我的具有复杂路径的场景。
希望这对您有所帮助!
这是我为
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();
}
只需使用
SetCurrentValue
方法即可(https://learn.microsoft.com/en-us/dotnet/api/system.windows.dependencyobject.setcurrentvalue):
SetCurrentValue 方法更改属性的有效值,但现有的触发器、数据绑定和样式将继续工作