Как связать два свойства из разных моделей в 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 — это модель представления текущего сфокусированного элемента управления.

Либо получите доступ к родительской виртуальной машине в xaml через относительный источник, либо просто напрямую привяжите parentModelInstance.CurrentModel.Command и уведомите об изменениях.

EpicKip 01.03.2019 15:39

«Любая более простая и чистая альтернатива?». Возможно, вы захотите изучить реактивную структуру, такую ​​​​как Реактивный интерфейс: stackoverflow.com/a/22215041/7252182.

mm8 01.03.2019 15:50

@EpicKip Я не могу этого сделать, потому что ButtonViewModel используется в качестве динамической модели представления для DataTemplate с ItemTemplateSelector в зависимости от его типа.

Fab 01.03.2019 16:28

Зачем вообще существует модель просмотра для кнопки? Рассматривали ли вы возможность использования селектора шаблонов данных для выбора шаблона на основе свойства, а не типа? Если вы нажмете кнопку, вы хотите вызвать команду в зависимости от текущей модели? Просто привязка команды к currentmodel.Command (кстати, ужасное имя) кажется очевидным. Затем заставьте все, что вы делаете, работать с этим.

Andy 01.03.2019 19:32
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
488
1

Ответы 1

Путешествие в связующую землю

Первое решение: один помощник, чтобы управлять ими всеми и связать их с моделью представления.

Он был вдохновлен 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.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);

Остается потенциальная проблема

  • Не полагаясь на DependencyProperty (очевидно), возникает потенциальная проблема, поскольку мы не можем сообщить слушателю, что свойство не установлено (с помощью DependencyProperty.UnsetValue).
  • это односторонняя привязка.

Другие вопросы по теме