32

我花了相当长的时间来尝试为以下挑战找到一个优雅的解决方案。我一直无法找到解决问题的解决方案。

我有一个简单的视图、视图模型和模型设置。为了解释起见,我将保持非常简单。

  • Model有一个名为TitleString 类型的属性。
  • ModelView. _
  • 模型上View有一个TextBlock数据绑定到Title该模型。
  • ViewModel一个方法调用Save(),将保存ModelServer
  • Server可以推送对Model

到现在为止还挺好。现在我需要进行两项调整,以使模型与Server. 服务器的类型并不重要。只知道我需要调用Save()才能将模型推送到Server.

调整一:

  • Model.Title需要调用该属性RaisePropertyChanged()才能将 对 所做的更改Model转换ServerView。这很好用,因为它Model是 DataContext 的View

还不错。

调整二:

  • 下一步是调用Save()以保存从View到 对Model所做的更改Server。这就是我卡住的地方。当模型发生更改时,我可以处理调用 Save() 的Model.PropertyChanged事件,ViewModel但这会使其回显服务器所做的更改。

我正在寻找一个优雅且合乎逻辑的解决方案,并且如果它有意义,我愿意改变我的架构。

4

5 回答 5

75

过去,我编写了一个支持从多个位置“实时”编辑数据对象的应用程序:应用程序的许多实例可以同时编辑同一个对象,当有人将更改推送到服务器时,其他人都会收到通知并(在最简单的情况下)立即看到这些变化。以下是它是如何设计的摘要。

设置

  1. 视图总是绑定到 ViewModel。我知道它有很多样板,但是直接绑定到模型是不可接受的,除了最简单的场景;它也不符合 MVVM 的精神。

  2. ViewModel独自负责推动更改。这显然包括将更改推送到服务器,但也可能包括将更改推送到应用程序的其他组件。

    为此,ViewModels 可能希望克隆它们包装的模型,以便它们可以向应用程序的其余部分提供事务语义,就像它们提供给服务器一样(即,您可以选择何时将更改推送到应用程序的其余部分,您如果每个人都直接绑定到同一个模型实例,则无法做到)。像这样隔离更改需要更多的工作,它也开辟了强大的可能性(例如,撤消更改是微不足道的:不要推动它们)。

  3. ViewModel 依赖于某种数据服务。数据服务是一个应用程序组件,位于数据存储和消费者之间,并处理它们之间的所有通信。每当 ViewModel 克隆其模型时,它还会订阅数据服务公开的适当“数据存储更改”事件。

    这允许 ViewModel 被通知其他 ViewModel 已推送到数据存储的“他们的”模型的更改并做出适当的反应。通过适当的抽象,数据存储也可以是任何东西(例如,该特定应用程序中的 WCF 服务)。

工作流程

  1. 创建一个 ViewModel 并分配一个模型的所有权。它立即克隆模型并将这个克隆暴露给视图。它依赖于数据服务,它告诉 DS 它想要订阅通知以更新此特定模型。ViewModel 不知道是什么标识了它的模型(“主键”),但它不需要,因为这是 DS 的责任。

  2. 当用户完成编辑时,他们与调用 VM 上的命令的视图交互。然后 VM 调用 DS,将所做的更改推送到其克隆模型。

  3. DS 保持这些更改并另外引发一个事件,通知所有其他感兴趣的 VM 已对 Model X 进行了更改;新版本的模型作为事件参数的一部分提供。

  4. 已分配同一模型所有权的其他虚拟机现在知道外部更改已经到来。他们现在可以决定如何更新手头所有拼图的视图(模型的“先前”版本,它被克隆;“脏”版本,它是克隆;和“当前”版本,它作为事件参数的一部分被推送)。

笔记

  • 模型INotifyPropertyChanged仅由视图使用;如果 ViewModel 想知道模型是否“脏”,它总是可以将克隆版本与原始版本进行比较(如果它一直保留,我建议如果可能的话)。
  • ViewModel 以原子方式将更改推送到服务器,这很好,因为它确保数据存储始终处于一致状态。这是一种设计选择,如果您想以不同的方式做事,另一种设计会更合适。
  • this如果 ViewModel作为参数传递给“推送更改”调用,则服务器可以选择不为负责此更改的 ViewModel 引发“模型更改”事件。即使它没有,如果 ViewModel 看到 Model 的“当前”版本与其自己的克隆相同,它也可以选择什么都不做。
  • 通过足够的抽象,可以将更改推送到其他机器上运行的其他进程,就像它们可以推送到 shell 中的其他视图一样容易。

