在开发应用程序时,经常需要在视图模型(ViewModel)和视图(View)之间建立数据绑定,以实现数据的实时更新和交互。XAML 绑定为提供了一种简洁的方式来实现这一点。但是,当需要在代码中实现类似的绑定效果时,就需要一些额外的工作。本文将介绍如何在代码中实现类似于 XAML 绑定的效果,以便在数据模型发生变化时自动更新视图模型。
为什么需要在代码中实现绑定效果
在遵循MVVM设计模式开发应用程序时,通常会创建视图模型来处理业务逻辑和数据,然后将这些视图模型与 XAML 视图进行绑定。然而,在某些情况下,视图模型本身也需要与其他数据源建立绑定关系。例如,当视图由子视图组成时,子视图模型的变更需要通知父视图模型;或者,数据模型在更新时,需要通知视图模型进行相应的更新。在这些情况下,需要在代码中实现类似于 XAML 绑定的效果。
如何在代码中实现绑定效果
要在代码中实现绑定效果,关键在于使用 Binding 对象。Binding 对象允许以编程方式创建绑定,其行为与 XAML 绑定相同。首先,需要一个工具来从 lambda 表达式生成属性路径。以下是一个名为 RootedPropertyPath 的工具类,它可以从 lambda 表达式生成属性路径,并创建对应的 Binding 对象。
public class RootedPropertyPath<T>
{
public Object Target { get; set; }
public String Path { get; set; }
public Binding ToBinding(BindingMode mode = BindingMode.TwoWay)
{
return new Binding(Path) { Source = Target, Mode = mode };
}
public static implicit operator System.Windows.PropertyPath(RootedPropertyPath<T> self)
{
return new System.Windows.PropertyPath(self.Path);
}
public static implicit operator String(RootedPropertyPath<T> self)
{
return self.Path;
}
}
public static class RootedPropertyPath
{
public static RootedPropertyPath<T> Create<T>(Expression<Func<T>> expr)
{
Expression currentExpression = expr.Body;
List<String> lst = new List<String>();
ConstantExpression ce;
while (true)
{
ce = currentExpression as ConstantExpression;
var me = currentExpression as MemberExpression;
if (ce != null) break;
if (me == null)
throw new Exception(String.Format("Unexpected expression type {0} in lambda.", expr.GetType()));
lst.Add(me.Member.Name);
currentExpression = me.Expression;
}
lst.Reverse();
return new RootedPropertyPath<T>() { Path = String.Join(".", lst), Target = ce.Value };
}
}
RootedPropertyPath<T> 类不仅可以生成属性路径,还可以收集绑定的“根”对象,即表达式的起始对象。接下来,需要一个工具来将生成的 Binding 对象绑定到 DependencyProperty 上。以下是一个名为 ObservableDependencyValue 的工具类,它可以创建一个可以绑定任意 Binding 并报告变更的 DependencyProperty。
public static class ObservableDependencyValue
{
public static ObservableDependencyValue<T> Create<T>(Expression<Func<T>> expr, BindingMode mode = BindingMode.OneWay)
{
return new ObservableDependencyValue<T>().Bind(expr, mode);
}
}
public class ObservableDependencyValue<T> : DependencyObject
{
#region T ObservableDependencyValue<T>.Value = default(T)
#region Boilerplate
public T Value
{
get { return (T)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(T), typeof(ObservableDependencyValue<T>), new PropertyMetadata(default(T), StaticHandleValueChanged));
static void StaticHandleValueChanged(DependencyObject self, DependencyPropertyChangedEventArgs args)
{
((ObservableDependencyValue<T>)self).HandleValueChanged((T)args.OldValue, (T)args.NewValue);
}
#endregion
void HandleValueChanged(T oldValue, T value)
{
Notify();
}
#endregion
public ObservableDependencyValue()
{
}
public ObservableDependencyValue(BindingBase bindingBase)
{
Bind(bindingBase);
}
public ObservableDependencyValue<T> Bind(BindingBase bindingBase)
{
BindingOperations.SetBinding(this, ValueProperty, bindingBase);
return this;
}
public ObservableDependencyValue<T> Bind(Expression<Func<T>> expr, BindingMode mode = BindingMode.OneWay)
{
var path = RootedPropertyPath.Create(expr);
return Bind(new Binding(path.Path) { Source = path.Target, Mode = mode });
}
public void Notify()
{
if (ValueChanged != null) ValueChanged(Value);
}
public event Action<T> ValueChanged;
}
现在已经拥有了创建绑定和处理变更通知的工具。接下来,可以在视图模型中使用这些工具来实现数据绑定。以下是一个示例:
public class PersonViewModel : OurViewModelBase
{
public PersonViewModel()
{
var observableDependencyProperty = ObservableDependencyValue.Create(() => this.Person.Employer.Address);
observableDependencyProperty.ValueChanged += address => UpdateAddress(address);
observableDependencyPropertyReference = observableDependencyProperty;
}
Object observableDependencyPropertyReference;
}