Pytanie Tworzenie modeli ViewModels na podstawie zagnieżdżonych encji modelowych w WPF i MVVM Pattern


Mam problem ze zrozumieniem, jak budować modele widoku na podstawie następujących modeli

(Uprościłem modele, aby były wyraźniejsze)

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

Teraz moim problemem jest, jak zbudować ViewModels ..

Muszę zachować oryginalne relacje za pośrednictwem modeli, beacaus mam Serialize () metoda w Pattern, który serializuje go do pliku XML .. (z pokrewnych utworów i hity)

Aby móc powiązać wzorzec z formantami użytkownika i jego zagnieżdżonymi szablonami, powinienem mieć również PatternViewModel z ObservableCollection <TrackViewModel>, to samo dla TrackViewModel i HitViewModel .. i chcę mieć niestandardowe właściwości prezentacji na zobacz modele, które nie są częścią obiektu biznesowego (kolory i więcej ..)

Po prostu nie wydaje mi się dobrze powielać wszystkie relacje modeli w modelach widoku ... i śledzenie wszystkich tych relacji podczas kodowania modeli podglądu jest również znacznie bardziej podatne na błędy.

ktoś ma lepsze podejście / rozwiązanie?


12
2017-11-22 10:02


pochodzenie




Odpowiedzi:


Jedną rzeczą, którą zrobiłem, z pewnym sukcesem, jest przeniesienie ObservableCollection z modelu. Oto mój ogólny wzór:

  • W obiektach modelu wystawiamy właściwość typu IEnumerable<TModel> który daje dostęp tylko do odczytu do kolekcji. Użyj zwykłego starego List<TModel>, a nie ObservableCollection, jako zbiór pomocniczy.
  • Dla kodu, który musi zmutować kolekcje modeli (dodaj, usuń itp.), Dodaj metody do obiektu modelu. Nie używaj zewnętrznego kodu bezpośrednio manipulującego kolekcją; enkapsulują wewnętrzne metody w modelu.
  • Dodaj zdarzenia do modelu dla każdego dozwolonego rodzaju zmiany. Na przykład, jeśli model obsługuje tylko dodawanie elementów na końcu kolekcji i usuwanie elementów, potrzebne będzie zdarzenie ItemAdded i zdarzenie ItemDeleted. Utwórz potomka EventArgs, który zawiera informacje o dodanym elemencie. Pożar tych zdarzeń z metod mutacji.
  • W swoim ViewModelu, mieć ObservableCollection<TNestedViewModel>.
  • Niech ViewModel przechwytuje zdarzenia w modelu. Ilekroć model mówi, że element został dodany, stwórz instancję ViewModel i dodaj ją do ObservableCollection ViewModel. Ilekroć model mówi, że element został usunięty, wykonaj iterację ObservableCollection, znajdź odpowiedni ViewModel i usuń go.
  • Oprócz obsługi zdarzeń upewnij się, że cały kod mutacji kolekcji jest wykonywany za pośrednictwem modelu - traktuj ViewModel's ObservableCollection jako coś ściśle związanego z konsumpcją widoku, a nie z czymś, co używasz w kodzie.

To sprawia, że ​​wiele kopii kodu dla każdego innego ViewModel, ale to najlepsze, jakie udało mi się wymyślić. Jest co najmniej skalowalna w oparciu o złożoność, jakiej potrzebujesz - jeśli masz kolekcję, która jest tylko dodawana, nie musisz pisać dużo kodu; jeśli masz kolekcję, która obsługuje arbitralne porządkowanie, wstawianie, sortowanie itd., to znacznie więcej pracy.


3
2017-11-22 15:52



