【问题标题】:Update UI when Entity Model changes in ViewModel当 ViewModel 中的实体模型更改时更新 UI
【发布时间】:2013-09-22 17:21:57
【问题描述】:

我正在尝试提出一种使用实体框架实现 MVVM 模式的好方法,其中我的实体是我的模型。我的DataContext 是我的视图模型。这是问题的一个小再现。

查看

<TextBox Text="{Binding MyText}" />

视图模型

我需要从我的数据库中逐条导航记录。单击视图中的按钮时,会向执行nextRecord() 的视图模型发送命令。 EF 发挥了它的魔力,_myObject 是数据库中的下一行/记录

public class myViewModel: INotifyPropertyChanged
{
    private MyEntityObject _myObject;

    public string MyText
    {
        get { return _myObject.MyText; }
        set
        {
            if (_myObject.MyText != value)
            {
                _myObject.MyText = value;
                OnPropertyChanged("MyText");
            }
        }
    }

    private void _nextRecord()
    {
      _myObject = myEntitiesContext.NextRecord() //pseudocode
    }
}

自动生成的实体模型

public partial class MyEntityObject
{
     public string MyText { get; set; }
}

由于 View 不知道 _myObject 的变化,因此不会在 _myObject 变化时更新。我想到的几种方法。

  1. 我还没有测试过将我的实体包装在 INotifyPropertyChanged 包装类中,但我很谨慎,因为我有很多实体对象。

  2. 我可以为所有属性调用OnPropertyChanged("..."),但是我的一些实体有很多属性,这会很丑。可以使用反射使其更干净,但我可能有未绑定数据的属性。

  3. 我也许可以将此推迟到 UI,当我单击“下一个记录”时以某种方式刷新绑定,但这会破坏 MVVM 并且看起来很脏

如何让 UI 识别来自 _myObject 的更改?

【问题讨论】:

  • 从 _nextRecord() 中手动调用 OnPropertyChanged 怎么样?
  • @Wiktor NextRecord 不是真正的方法,我只是想隐藏获取 next 记录的实现细节,因为它们对于问题并不重要。但你所说的本质上是我的第二种方法。问题在于,说 15-20 个属性是不可扩展的,您需要调用 OnPropertyChanged 15-20 次
  • 那又怎样?这就是真正发生的情况,一个对象被另一个对象替换,这意味着 20 个属性发生了变化,因此需要刷新 20 个 ui 组件。绑定没有魔法,如果某个地方有人没有触发更新,什么都不会发生。
  • @Wiktor 我不关心性能。看到 20 次对 OnPropertyChanged 的调用很难看,而且您必须记住在属性更改时更新/更改。我想我可以利用反射来解决这个问题,但还没有测试过。
  • 可能。恕我直言,最干净的解决方案是反映如果您切换到另一条记录,视图模型会发生变化的事实。这意味着没有内部的隐藏替换。相反,您在新模型上重新创建视图模型并完全重新绑定 ui。

标签: c# wpf entity-framework mvvm data-binding


【解决方案1】:

正如我在 cmets 中提到的,调用 OnPropertyChanged("")OnPropertyChanged(null) 会使所有属性无效,相当于为每个属性调用 OnPropertyChanged。这种行为也是documented here

PropertyChanged 事件可以指示对象上的所有属性 已通过使用 null 或 String.Empty 作为属性名称进行了更改 在 PropertyChangedEventArgs 中。

这意味着您可以在更新对象时简单地添加对OnPropertyChanged("") 的调用以强制 WPF 重新评估与您的视图模型的所有绑定:

private void _nextRecord()
{
    _myObject = myEntitiesContext.NextRecord();
    OnPropertyChanged("");
}

话虽如此,我仍然会选择@Anand 的解决方案 (+1)。关于视图模型是否可以将模型公开为属性的争论一直存在,我倾向于公开它,直到您需要引入一些视图模型特定的逻辑。大多数时候您不必这样做,而且包装模型属性的麻烦也不值得。

