ViewModel 之间的 WPF 数据绑定
WPF DataBinding between ViewModels
我有一个问题,我无法正确地表述,因此还没有找到满意的答案。也许这里有人可以指出正确的方向。
我在 WPF 中使用 MVVM 来创建类似 UML 的建模工具。出于所有意图和目的,让我们坚持使用 UML 类比。
我基本上有 4 个相关的 ViewModel:CanvasViewModel、ClassViewModel、MemberViewModel、TypeViewModel。它们都实现了相同的接口,暴露了一个 bool Valid 属性.
如您所想,canvas 上有 1 个全局 Canvas,n 个 Class,Class 中有 n 个成员,每个成员有 1 个类型.它可以这样表示:
Canvas
{
Person (Class)
{
Age (Member)
{
int (Type)
}
Name (Member)
{
string (Type)
}
}
House (Class)
{
Price (Member)
{
currency (Type)
}
}
}
它实现了这样一张图:(http://www.tutorialspoint.com/uml/images/uml_class_diagram.jpg)
我把CanvasViewModel.Valid绑定到canvas,如果是假的,会显示一个大红色的探照灯。
如果 ClassViewModel.Valid 为假,我将 ClassViewModel.Valid 绑定到 class 框以执行摆动动画。
如果 MemberViewModel.Valid 为假,我将其绑定到列表视图以闪烁红色。
我没有特别绑定 TypeViewModel.Valid。
当然,Valid 属性 的实现非常简单(代码未测试):
// CanvasViewModel
public bool Valid { get { return Classes.All(x => x.Valid); } }
// ClassViewModel
public bool Valid { get { return Members.All(x => x.Valid); } }
// MemberViewModel
public bool Valid { get { return Type.Valid; } }
所以这里的用例是:用户不小心将 TypeViewModel 设置为 "innt",这是一个无效的类型。我希望所有 4 个 ViewModel 然后将它们的 Valid 属性 评估为 false。我希望此事件通过所有正确的 ViewModels 传播(从类型 -> 成员 -> Class -> Canvas)。
我到底要怎么做才能做到这一点,而不必 fiddle 在我的代码中到处都是可怕的行,例如
var memberViewModel = new MemberViewModel(member);
memberViewModel.PropertyChanged += (o, e) => { if ( e.PropertyName == "Valid") OnPropertyChanged("Valid"); }
Members.Add(memberViewModel);
我不想像这样把我的功能串在一起。我更喜欢干净的绑定,具有清晰的 entry/exit、get/set 或 add/remove 函数。
是的,不是 ViewModel 和 GUI 之间的绑定,而是 ViewModel 之间的绑定。我尝试使用 On属性Changed 和 CoerceValue。但他们的目的对我来说并不完全清楚。在我的例子中,成员 On属性Changed 实现看起来像这样
public static void OnPropertyChanged(...)
{
MyParentClass.CoerceValue(ClassViewModel.ValidProperty);
}
我想这不会太糟糕,除了我不知道 CoerceValue 实际有什么责任。评估在那里进行? 属性 是否假定 CoerceValue 的 return 值?是否像常规 属性 中的 On属性Changed 代表 "set" 和 CoerceValue 代表 "get" 一样简单?无论如何,我没有让它工作,所以我怀疑我是否正确理解了它的用途。所有使用 OnPropertyChanged/CoerceValue 的示例基本上都在同一个 class 中处理 DependencyProperties(从不跨越 class)。
有什么想法可以解决这个相当普遍的问题吗?
干杯,
雷内
使用反射和接口(或某种基础 class)。使用基础 class 来识别您要为其设置值的对象类型,并向下迭代到每个 class。效果为:
interface IValid
{
bool Valid { get; set; }
}
class Canvas : IValid, INotifyPropertyChanged
{
private bool valid;
public bool Valid
{
get { return this.valid; }
set
{
this.valid = value;
this.OnPropertyChanged("Valid");
this.SetPropertyValues(this);
}
}
public Canvas()
{
this.Person = new Person();
}
public Person Person { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
private void SetPropertyValues(IValid obj)
{
var properties = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in properties)
{
if (property.CanRead)
{
var validObject = property.GetValue(obj) as IValid;
if (validObject != null)
{
validObject.Valid = obj.Valid;
SetPropertyValues(validObject);
}
}
}
}
}
class Person : IValid, INotifyPropertyChanged
{
private bool valid;
public bool Valid
{
get { return this.valid; }
set
{
this.valid = value;
this.OnPropertyChanged("Valid");
}
}
public Person()
{
this.Age = new Age();
}
public Age Age { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
class Age : IValid, INotifyPropertyChanged
{
private bool valid;
public bool Valid
{
get { return this.valid; }
set
{
this.valid = value;
this.OnPropertyChanged("Valid");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
处理这种情况的设计模式讨论得很好 here。
在你的情况下,我喜欢选项 1,你在每个子对象中持有对父对象的引用。然后,您将调用父项的 属性 从 属性 setter 中的子项更改。这可能是最简单的解决方案,但它确实引入了父子耦合,但您似乎不会像他们描述的那样有同样的担忧。我建议将 IValidating 传递给子构造函数并在 setter 中检查它是否为 null。如果您使用的是 mvvm 框架,您还可以查看消息总线模式
我有一个问题,我无法正确地表述,因此还没有找到满意的答案。也许这里有人可以指出正确的方向。
我在 WPF 中使用 MVVM 来创建类似 UML 的建模工具。出于所有意图和目的,让我们坚持使用 UML 类比。
我基本上有 4 个相关的 ViewModel:CanvasViewModel、ClassViewModel、MemberViewModel、TypeViewModel。它们都实现了相同的接口,暴露了一个 bool Valid 属性.
如您所想,canvas 上有 1 个全局 Canvas,n 个 Class,Class 中有 n 个成员,每个成员有 1 个类型.它可以这样表示:
Canvas
{
Person (Class)
{
Age (Member)
{
int (Type)
}
Name (Member)
{
string (Type)
}
}
House (Class)
{
Price (Member)
{
currency (Type)
}
}
}
它实现了这样一张图:(http://www.tutorialspoint.com/uml/images/uml_class_diagram.jpg)
我把CanvasViewModel.Valid绑定到canvas,如果是假的,会显示一个大红色的探照灯。 如果 ClassViewModel.Valid 为假,我将 ClassViewModel.Valid 绑定到 class 框以执行摆动动画。 如果 MemberViewModel.Valid 为假,我将其绑定到列表视图以闪烁红色。 我没有特别绑定 TypeViewModel.Valid。
当然,Valid 属性 的实现非常简单(代码未测试):
// CanvasViewModel
public bool Valid { get { return Classes.All(x => x.Valid); } }
// ClassViewModel
public bool Valid { get { return Members.All(x => x.Valid); } }
// MemberViewModel
public bool Valid { get { return Type.Valid; } }
所以这里的用例是:用户不小心将 TypeViewModel 设置为 "innt",这是一个无效的类型。我希望所有 4 个 ViewModel 然后将它们的 Valid 属性 评估为 false。我希望此事件通过所有正确的 ViewModels 传播(从类型 -> 成员 -> Class -> Canvas)。
我到底要怎么做才能做到这一点,而不必 fiddle 在我的代码中到处都是可怕的行,例如
var memberViewModel = new MemberViewModel(member);
memberViewModel.PropertyChanged += (o, e) => { if ( e.PropertyName == "Valid") OnPropertyChanged("Valid"); }
Members.Add(memberViewModel);
我不想像这样把我的功能串在一起。我更喜欢干净的绑定,具有清晰的 entry/exit、get/set 或 add/remove 函数。
是的,不是 ViewModel 和 GUI 之间的绑定,而是 ViewModel 之间的绑定。我尝试使用 On属性Changed 和 CoerceValue。但他们的目的对我来说并不完全清楚。在我的例子中,成员 On属性Changed 实现看起来像这样
public static void OnPropertyChanged(...)
{
MyParentClass.CoerceValue(ClassViewModel.ValidProperty);
}
我想这不会太糟糕,除了我不知道 CoerceValue 实际有什么责任。评估在那里进行? 属性 是否假定 CoerceValue 的 return 值?是否像常规 属性 中的 On属性Changed 代表 "set" 和 CoerceValue 代表 "get" 一样简单?无论如何,我没有让它工作,所以我怀疑我是否正确理解了它的用途。所有使用 OnPropertyChanged/CoerceValue 的示例基本上都在同一个 class 中处理 DependencyProperties(从不跨越 class)。
有什么想法可以解决这个相当普遍的问题吗?
干杯,
雷内
使用反射和接口(或某种基础 class)。使用基础 class 来识别您要为其设置值的对象类型,并向下迭代到每个 class。效果为:
interface IValid
{
bool Valid { get; set; }
}
class Canvas : IValid, INotifyPropertyChanged
{
private bool valid;
public bool Valid
{
get { return this.valid; }
set
{
this.valid = value;
this.OnPropertyChanged("Valid");
this.SetPropertyValues(this);
}
}
public Canvas()
{
this.Person = new Person();
}
public Person Person { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
private void SetPropertyValues(IValid obj)
{
var properties = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in properties)
{
if (property.CanRead)
{
var validObject = property.GetValue(obj) as IValid;
if (validObject != null)
{
validObject.Valid = obj.Valid;
SetPropertyValues(validObject);
}
}
}
}
}
class Person : IValid, INotifyPropertyChanged
{
private bool valid;
public bool Valid
{
get { return this.valid; }
set
{
this.valid = value;
this.OnPropertyChanged("Valid");
}
}
public Person()
{
this.Age = new Age();
}
public Age Age { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
class Age : IValid, INotifyPropertyChanged
{
private bool valid;
public bool Valid
{
get { return this.valid; }
set
{
this.valid = value;
this.OnPropertyChanged("Valid");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
处理这种情况的设计模式讨论得很好 here。 在你的情况下,我喜欢选项 1,你在每个子对象中持有对父对象的引用。然后,您将调用父项的 属性 从 属性 setter 中的子项更改。这可能是最简单的解决方案,但它确实引入了父子耦合,但您似乎不会像他们描述的那样有同样的担忧。我建议将 IValidating 传递给子构造函数并在 setter 中检查它是否为 null。如果您使用的是 mvvm 框架,您还可以查看消息总线模式