Bubbling NotifyOfPropertyChange со встроенными классами - PullRequest
0 голосов
/ 11 февраля 2020

Итак, вот вопрос, который заставляет меня бегать кругами. Я работаю со встроенной структурой класса, которая должна сохранять свои дочерние объекты частными, но должна иметь возможность передавать определенные события NotifyOfPropertyChange вверх по лестнице из данных в этих дочерних объектах. Каков наилучший способ сделать это.

Мой текущий подход - код ниже, где мое представление для SystemViewModel (SystemView) имеет элемент, связанный со свойством CommunicationStatus, и у меня есть родительский класс SystemViewModel, у которого есть дочерний класс CommunicationManager, у которого есть дочерний класс Communicator как

Вещи, которые затрудняют это:

1) В этом случае ДОЛЖНО быть допущено, что Communicator не имеет видимости SystemViewModel, поэтому в методе set в Communicator следует ввести NotifyOfPropertyChanged(() => CommunicationStatus) Свойство Connected не должно быть опцией ... если только я не упустил что-то очевидное.

2) SystemViewModel не должен иметь прямой доступ к коммуникатору, поэтому привязка от SystemView.xaml к Connected невозможна быть сделано.

На мой взгляд, событие NotifyOfPropertyChanged в Connected должно всплыть для родителей из-за реализации PropertyChangedBase во всех классах, но этого не происходит. Буду рад любой помощи!

public class SystemViewModel : PropertyChangedBase
{
    private CommunicationManager CommunicationManager;
    public string CommunicationStatus
    {
        get
        {
            if (CommunicationManager.YepConnected)
            {
                return "Green";
            }
            else
            {
                return "Red";
            }
        }
    }
}

public class CommunicationManager : PropertyChangedBase
{
    private Communicator Communicator;
    public bool YepConnected { get { return Communicator.Connected; } }
}

public class Communicator: PropertyChangedBase
{
    private bool _connected;

    public bool Connected
    {
        get { return _connected; }
        set 
        {
            _connected = value;
            NotifyOfPropertyChange(() => Connected);
        }
    }
}

РЕДАКТИРОВАТЬ

Таким образом, похоже, что это работает правильно и передает событие, как ожидается, от дочернего класса к родительскому классу. Реальная проблема, которая была немного более глубокой, связана с тем, как привязка WPF связана со свойством. Просто для справки, используемый мной XAML выглядит следующим образом:

<TextBlock Text="Status" Background="{Binding CommunicationStatus}"/>

Кроме того, я использовал SolidColorBrush вместо string (хотя они оба связаны одинаково и работают).

Проблема в том, что когда событие уведомления распространяется от Connected до CommunicationStatus, оно останавливается там и не распространяется на привязку XAML (нигде в моем коде не используется CommunicationStatus, кроме как в XAML связывание). Я знаю, что привязка работает, потому что при отладке я вижу, что когда программа запускается изначально, цвет устанавливается на красный при выполнении метода CommunicationStatus get, предположительно вызываемого из привязки XAML. После запуска кода CommunicationStatus обновляется каждый раз, когда Connected, но привязка XAML больше не наблюдает за этим изменением. Если я вручную реализую NotifyOfPropertyChange(() => CommunicationStatus);, обязательный элемент решает обновить. Однако, поскольку я не использую какой-либо метод set в CommunicationStatus (и событие notify не распространяется вверх), кажется, нет простого способа сообщить XAML, что мое значение изменилось.

Схематичное решение: Следите за изменениями CommunicationStatus и вызовите событие NotifyOfPropertyChange(() => CommunicationStatus); следующим образом:

public class SystemViewModel : Conductor<object>
{

    private CommunicationManager CommunicationManager;
    private SolidColorBrush LastCommunicationStatusValue = new SolidColorBrush();
    public SolidColorBrush CommunicationStatus
    {
        get
        {
            SolidColorBrush CurCommunicationStatusValue;
            if (CommunicationManager.YepConnected)
            {
                CurCommunicationStatusValue = new SolidColorBrush(Colors.Green);
            }
            else
            {
                CurCommunicationStatusValue = new SolidColorBrush(Colors.Red);
            }
            if (CurCommunicationStatusValue.Color != LastCommunicationStatusValue.Color)
            {
                LastCommunicationStatusValue = CurCommunicationStatusValue;
                NotifyOfPropertyChange(() => CommunicationStatus);
            }
            return CurCommunicationStatusValue;
        }
    }
}

И да, если вы не сделаете это идеально, это мгновенное переполнение стека (каламбур). Всякий раз, когда изменяется значение Connected, я наблюдаю, как выполняется метод CommunicationStatus get. Таким образом, это выполнение приводит к другому выполнению метода get, только на этот раз обновления XAML.

Может кто-нибудь объяснить, почему это решение работает, и / или предложить более красноречивое решение?

1 Ответ

0 голосов
/ 11 февраля 2020

Вот пример, как это сделать с ReactiveUI

public class SystemViewModel : ReactiveObject
{
    private readonly CommunicationManager communicationManager;
    private readonly ObservableAsPropertyHelper<string> connectionStatus;

    public SystemViewModel( CommunicationManager communicationManager )
    {
        this.communicationManager = communicationManager ?? throw new ArgumentNullException(nameof(communicationManager));
        this.communicationManager
            .WhenAnyValue( e => e.YepConnected, state => state ? "Green" : "Red" )
            .ToProperty( this, e => e.ConnectionStatus, out connectionStatus );
    }
    public string ConnectionStatus => connectionStatus.Value;
}

public class CommunicationManager : ReactiveObject
{
    private readonly Communicator communicator;
    private readonly ObservableAsPropertyHelper<bool> yepConnected;

    public CommunicationManager(Communicator communicator)
    {
        this.communicator = communicator ?? throw new ArgumentNullException(nameof(communicator));
        this.communicator
            .WhenAnyValue( e => e.Connected )
            .ToProperty( this, e => e.YepConnected, out yepConnected );
    }
    public bool YepConnected => yepConnected.Value;
}

public class Communicator : ReactiveObject
{
    private bool _connected;
    public bool Connected
    {
        get { return _connected; }
        set { this.RaiseAndSetIfChanged( ref _connected, value); }
    }
}

Простой тест

var communicator = new Communicator();
var manager = new CommunicationManager(communicator);
var vm = new SystemViewModel( manager );

vm.PropertyChanged += (s,e) => Console.WriteLine( "SystemViewModel.{0} changed", e.PropertyName );

communicator.Connected = true;
communicator.Connected = false;

сгенерированный вывод

SystemViewModel.ConnectionStatus changed
SystemViewModel.ConnectionStatus changed
...