【讨论】:

  • 我不知道可以做到这一点——整洁。但是,对于将来可能会发现这一点的人来说,值得注意的是,它只会使 ViewModel 上的属性无效,如果您想要对该视图上的所有绑定(即型号。
  • OnPropertyChanged 不是 INotifyPropertyChanged 提供的方法。您需要提供 OnPropertyChanged 的实现,以便声称它可以处理 null 和 ""
  • @Shoe 这只是说“在PropertyChangedEventArgs 中传递一个空字符串或null 作为属性名称”的一种简短方式。澄清一下,当属性名称为 null 时,知道重新查询所有属性的是 WPF(注册到 PropertyChanged 事件的绑定事件处理程序)。
  • @Shoe 我还在我的答案中添加了一个指向 MSDN 的链接,该链接记录了这种行为。
  • @Adi 我回家后会测试这个
【解决方案2】:

您的代码的问题在于,当_myObject 更改时,MyText 属性更改事件不会被触发。解决方法是创建一个新属性来保存您的实体 并在您的视图中将此属性设置为您的Grids DataContext,如下所示。现在当这条线被执行MyObject = myEntitiesObject.NextRecord()你的视图将被通知有关更改。

public class myViewModel : INotifyPropertyChanged
{

    private MyEntityObject _myObject;

    public MyEntityObject MyObject
    {
        get { return _myObject; }
        set {
            if (_myObject != value)
            {
                _myObject = value;
                OnPropertyChanged("MyObject");
            }
        }
    }


    private void _nextRecord()
    {
      MyObject = myEntitiesObject.NextRecord() //pseudocode
    }
}

查看:

    <Grid DataContext="{Binding MyObject}"> 
        <TextBlock Text="{Binding MyText}"/>
    </Grid>

【讨论】:

  • 这个问题是它使 ViewModel 绑定无效。 MyText 现在将绑定到我的模型而不是 ViewModel,这意味着整个 ViewModel 真的没有任何用途。另一个问题是,除非我将实体对象包装在另一个类中以处理 INotifyPropertyChanged,否则 MyText 将无法响应更改,因为现在绑定在 Model 而不是 viewmodel 上跨度>
  • @Shoe,您的 ViewModel 仍然应该有一个用途——它保存命令、执行初始化和其他事情。此外,如果您确实想在 DataContext 更改后绑定到 ViewModel,您可以只使用 RelativeSource 绑定到 View 的 DataContext。唯一的问题是我们无法看到从 UI 外部对实体进行的更新,但如果您的模型只是一堆普通的旧属性,这是可以预料的。
  • @Guttsy ViewModel 应该是模型的接口。从视图直接绑定到模型意味着我们绕过任何过滤/操作该数据的方法。假设我想验证用户输入视图的数据。现在很难做到这一点,因为我的业务层被绑定绕过了。
  • @Shoe 好吧,业务规则和验证应该在像业务对象这样的适当(可重用)类中实现,并且业务对象可以理解如何通知属性已更改。仅将您的实体用作将数据输入和输出数据库的接口。如果您打算在 ViewModel 中执行所有这些操作,那么您可能并没有真正使用 MVVM,并且您希望您的应用程序不会变大。
  • @Guttsy 抱歉,我不一定指业务逻辑,而只是验证通过的数据。让 VM 负责这很好,但直接绑定到模型意味着您无法验证。此时唯一的解决方法是在视图和模型之间创建一个包装类或另一个层。
【解决方案3】:

一个非常简单但不是很优雅的解决方案,我相信它可以满足需求:在切换记录时,将 DataContext 设置为 null,然后返回 ViewModel。

但是,可以说有更优雅的替代方案需要更多的工作才能满足所有要求。请参阅 Anand 的答案以对此进行改进。

【讨论】:

  • 我对此进行了探索,但事件在命令之前得到处理,因此视图在设置视图模型中的新数据之前被刷新。最后你会遇到一个“一次性”的问题。
【解决方案4】:

View 中的标签应该有 mode 和 UpdateSourceTrigger 属性的值。

【讨论】:

  • 这不会解决任何问题。该问题与从 View 更新 ViewModel 无关,而是相反。
猜你喜欢
  • 2018-05-18
  • 1970-01-01
  • 2013-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多