【问题标题】:Bubbling NotifyOfPropertyChange with embedded classes使用嵌入式类冒泡 NotifyOfPropertyChange
【发布时间】:2020-02-10 23:15:08
【问题描述】:

所以这是一个让我在圈子里跑来跑去的问题。我正在使用一个嵌入式类结构,它需要将其子对象保持私有,但应该能够从这些子对象中的数据向上传递某些 NotifyOfPropertyChange 事件。做这个的最好方式是什么。

我当前的方法是下面的代码,其中我的 SystemViewModel (SystemView) 视图有一个绑定到 CommunicationStatus 属性的元素,并且我有一个父类 SystemViewModel,它具有子类 CommunicationManager,它具有子类 Communicator,如下所示。

让事情变得困难的事情:

1) 在这种情况下,必须假设 Communicator 对 SystemViewModel 不可见,因此不应将 NotifyOfPropertyChanged(() => CommunicationStatus) 放在 Communicator 的 Connected 属性的 set 方法中......除非我错过了很明显的东西。

2) SystemViewModel 不应直接访问 Communicator,因此无法完成从 SystemView.xamlConnected 的绑定。

在我看来,Connected 中的 NotifyOfPropertyChanged 事件应该会出现在父母身上,因为在所有课程中都实施了 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(() =&gt; CommunicationStatus);,绑定元素决定更新。但是,因为我没有在CommunicationStatus 中使用任何类型的set 方法(并且通知事件不会向上传播),所以似乎没有一种直接的方式来通知 XAML 我的值变了。

粗略解决方案:注意CommunicationStatus 的更改并引发NotifyOfPropertyChange(() =&gt; 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 的值发生变化时,我观察到CommunicationStatusget 方法会执行。通过这样做,该执行会导致 get 方法的另一次执行,只是这次 XAML 更新。

谁能解释为什么这个解决方案有效和/或提供一个更有说服力的解决方案?

【问题讨论】:

  • 使用 ReactiveUI 非常简单
  • 这是基本的面向对象理论——简单地包装或隐藏属性。因此,您从成员实例中捕获通知,并且只传递(传播)您特别允许的通知。
  • 我想我的问题是包装机制以及通知如何传播。如果我将成员实例的属性包装在 get 方法中,它的通知是否会因为它在包装器中而自动在包装器中引发?或者更确切地说,如果我得到的是ParentMethod { get {Child.A*Child.B*Child.C} },那么来自ABC 的引发通知是否会导致ParentMethod 中的引发通知?
  • 这个问题的答案是肯定的。这个问题更复杂。查看更新的编辑

标签: c# wpf mvvm caliburn.micro


【解决方案1】:

这里是一个如何使用 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 已更改 SystemViewModel.ConnectionStatus 已更改

【讨论】:

    猜你喜欢
    • 2018-09-06
    • 1970-01-01
    • 1970-01-01
    • 2012-07-26
    • 2014-08-26
    • 1970-01-01
    • 2019-10-04
    • 2016-08-11
    • 2014-07-26
    相关资源
    最近更新 更多