To naprawdę ciekawe, jedno z czystszych podejść, ale wciąż jest przesadą pod względem kodu do napisania, chciałbym, żeby był dobry sposób, próbowałem myśleć o podklasowaniu modelu i mnóstwie innych rozwiązań, ale żaden nie jest wystarczająco dobre .. i nie mogę znaleźć dobrego przykładu MVVM przy użyciu modeli z relacjami zagnieżdżonymi. - BFil
Tak, to śmieszna ilość pracy, ale to najlepszy sposób, jaki znalazłem. Chciałbym, aby któryś z frameworków MVVM zastosował konwencje, aby automatycznie owijać ViewModel wokół swojego Modelu, w ten sam sposób, w jaki automatycznie odkrywają Widoki dla twoich ViewModels. Wtedy potrzebujesz tylko jednej ObservableCollection (w twoim modelu), a reszta zajmie się sama. - Joe White


Skończyło się na tym, że użyłem części rozwiązania, które Joe White zaproponował w nieco odmienny sposób

Rozwiązaniem było pozostawienie modeli tak jak na początku, a dołączenie do kolekcji elementu obsługi zdarzenia CollectionChanged wewnętrznych kolekcji, na przykład PatternViewModel byłoby:

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

Mogę więc dołączyć nowy kolor / styl / polecenie w modelach widoku, aby zachować czyste modele podstawowe

I kiedykolwiek dodaję / usuwam / przenoszę elementy w kolekcji modeli podstawowych, kolekcje modeli widoków pozostają ze sobą zsynchronizowane

Na szczęście nie muszę zarządzać mnóstwem obiektów w mojej aplikacji, więc zduplikowane dane i wydajność nie będą stanowić problemu

Nie podoba mi się to zbytnio, ale działa dobrze, i to nie jest ogromna ilość pracy, tylko obsługa zdarzeń dla modelu widoku, który zawiera inne kolekcje modeli widoku (w moim przypadku, jeden dla PatternViewModel do zsynchronizowania TrackViewModels i innego na TrackViewModel do zarządzania HitViewModels)

Nadal jesteś zainteresowany swoimi pomysłami lub lepszymi pomysłami =)


2
2017-11-30 12:15



+1 za udostępnienie rozwiązania. Mimo to wciąż jestem zdumiony, jak bardzo wiążący kod wymaga MVVM w przypadku takich "powszechnych" scenariuszy: - / - Heinzi


Myślę, że miałem ten sam problem i jeśli robisz to jak "PatternViewModel z ObservableCollection <TrackViewModel>", również masz ogromny wpływ na twoją wydajność, ponieważ zaczynasz duplikować dane.

Moje podejście polegało na zbudowaniu - dla twojego przykładu - PatternViewModel z ObservableCollection <Track>. Nie jest to sprzeczne z MVVM, ponieważ widok jest powiązany z kolekcją.

W ten sposób unikniesz powielania relacji.


1
2017-11-22 10:21



To jest właśnie podejście, którego używam teraz, wiążę PatternViewModel, który ma ObservableCollection of Tracks, ale chciałbym dodać logikę prezentacji na utworach i hity, na przykład właściwość koloru na HitViewModel, które zwracają corrispondent Śledź kolor lub ToggleCommand, które włączają i wyłączają ... i musiałbym dodać tę logikę prezentacji na modelu, aby nie powielać relacji i danych, ale jeśli umieściłem logikę prezentacji na modelu, kończę nie podążając za wzorcem ... - BFil


Jedno rozwiązanie, które rozważałem, choć nie jestem pewien, czy w praktyce byłoby to idealne, to użycie konwerterów do stworzenia modelu widoku wokół twojego modelu.

W twoim przypadku możesz się związać Tracks bezpośrednio do (jako przykład) listbox, z konwerterem, który tworzy nowy TrackViewModel z toru. Cała twoja kontrola kiedykolwiek zobaczyłaby byłaby TrackViewModel obiekt, a wszystkie modele zobaczą jeszcze inne modele.

Nie jestem pewien co do dynamicznego aktualizowania tego pomysłu, jednak jeszcze go nie wypróbowałem.


0
2017-12-30 13:27