代码之家  ›  专栏  ›  技术社区  ›  Jeff

冒泡NotifyPropertyChanged

  •  1
  • Jeff  · 技术社区  · 8 年前

    根据 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"));
                }
            }
        }
    }
    

    使用该测试类和测试属性的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
                }
            }
        }
    }
    

    最后是我的主窗口

    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中,除了这个按钮之外,我还有一个简单的文本块,它绑定到ViewModel的测试属性:

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

    现在发生了什么:

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

    • 当我单击按钮时,消息框显示“新值”

    • 文本块是 更新为“新值”

    我想实现的目标:

    • 似乎很简单:TextBlock应该更新为“新值”

    当我直接设置测试值时,我可以很容易地使这个工作 在视图模型上 -但这似乎不对,而且与我认为我可以构建应用程序/代码的想法相去甚远。未来的目标是要有一个单例(静态的我想不通)的“记录存储”,它拥有大部分的数据(并且从一个API、本地数据库或者仅仅从内存中获取,如果这些都完成了的话)

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

    我读过 INotifyPropertyChanged bubbling in class hierarchy What 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-interface OnPropertyChange called but not taking any effect on UI

    这些问题大多也很古老。。。

    编辑:
    我试过“矿工”的建议:

    // 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}"/>
    

    不幸的是,现在我甚至没有得到默认值,所以我一定误解了smth。

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

    // 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

    所以我明白了,财产的变化 不要 按照我的期望/想法,最好直接绑定到嵌套对象。

    1 回复  |  直到 8 年前
        1
  •  2
  •   Clemens    8 年前

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

    除了明显的虫子 tnc = tnc; 在视图模型中(应该是 this.tnc = tnc; )这两个类的测试属性是不相关的。为了更新自己的测试属性,ViewModel必须在 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
                }
            }
        }
    }
    

    或者,删除测试属性的backing字段,只对 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}"/>