让我说我有这样的课程:
public class ParentModel : INotifyPropertyChanged
{
// INotifyPropertyChanged pattern implemented ...
public IChildViewModel CurrentControlModel {
get { ... } set { /* Notify on changes */ }
}
}
public class ChildModelA : INotifyPropertyChanged, IChildViewModel
{
// INotifyPropertyChanged pattern implemented ...
public ICommand Command {
get { ... } set { /* Notify on changes */ }
}
}
public class ChildModelB : INotifyPropertyChanged, IChildViewModel
{
// INotifyPropertyChanged pattern implemented ...
public ICommand Command {
get { ... } set { /* Notify on changes */ }
}
}
public class ButtonViewModel : INotifyPropertyChanged
{
ICommand Command get { ... } set { /* Notify on changes */ }
}
我想拥有Command
属性以反映parentModelInstance.CurrentControlModel.Command
事件的值 CurrentControlModel
更改。
我不能修改 ButtonViewModel.Command
属性作为属性的代理因为这是所有按钮的视图模型,我不想为每个可能的按钮专业化它。
如果我做
ButtonViewModel viewModel;
viewModel.Command = parentModelInstance.CurrentControlModel.Command;
它不起作用,因为CurrentControlModel
可以更改(例如,在启动时是null
)。我可以收听PropertyChanged
事件,但是对于模型的所有属性,都会很麻烦。
有任何更轻松,更干净的替代方案吗?
上下文
给出一些上下文,它是动态工具栏代码的一部分,您的按钮可以更改图标,被禁用或更改命令,命令目标等...取决于当前的集中控制(可能是不同类型)的是什么。 CurrentControlModel
是当前集中控制的视图模型。
进入约束土地的旅程
第一个解决方案:一个授权它们的助手,并使用视图模型绑定
它的启发是由反应性和手动结合对依赖性的启发:
public static BindableProperty<TProperty> Watch<TInstance, TProperty>(
this TInstance instance,
Expression<Func<TInstance, TProperty>> expression,
BindingMode mode = BindingMode.TwoWay)
{
return new BindableProperty<TProperty>(instance,
GetPath((MemberExpression)expression.Body), mode);
}
public static void BindTo<TInstance, TProperty>(
this BindableProperty<TProperty> bindable,
TInstance instance,
Expression<Func<TInstance, TProperty>> expression) where TInstance
: DependencyObject
{
var getterBody = expression.Body;
var propertyInfo = (PropertyInfo)((MemberExpression)getterBody).Member;
var name = propertyInfo.Name;
var dependencyPropertyName = name + "Property";
var fieldInfo = typeof(TInstance).GetField(dependencyPropertyName,
BindingFlags.Public | BindingFlags.Static);
var dependencyProperty = (DependencyProperty)fieldInfo.GetValue(null);
Binding binding = new Binding();
binding.Source = bindable.Source;
binding.Path = new PropertyPath(bindable.Path);
binding.Mode = bindable.Mode;
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
BindingOperations.SetBinding(instance, dependencyProperty, binding);
}
public class BindableProperty<T>
{
public object Source { get; }
public string Path { get; }
public BindingMode Mode { get; }
public BindableProperty(object source, string path, BindingMode mode)
{
Source = source;
Path = path;
Mode = mode;
}
}
ButtonViewModel
必须源自DependencyObject
并实现模式对于Command
属性
public class ButtonViewModel : DependencyObject
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand),
typeof(ButtonViewModel), new PropertyMetadata(default(ICommand)));
public ICommand Command
{
get { return (ICommand) GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
}
然后可以像这样使用它(用于将糊命令绑定到粘贴按钮):
container.Watch(x => x.CurrentControlModel.Commands.Paste)
.BindTo(pasteButtonViewModel, x => x.Command);
问题
- 必须为视图模型的所有属性设置依赖关系模式。
- 反射和表达分析可以提高运行时异常。
- 如果需要转换,我们必须编写一个代理进行转换和值修改传播的代理。
第二个解决方案:反应性。UI和FODY
引用ReactiveUI.WPF
和ReactiveUI.Fody
,并像这样修改视图模型
public class ButtonViewModel : ReactiveObject
{
[Reactive]
public ICommand Command { get; set; }
}
然后我们可以绑定这样的两个属性:
container.WhenAnyValue(x => x.CurrentControlModel.Commands.Paste)
.BindTo(pasteButtonViewModel, x => x.Command);
潜在问题剩下的问题
- 通过不依赖依赖性Property(显然),存在一个潜在的问题,因为我们无法告诉听众未设置属性(使用
DependencyProperty.UnsetValue
)。 - 这是一种绑定的一种方式。