MVVM中使用INotifyPropertyChanged的Model没有通知ViewModel

4
我第一次尝试使用 MVVM。我的 Windows Phone 应用程序(Mango)有一个模型类、一个视图模型类和一个视图 XAML 页面。我有一些控件(文本框)与 VM 绑定,VM 与模型绑定。
模型和视图模型都实现了 INotifyPropertyChanged 接口。我使用的实现是复制的,这样我就可以尝试弄清楚如何使用 INPC。以下是两个类中列出的代码:
public event PropertyChangedEventHandler PropertyChanged;

private void NotifyPropertyChanged(String info)
{
   if (PropertyChanged != null)
   {
      PropertyChanged(this, new PropertyChangedEventArgs(info));
   }
}

我在 Model 类中有一个属性,可以手动设置(来自文本框)或通过更改其他属性进行计算。我们称之为“Result”。
如果我更改其他属性,并逐步执行,INPC 将在 Model 类中的更改属性和重新计算属性中调用,尽管 “PropertyChanged” 为“null”,所以该代码部分将被跳过。然后在 VM 中,已更改的属性会逐步执行该类的 INPC(作为 set 访问器的一部分),这时候“PropertyChanged” 不为“null”,因此将调用“PropertyChanged” 方法。然而,“Result” 属性没有引发 INPC(该属性不是由其他属性的 set 访问器引发的)。
以下是 Model 中的一个属性,它不是计算属性:
public int AgeSetting
{
   get
   {
      return (int)GetValueOrDefault(AgeSettingKeyName, AgeSettingDefault);
   }
   set
   {
      AddOrUpdateValue(AgeSettingKeyName, value);
      Calculate();
   }
}

以下是模型中计算值的属性。

public int PointsSetting
{
   get
   {
      return (int)GetValueOrDefault(PointsSettingKeyName, PointsSettingDefault);
   }
   set
   {
      AddOrUpdateValue(PointsSettingKeyName, value);
   }
}

从 ViewModel 中,以下是这两个属性:

public int Age
{
   get
   {
      return person.AgeSetting;
   }
   set
   {
      person.AgeSetting = value;
      NotifyPropertyChanged("Age");
   }
} 

public int PointsAllowed
{
   get
   {
      return person.PointsSetting;
   }
   set
   {
      person.PointsSetting = value;
      NotifyPropertyChanged("PointsAllowed");
   }
}

由于我以前从未做过这种事情,我原本以为 INPC 应该从模型类冒泡到 VM 类再到 UI。但实际上似乎不是这样的。

我知道 Result(已计算)属性正在更改,因为我可以离开页面并返回,新显示的值是正确的。我只是不知道如何从模型中的计算值到达视图模型,然后再到达视图。

感谢您的任何建议。


一个有用的工具是VS中的输出窗口。它将告诉您在启动应用程序/初始化UI时是否存在任何绑定失败。可能是您的绑定不正确。 - Ed S.
我之前不知道这个。谢谢你。绑定是有效的,因为它将隔离存储设置中存储的值提取到文本框中。 - Rich Hopkins
我没有时间去弄清楚你真正的问题,所以你能不能做个好心人编辑一下标题,让它成为一个实际的问题,并与你所询问的内容有关系呢?最好只使用标签,感谢! - user1228
有机地使用标签。这是什么意思? - Rich Hopkins
4个回答

5

如果一个计算属性的值发生改变,也请为该属性引发 PropertyChanged 事件。这意味着当单个属性更改时,可能会触发多个属性的事件。

此外,如果在您的设计中可能/明智,您可以直接绑定到模型属性,并将模型作为 ViewModel 的属性。这将大大减少代码维护。


