如何在WPF中绑定来自不同模型的两个属性

时间:2019-03-01 14:34:14

标签: c# .net wpf

假设我有类似的课程:

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是当前焦点控件的视图模型。

1 个答案:

答案 0 :(得分:0)

进入约束地的旅程

第一个解决方案:一个人来统治它们,并用视图模型将它们绑定

它受ReactiveUI和DependencyProperty上的手动绑定的启发:

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

问题

  • 必须为视图模型的所有属性设置DependencyProperty模式。
  • 反思和表情分析会引发运行时异常。
  • 如果需要进行转换,我们必须编写一个代理进行转换和值修改传播。

第二个解决方案:Reactive.UI和Fody

参考ReactiveUI.WPFReactiveUI.Fody,并像这样修改视图模型

public class ButtonViewModel : ReactiveObject
{
    [Reactive]
    public ICommand Command { get; set; }
}

然后我们可以像这样绑定两个属性:

container.WhenAnyValue(x => x.CurrentControlModel.Commands.Paste)
    .BindTo(pasteButtonViewModel, x => x.Command);

可能存在的问题

  • (显然)由于不依赖DependencyProperty,因此存在潜在的问题,因为我们无法告知侦听器未设置属性(使用DependencyProperty.UnsetValue)。
  • 这是一种单向绑定。