基于 WPF 和 MVVM 模式中的嵌套模型实体构建 ViewModel

发布于 2024-10-04 05:49:20 字数 854 浏览 0 评论 0原文

我在理解如何基于以下模型构建视图模型时遇到问题

(我简化了模型以使其更清晰)

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; }
}

现在,我的问题是如何构建 ViewModel ..

我需要通过模型保留原始关系,因为我Pattern 上有一个 Serialize() 方法,将其序列化为 XML 文件。(带有相关的 Tracks 和 Hits)

为了能够将模式绑定到用户控件及其嵌套模板,我还应该有一个带有 ObservableCollection< 的 PatternViewModel ;TrackViewModel>其中,TrackViewModel 和 HitViewModel 也是如此。我需要在不属于业务对象的视图模型上拥有自定义表示属性(颜色等)。

这对我来说似乎不是一件好事复制视图模型上模型的所有关系... 并且在编码视图模型时跟踪所有这些关系也更容易出错..

有人有更好的方法/解决方案吗?

I have a problem understanding how to build view models based on the following models

(I simplified the models to be clearer)

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; }
}

Now, my problem is, how to build the ViewModels..

I need to keep the original relationships through the models, beacaus i have a Serialize() method on the Pattern that serializes it to an XML file.. (with the related Tracks and Hits)

To be able to bind the pattern to the user controls and it's nested templates I should also have a PatternViewModel with an ObservableCollection<TrackViewModel> in it, same thing for the TrackViewModel and the HitViewModel.. and i neet to have custom presentation properties on the view models that aren't part of the business object (colors and more..)

It just seem not a good thing to me to duplicate all of the relationships of the models on the view models...
and keeping track of all this relations while coding the viewmodels is also much more error prone..

anyone has a better approach/solution?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

太阳哥哥 2024-10-11 05:49:20

我做过的一件事(并取得了一些成功)是将 ObservableCollection 移出模型。这是我的一般模式:

  • 在模型对象中,公开一个 IEnumerable类型的属性,该属性提供对集合的只读访问权限。使用普通的旧 List 作为后备集合,而不是 ObservableCollection。
  • 对于需要更改模型集合(添加、删除等)的代码,请向模型对象添加方法。没有外部代码直接操作集合;将其封装在模型的方法内部。
  • 为您允许的每种类型的更改添加事件到模型。例如,如果您的模型仅支持将项目添加到集合末尾以及删除项目,则您将需要 ItemAdded 事件和 ItemDeleted 事件。创建一个 EventArgs 后代,提供有关已添加项目的信息。从突变方法中触发这些事件。
  • 在您的 ViewModel 中,有一个 ObservableCollection
  • 让 ViewModel 挂钩模型上的事件。每当模型显示添加了一个项目时,实例化一个 ViewModel 并将其添加到 ViewModel 的 ObservableCollection 中。每当模型显示某个项目已删除时,迭代 ObservableCollection,找到相应的 ViewModel,然后将其删除。
  • 除了事件处理程序之外,请确保所有集合突变代码都是通过模型完成的 - 将 ViewModel 的 ObservableCollection 严格视为供视图使用的东西,而不是在代码中使用的东西。

这会为每个不同的 ViewModel 带来大量重复的代码,但这是我能想到的最好的。它至少可以根据您需要的复杂性进行扩展——如果您有一个仅添加的集合,则无需编写太多代码;如果你有一个支持任意重新排序、插入、排序等的集合,那么工作量就大得多。

