在 WPF 和 MVVM 模式中基于嵌套模型实体构建视图模型

12

我不太理解如何基于以下模型构建视图模型

(我简化了模型以便更清晰地阐述)

public class Hit
{
   public bool On { get; set;}
   public Track Track { get; set; }
}
public class Track
{
   public ObservableCollection<Hit> Hits { get; set; }
   public LinearGradientBrush Color { get; set; }
   public Pattern Pattern { get; set; }
}
public class Pattern
{
   public string Name { get; set; }
   public ObservableCollection<Tracks> Tracks { get; set; }
}
现在,我的问题是如何构建ViewModels。
我需要通过模型保留原始关系,因为我有一个将其序列化为XML文件的Serialize()方法(包括相关的Tracks和Hits)。
为了能够将Pattern绑定到用户控件及其嵌套模板,我还应该有一个PatternViewModel,其中包含一个ObservableCollection<TrackViewModel>,对于TrackViewModel和HitViewModel也是同样的情况,并且我需要在视图模型上有自定义的呈现属性,这些属性不是业务对象的一部分(颜色等)。
但是在视图模型上复制所有模型关系并且还要同时跟踪所有这些关系在编写视图模型时也更容易出错。是否有更好的方法/解决方案?
4个回答

3
我已经尝试过一种方法,将ObservableCollection从模型中移出。以下是我的一般模式:
- 在模型对象中,公开一个类型为IEnumerable的属性,以只读方式访问集合。使用普通的List作为支持集合,而不是ObservableCollection。 - 对于需要修改模型集合(添加、删除等)的代码,请在模型对象中添加方法。不要让外部代码直接操作集合;将其封装在模型方法中。 - 为每种允许更改的类型在模型中添加事件。例如,如果您的模型仅支持向集合末尾添加项目和删除项目,则需要ItemAdded事件和ItemDeleted事件。创建一个EventArgs的派生类,提供有关添加的项的信息。从变异方法中触发这些事件。 - 在ViewModel中,有一个ObservableCollection。 - 使ViewModel挂钩模型上的事件。每当模型说已添加一个项目时,实例化一个ViewModel并将其添加到ViewModel的ObservableCollection中。每当模型说已删除一个项目时,迭代ObservableCollection,找到相应的ViewModel并将其删除。 - 除了事件处理程序之外,确保所有集合变异代码都通过模型完成 - 将ViewModel的ObservableCollection视为严格用于视图消耗的内容,而不是您在代码中使用的内容。
这会导致很多重复的代码适用于每个不同的ViewModel,但这是我能想到的最好方法。它至少可以根据您需要的复杂性进行扩展 - 如果您有一个仅支持添加的集合,则不必编写太多代码;如果您有一个支持任意重新排序、插入、排序等的集合,则需要更多的工作。

1
这很有趣,确实是其中一种更简洁的方法,但在编写代码方面仍然过度冗长。我希望有一个好的方法,我尝试了想到子类化模型和许多其他解决方案,但没有一个足够好。我也找不到一个使用具有嵌套关系的模型的MVVM的好例子。 - BFil
是的,这是一项荒谬的工作量,但这是我找到的最好的方法。我希望MVVM框架之一能够应用约定来自动包装一个ViewModel,就像它们自动发现ViewModel的View一样。然后你只需要一个ObservableCollection(在你的Model中),其余的都会自动处理。 - Joe White

2

我最终采用了Joe White建议的部分解决方案,但稍微有所不同。

解决方案是将模型保持原样,为集合附加内部集合的CollectionChanged事件处理程序,例如,PatternViewModel会是这样:

public class PatternViewModel : ISerializable
{
    public Pattern Pattern { get; set; }
    public ObservableCollection<TrackViewModel> Tracks { get; set; }

    public PatternViewModel(string name)
    {
        Pattern = new Pattern(name);
        Tracks = new ObservableCollection<TrackViewModel>();
        Pattern.Tracks.CollectionChanged += new NotifyCollectionChangedEventHandler(Tracks_CollectionChanged);
    }

    void Tracks_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                foreach (Track track in e.NewItems)
                {
                    var position = Pattern.Tracks.IndexOf((Track) e.NewItems[0]);
                    Tracks.Insert(position,new TrackViewModel(track, this));
                }
                break;
            case NotifyCollectionChangedAction.Remove:
                foreach (Track track in e.OldItems)
                    Tracks.Remove(Tracks.First(t => t.Track == track));
                break;
            case NotifyCollectionChangedAction.Move:
                for (int k = 0; k < e.NewItems.Count; k++)
                {
                    var oldPosition = Tracks.IndexOf(Tracks.First(t => t.Track == e.OldItems[k]));
                    var newPosition = Pattern.Tracks.IndexOf((Track) e.NewItems[k]);
                    Tracks.Move(oldPosition, newPosition);
                }
                break;
        }
    }
}

所以,我可以将新的颜色/样式/命令附加到视图模型上,使我的基础模型保持清洁。每当我在基础模型集合中添加/删除/移动项目时,视图模型集合会相互保持同步。幸运的是,我不必在应用程序中管理大量对象,因此重复数据和性能不会成为问题。虽然我不太喜欢它,但它工作得很好,而且工作量不大,只需要一个包含其他视图模型集合的视图模型的事件处理程序(在我的情况下,一个用于同步TrackViewModels的PatternViewModel,以及另一个用于管理HitViewModels的TrackViewModel)。仍然对您的想法或更好的建议感兴趣=)

+1 分享你的解决方案。尽管如此,我仍然惊讶于即使是这样“常见”的情况,MVVM 需要多少绑定代码 :-/ - Heinzi

1
我认为我遇到了同样的问题,如果你像这样使用"PatternViewModel with an ObservableCollection<TrackViewModel>",你的性能也会受到巨大影响,因为你开始复制数据。
我的方法是构建一个含有ObservableCollection<Track>的PatternViewModel,就像你的例子一样。这并不违背MVVM,因为视图绑定到集合。
这样可以避免重复关系。

这正是我现在使用的方法,我绑定了一个PatternViewModel,其中包含一个Tracks的ObservableCollection,但我想在Tracks和Hits上添加演示逻辑,例如HitViewModel上的Color属性返回相应的Track颜色,或者一个ToggleCommand,将其打开和关闭...我需要在模型上添加这个演示逻辑而不是复制关系和数据,但如果我在模型上放置演示逻辑,我最终无法遵循该模式。 - BFil

0

我一直在考虑的一个解决方案,虽然我不确定它在实践中是否完美,但是可以使用转换器围绕您的模型创建视图模型。

因此,在您的情况下,您可以将Tracks直接绑定到(例如)列表框,使用转换器从Track创建一个新的TrackViewModel。您的所有控件将只看到一个TrackViewModel对象,而您的所有模型将只看到其他模型。

我不确定这个想法的动态更新,因为我还没有尝试过。


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