我不明白在已经实现了INotifyPropertyChanged接口的类上使用ViewModel的意义,除非需要其他MVVM功能(例如,将可以完全绑定的内容适配为无意义)。 - RichardOD
谢谢你们两个。是的,Bas,我可以看到我可能需要为多个属性触发。我以为当M中的某些内容更改时,VM会自动看到它。另外,是的,我可以直接从V到M,但这个练习的重点是学习MVVM。我以前没有做过,所以想尝试一下。我认为关键是Maxim在下面说的关于事件处理程序和订阅的事情(我不知道的东西,所以会搜索),但我的VM没有“订阅”M,它只是获取/设置M实例的属性。我想那就是我需要查找的地方。 - Rich Hopkins
1
通常在MVVM中,您不需要让Model类发出任何通知。Model只是逻辑上的存在,并由ViewModel进行访问/修改。基本上,Model永远不会改变,除非由ViewModel指示这样做(类似于MVC中的Model / Controller交互)。例如,ViewModel可能会告诉Model从数据库加载数据,然后ViewModel将在数据加载完成时发出通知。 - EtherDragon
是的,这就是为什么要使用“如果可能/聪明”的原因,虽然它不是领域驱动开发中最干净的方式,但可以节省很多时间。 - Bas
在这种情况下,我最终取消了MV。因为它只是将模型传递到视图中,实际上并不需要它。我将在应用程序的另一部分中使用ModelView,但对于这部分来说,它确实没有必要。我之前尝试使用它只是因为我以前从未使用过MVVM(也没有使用过MVC),但在这里真的不需要。谢谢。 - Rich Hopkins

3

对于那些依赖其他属性值的属性,程序员需要通知多个属性已更改。

假设有一个具有三个属性(得分、乘数和总分)的类,其中总分取决于得分和乘数的值,可以像下面这样编写该类:

public class ViewModel
{
    private int score;
    public int Score
    {
        get
        {
            return this.score;
        }
        set
        {
            if (this.score != value)
            {
                this.score = value;
                // Notify that this property has changed
                NotifyOfPropertyChange(() => this.Score);
                // Notify that a dependant property has changed
                NotifyOfPropertyChange(() => this.Total);
            }
        }
    }

    private int multiplier;
    public int Multiplier
    {
        get
        {
            return this.multiplier;
        }
        set
        {
            if (this.multiplier != value)
            {
                this.multiplier = value;
                // Notify that this property has changed
                NotifyOfPropertyChange(() => this.Multiplier);
                // Notify that a dependant property has changed
                NotifyOfPropertyChange(() => this.Total);
            }
        }
    }

    public int Total
    {
        get
        {
            return this.Score * this.Multiplier;
        }
    }
}

例如,当我设置Score的值时,您会注意到我通知了Score和Total已更改。您的NotifyOfPropertyChange的具体实现与我的不同,但中心思想是相同的。因为Total是依赖于其他属性的值而存在的属性,所以它永远不会通知。相反,Total所依赖的属性负责发出通知。


1

看起来你的模型类没有实现INotifyPropertyChanged接口。如果没有实现,当调用事件处理程序时,它将为空。

public class ViewModel : INotifyPropertyChanged
{
   // Stuff
}

public class Model : INotifyPropertyChanged
{
   // Stuff
}

同时也请查看我的博客,其中有关于MVVM的一些内容。我有一些教程以及一些不使用字符串值(而是使用反射)来触发Inotify的方法。


我看了你的博客文章。虽然我还没有尝试去实现它,但我相信我会实现它的。我喜欢你使用反射来获取更改属性的名称的方式。不过在这种情况下,我决定最好的方法是消除这个视图的modelview。我研究了一下,在这种情况下,modelview 并没有起到什么作用,只是将模型传递给视图。感谢你的建议。 - Rich Hopkins

1
在模型中,计算属性的setter方法中调用NotifyPropertyChanged方法。您的应用程序中的某些逻辑应该更新模型。确保ViewModel已订阅此事件。这非常重要,因为我认为这正是您在这里缺少的东西。
然后,在ViewModel中订阅Models on PropertyChanged事件的EventHandler中,更改ViewModel的相应属性值(这里ViewModel中的方法将更新绑定到UI的属性)。

在我的模型中,每当属性被更改时,NPC方法都会被调用,尽管它不是在setter中,而是在从setter调用的另一个方法中(将值保存到isostore的方法)。我已经逐步进行了调试,并且该方法被调用。然而,我可能错过了你所说的事件处理程序。我不确定你所说的“订阅模型的on PropertyChanged事件”的意思,所以我会研究一下。就目前而言,我的VM属性只是从模型实例中提取的。那可能是关键。我会研究一下的。谢谢! - Rich Hopkins
例如,如果您的模型具有公共事件PropertyChanged,则您的视图模型应该A)引用模型B)在某个时刻订阅此事件。 像model.PropertyChanged + = OnModelChanged; 在OnModelChanged中设置UI绑定的公共属性的值。 这样通知链接就完成了。 - Maxim V. Pavlov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接