【问题标题】:Bubbling NotifyPropertyChanged冒泡NotifyPropertyChanged
【发布时间】:2018-07-30 22:20:03
【问题描述】:

根据this answer,我不应该为 NotifyPropertyChanges 冒泡层次结构而烦恼,但我仍然无法使用这样的(简化测试)结构:

数据保存类

public class TestNotifyChanged : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private string _Test = "default";
    public string Test
    {
        get
        {
            return _Test;
        }
        set
        {
            if(_Test!=value)
            {
                _Test = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Test"));
            }
        }
    }
}

使用该 Test-Class 和 Test-Property 的 ViewModel:

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private TestNotifyChanged tnc = new TestNotifyChanged(); // only to init, otherwise VS screams at me

    public ViewModel(TestNotifyChanged tnc)
    {
        tnc = tnc; // getting an instance of TestNotifyChanged from "Master" passed in, which hopefully will be replaces by a singleton class.
    }

    private string _Test;
    public string Test
    {
        get
        {
            return tnc.Test;  // this might be the crucial part!?
        }
        set
        {
            if (_Test != value) // never hits that, as I would expect, but you never know..
            {
                _Test = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Test"));  // of course also never hit, as expected
            }
        }
    }
}

最后是我的 MainWindow cs

public partial class MainWindow : Window
{
    TestNotifyChanged tnc;
    public MainWindow()
    {
        InitializeComponent();
        tnc = new TestNotifyChanged();
        DataContext = new ViewModel(tnc); // pass in my Test-Object that has the Values.
    }

    private void ButtonGet_Click(object sender, RoutedEventArgs e)
    {
        tnc.Test = "new Value";
        MessageBox.Show($"{tnc.Test}"); // got "new Value" here!
    }
}

在 xaml 中,除了那个按钮之外,我还有一个简单的 TextBlock,它绑定到 ViewModel 的测试属性:

 <TextBlock x:Name="OutputId" Text="{Binding Path=Test, Mode=OneWay}"/>

现在发生了什么:

  • 默认值“default”显示在TextBlock中。

  • 当我点击按钮时,messageBox 显示“新值”

  • TextBlock 更新为“新值”

我想要达到的目标:

  • 看起来很简单:TextBlock 应该更新为“新值”

当我直接在 ViewModel 上设置测试值 时,我可以轻松完成这项工作 - 但这似乎不正确,并且与我认为可以构建我的应用程序/代码的方式相去甚远。未来的目标是拥有一个拥有大部分数据的单例(静态行不通)“RecordStore”(并从 API、本地数据库或仅从内存中获取数据,如果其中任何一项完成)

所以问题是:
为什么 NotifyPropertyChange 没有冒泡到 View/ViewModel?
还是有其他我没有看到的问题?

我读过INotifyPropertyChanged bubbling in class hierarchyWhat is a good way to bubble up INotifyPropertyChanged events through ViewModel properties with MVVM?https://docs.microsoft.com/en-us/dotnet/framework/winforms/how-to-implement-the-inotifypropertychanged-interfaceOnPropertyChange called but not taking any effect on UI

大多数问题也很老了......

编辑:
我以这种方式尝试了@MineR 的建议:

// made tnc public in ViewModel
public TestNotifyChanged tnc = new TestNotifyChanged();

// changed Binding directly to that (and it's Property):
<TextBlock x:Name="OutputId" Text="{Binding Path=tnc.Test, Mode=OneWay}"/>

不幸的是,我现在连默认都没有,所以我一定是误会了。

EDIT2:
我在第一次编辑中做错了一件事:

// this isn't recognized as bindable parameter:
public TestNotifyChanged tnc = new TestNotifyChanged();
// it instead has to be
public TestNotifyChanged tnc { get; }

我做了TNC,去掉了本地的Test参数,直接绑定到Path=TNC.Test

所以我明白,PropertyChanges 不会按照我希望/认为的方式冒泡,最好直接绑定到嵌套对象。