希望这可以帮助; 如果需要,我可以提供更多说明。

于 2012-05-04T08:34:03.533 回答
7

我建议将控制器添加到 MVVM 组合(MVCVM?)以简化更新模式。

控制器在更高级别侦听更改并在模型和视图模型之间传播更改。

保持清洁的基本规则是:

  • ViewModel 只是保存某种形状数据的愚蠢容器。他们不知道数据来自哪里或显示在哪里。
  • 视图显示特定形状的数据(通过绑定到视图模型)。 他们不知道数据来自哪里,只知道如何显示。
  • 模型提供真实数据。他们不知道在哪里消费。
  • 控制器实现逻辑。诸如在 VM 中为 ICommand 提供代码、监听数据更改等事情。它们从模型中填充 VM。让他们监听 VM 更改并更新模型是有意义的。

正如另一个答案中提到的,您DataContext应该是虚拟机(或它的属性),而不是模型。指向 DataModel 使得分离关注点变得很困难(例如,对于测试驱动开发)。

大多数其他解决方案将逻辑放在“不正确”的 ViewModel 中,但我看到控制器的好处一直被忽视。该死的 MVVM 首字母缩写词!:)

于 2012-05-04T08:52:28.203 回答
1

仅当模型实现 INotifyPropertyChanged 接口时,绑定模型才能直接查看。(例如,您的模型由实体框架生成)

模型实现 INotifyPropertyChanged

你可以这样做。

public interface IModel : INotifyPropertyChanged //just sample model
{
    public string Title { get; set; }
}

public class ViewModel : NotificationObject //prism's ViewModel
{
    private IModel model;

    //construct
    public ViewModel(IModel model)
    {
        this.model = model;
        this.model.PropertyChanged += new PropertyChangedEventHandler(model_PropertyChanged);
    }

    private void model_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "Title")
        {
            //Do something if model has changed by external service.
            RaisePropertyChanged(e.PropertyName);
        }
    }
    //....more properties
}

ViewModel 作为 DTO

如果 Model 实现 INotifyPropertyChanged(取决于),在大多数情况下,您可以将其用作 DataContext。但在 DDD 中,大多数 MVVM 模型将被视为 EntityObject 而不是真正的域模型。

更有效的方法是使用 ViewModel 作为 DTO

//Option 1.ViewModel act as DTO / expose some Model's property and responsible for UI logic.
public string Title
{
    get 
    {
        // some getter logic
        return string.Format("{0}", this.model.Title); 
    }
    set
    {
        // if(Validate(value)) add some setter logic
        this.model.Title = value;
        RaisePropertyChanged(() => Title);
    }
}

//Option 2.expose the Model (have self validation and implement INotifyPropertyChanged).
public IModel Model
{
    get { return this.model; }
    set
    {
        this.model = value;
        RaisePropertyChanged(() => Model);
    }
}

上面两个 ViewModel 的属性都可以用于绑定,而不会破坏 MVVM 模式(模式!= 规则),这真的取决于。

还有一件事.. ViewModel 依赖于 Model。如果模型可以被外部服务/环境改变。使事情变得复杂的是“全球状态”。

于 2012-05-04T06:49:09.317 回答
0

如果您唯一的问题是服务器的更改会立即重新保存,为什么不执行以下操作:

//WARNING: typed in SO window
public class ViewModel
{
    private string _title;
    public string Title
    {
        get { return _title; }
        set
        {
            if (value != _title) 
            {
                _title = value;
                this.OnPropertyChanged("Title");
                this.BeginSaveToServer();
            }
        }
    }

    public void UpdateTitleFromServer(string newTitle)
    {
        _title = newTitle;
        this.OnPropertyChanged("Title"); //alert the view of the change
    }
}

此代码手动提醒来自服务器的属性更改视图,而无需通过属性设置器,因此无需调用“保存到服务器”代码。

于 2012-05-03T18:29:34.653 回答
0

你有这个问题的原因是你的模型不知道它是否脏。

string Title {
  set {
    this._title = value;
    this._isDirty = true; // ??!!
  }
}}

解决方案是通过单独的方法复制服务器更改:

public void CopyFromServer(Model serverCopy)
{
  this._title = serverCopy.Title;
}
于 2012-05-04T05:52:35.940 回答