One thing I've done, with some success, is to move the ObservableCollection out of the model. Here's my general pattern:

  • In the model objects, expose a property of type IEnumerable<TModel> that gives read-only access to the collection. Use a plain old List<TModel>, not an ObservableCollection, as the backing collection.
  • For code that needs to mutate the models' collections (add, delete, etc.), add methods to the model object. Don't have outside code directly manipulating the collection; encapsulate that inside methods on the model.
  • Add events to the model for each type of change you allow. For example, if your model only supports adding items to the end of the collection, and deleting items, then you would need an ItemAdded event and an ItemDeleted event. Create an EventArgs descendant that gives information about the item that was added. Fire these events from the mutation methods.
  • In your ViewModel, have an ObservableCollection<TNestedViewModel>.
  • Have the ViewModel hook the events on the model. Whenever the model says an item was added, instantiate a ViewModel and add it to the ViewModel's ObservableCollection. Whenever the model says an item was deleted, iterate the ObservableCollection, find the corresponding ViewModel, and remove it.
  • Apart from the event handlers, make sure all of the collection-mutation code is done via the model -- treat the ViewModel's ObservableCollection as strictly something for the view's consumption, not something you use in code.

This makes for a lot of duplicate code for each different ViewModel, but it's the best I've been able to come up with. It does at least scale based on the complexity you need -- if you have a collection that's add-only, you don't have to write much code; if you have a collection that supports arbitrary reordering, inserts, sorting, etc., it's much more work.

故事↓在人 2024-10-11 05:49:20

我最终使用了 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;
        }
    }
}

所以我可以在视图模型上附加新的颜色/样式/命令,以保持我的基本模型干净

每当我添加/删除/移动基本模型集合中的项目时,视图模型集合与每个模型保持同步其他

幸运的是,我不必在应用程序中管理大量对象,因此重复的数据和性能不会成为问题,

我不太喜欢它,但它运行良好,而且工作量也不是很大,只是包含其他视图模型集合的视图模型的事件处理程序(在我的例子中,一个用于 PatternViewModel 来同步 TrackViewModels,另一个用于 TrackViewModel 来管理 HitViewModels)

仍然对您的想法或更好的想法感兴趣 =)

I ended up using part of the solution that Joe White suggested, in a slighty differ manner

The solution was to just leave the models as they were at the beginning, and attaching to the collections an eventhandler for CollectionChanged of the inner collections, for example, the PatternViewModel would be:

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;
        }
    }
}

So i can attach the new Color/Style/Command on the view models to keep my base models clean

And whenever I add/remove/move items in the base models collection, the view models collections remain in sync with each other

Luckily I don't have to manage lots of object in my application, so duplicated data and performance won't be a problem

I don't like it too much, but it works well, and it's not a huge amount of work, just an event handler for the view model that contains others view model collections (in my case, one for PatternViewModel to sync TrackViewModels and another on TrackViewModel to manage HitViewModels)

Still interested in your thoughs or better ideas =)

月寒剑心 2024-10-11 05:49:20

我想我也有同样的问题,如果你像“PatternViewModel with an ObservableCollection”那样做由于开始复制数据,您的性能也会受到巨大影响。

我的方法是为您的示例构建一个具有 ObservableCollection

的 PatternViewModel。这与 MVVM 并不矛盾,因为视图是绑定到集合的。

这样您就可以避免重复的关系。

I think I had the same problem and if you do it like "PatternViewModel with an ObservableCollection<TrackViewModel>" you also get a massive impact on your performance because you start duplicating data.

My approach was to build - for your expample - a PatternViewModel with a ObservableCollection<Track>. It's no contradiction to MVVM because the view is bound to the collection.

This way you may avoid the duplication of the relationships.

羅雙樹 2024-10-11 05:49:20

我一直在考虑的一种解决方案是使用转换器围绕模型创建视图模型,尽管我不确定它在实践中是否能完美工作。

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

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

One solution I've been considering, although I'm not sure if it would work perfectly in practice, is to use converters to create a viewmodel around your model.

So in your case, you could bind Tracks directly to (as an example) a listbox, with a converter that creates a new TrackViewModel from the Track. All your control would ever see would be a TrackViewModel object, and all your models will ever see is other models.

I'm not sure about the dynamic updating of this idea though, I've not tried it out yet.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文