【问题讨论】:

  • 在您的 ViewModel 中,_Test = value; 实际上应该是 tnc.Test = value;?
  • @Sach 啊,好的。我会试试的! - 但tnc.Text 是唯一一个击中set{} 的源 - 无论如何都不会被击中!? - 尝试后:不,没有任何改变。
  • 在你拥有的代码中根本没有冒泡。如果你想让它在不冒泡的情况下工作,你必须公开 tnc,并绑定到 tnc.Test - 这就是你引用的第一个问题中所做的。或者,您可以通过订阅 tnc.PropertyChanged 并在引发 tnc.PropertyChanged 时引发 PropertyChanged 来冒泡它。如果您可以摆脱第一个选项,那就这样做 - 它更简单。
  • @MineR 不幸的是,我无法使用第一个选项完成它。你能给我举个第二种选择的例子吗? (虽然我更喜欢第一个选项....会更有意义)
  • 如果你把tnc设为public,它也应该是一个要绑定的属性:public TestNotifyChanged tnc {get;set;}。它对我有用,但是你的 VM 中的 Test 属性不再使用,所以它并不是真正的“冒泡”。

标签: c# wpf


【解决方案1】:

“冒泡”是routed events 的概念。像 PropertyChanged 这样的常规事件不会“冒泡”。

除了 ViewModel 中明显的错误 tnc = tnc;(应该是 this.tnc = tnc;)之外,这两个类的 Test 属性是不相关的。为了更新自己的 Test 属性,ViewModel 必须在 tnc 注册一个 PropertyChanged 事件处理程序。并且它必须在自己的 Test 属性发生变化时更新tnc 的属性。

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private TestNotifyChanged tnc;

    public ViewModel(TestNotifyChanged t)
    {
        tnc = t;
        tnc.PropertyChanged += (s, e) =>
        {
            if (e.PropertyName == nameof(Test) || string.IsNullOrEmpty(e.PropertyName))
            {
                Test = tnc.Test; // update ViewModel.Test from TestNotifyChanged.Test
            }
        };
    }

    private string test;
    public string Test
    {
        get
        {
            return test; // always return own value
        }
        set
        {
            if (test != value)
            {
                test = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Test)));

                tnc.Test = Test; // update TestNotifyChanged.Test from ViewModel.Test
            }
        }
    }
}

或者,删除 Test 属性的支持字段并仅对 tnc.Test 进行操作:

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private TestNotifyChanged tnc;

    public ViewModel(TestNotifyChanged t)
    {
        tnc = t;
        tnc.PropertyChanged += (s, e) =>
        {
            if (e.PropertyName == nameof(Test) || string.IsNullOrEmpty(e.PropertyName))
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Test)));
            }
        };
    }

    public string Test
    {
        get { return tnc.Test; }
        set { tnc.Test = Test; }
    }
}

幸运的是,完全没有必要。

可能只有一个公共的Tnc 属性,例如

public class ViewModel
{
    public TestNotifyChanged Tnc { get; }

    public ViewModel(TestNotifyChanged tnc)
    {
        Tnc = tnc;
    }
}

使用这样的绑定:

<TextBlock Text="{Binding Tnc.Test}"/>

【讨论】:

  • 如果我们使用您的第一个答案,根据我的理解,这是真正的冒泡,那么冒泡多个属性会不会有点棘手?
  • 这不是冒泡。这很棘手,因为您必须注意附加和分离 PropertyChanged 事件处理程序。如前所述,幸运的是根本没有必要。
  • 感谢您的全面回答!我之前尝试过第二个版本,但犯了一个严重的错误。我有 public TestNotifyChanged Tnc = new TestNofifyChanged();,所以 xaml 没有将其识别为可绑定属性。
猜你喜欢
  • 1970-01-01
  • 2017-03-11
  • 2011-02-13
  • 2013-10-09
  • 1970-01-01
  • 2015-09-12
  • 1970-01-01
  • 2011-10-20
  • 2014-03-26
相关资源
最近更新 更多