Dobre przykłady szablonu MVVM

141

Obecnie pracuję z szablonem Microsoft MVVM i frustruje mnie brak szczegółowych przykładów. Dołączony przykład ContactBook pokazuje bardzo mało obsługi poleceń, a jedyny inny przykład, jaki znalazłem, pochodzi z artykułu MSDN Magazine, w którym koncepcje są podobne, ale używa nieco innego podejścia i nadal nie jest skomplikowane. Czy są jakieś przyzwoite przykłady MVVM, które przynajmniej pokazują podstawowe operacje CRUD i przełączanie dialogów / treści?


Sugestie wszystkich były naprawdę przydatne i zacznę tworzyć listę dobrych zasobów

Struktury / szablony

Przydatne artykuły

Screencasty

Dodatkowe biblioteki

jwarzech
źródło
Cieszę się, że te zasoby pomogły. Obecnie pracuję nad moją drugą produkcyjną aplikacją MVVM i będę nadal dodawać zawartość, która będzie pomocna dla początkujących, gdy ją napotkam.
jwarzech

Odpowiedzi:

59

Niestety nie ma jednej świetnej przykładowej aplikacji MVVM, która robi wszystko, a istnieje wiele różnych podejść do robienia rzeczy. Po pierwsze, możesz chcieć zapoznać się z jednym z dostępnych frameworków aplikacji (Prism to przyzwoity wybór), ponieważ zapewniają one wygodne narzędzia, takie jak wstrzykiwanie zależności, polecenia, agregacja zdarzeń itp., Aby łatwo wypróbować różne wzorce, które Ci odpowiadają .

Wersja pryzmatu:
http://www.codeplex.com/CompositeWPF

Zawiera całkiem przyzwoitą przykładową aplikację (handlowca giełdowego) wraz z wieloma mniejszymi przykładami i instrukcjami. Przynajmniej jest to dobra demonstracja kilku popularnych wzorców podrzędnych, których ludzie używają, aby MVVM faktycznie działał. Sądzę, że mają przykłady zarówno dla CRUD, jak i dialogów.

Pryzmat niekoniecznie pasuje do każdego projektu, ale warto się z nim zapoznać.

CRUD: Ta część jest całkiem prosta, dwukierunkowe powiązania WPF ułatwiają edytowanie większości danych. Prawdziwą sztuczką jest zapewnienie modelu, który ułatwi konfigurację interfejsu użytkownika. Przynajmniej chcesz się upewnić, że Twój ViewModel (lub obiekt biznesowy) jest zaimplementowany INotifyPropertyChangeddo obsługi powiązania i możesz powiązać właściwości bezpośrednio z kontrolkami interfejsu użytkownika, ale możesz również chcieć zaimplementować go w IDataErrorInfocelu walidacji. Zazwyczaj, jeśli używasz jakiegoś rozwiązania ORM, konfiguracja CRUD jest bardzo prosta.

W tym artykule przedstawiono proste operacje Crud: http://dotnetslackers.com/articles/wpf/WPFDataBindingWithLINQ.aspx

Jest on oparty na LinqToSql, ale nie ma to znaczenia dla przykładu - ważne jest tylko, aby zaimplementować obiekty biznesowe INotifyPropertyChanged(które klasy generowane przez LinqToSql robią). MVVM nie jest celem tego przykładu, ale nie sądzę, że ma to znaczenie w tym przypadku.

W tym artykule przedstawiono sprawdzanie poprawności danych
http://blogs.msdn.com/wpfsdk/archive/2007/10/02/data-validation-in-3-5.aspx

Ponownie, większość rozwiązań ORM generuje klasy, które już implementują IDataErrorInfoi zazwyczaj udostępniają mechanizm ułatwiający dodawanie niestandardowych reguł walidacji.

W większości przypadków możesz wziąć obiekt (model) utworzony przez jakiś ORM i zawinąć go w ViewModel, który przechowuje go i polecenia do zapisywania / usuwania - i jesteś gotowy do powiązania interfejsu użytkownika bezpośrednio z właściwościami modelu.

Widok wyglądałby mniej więcej tak (ViewModel ma właściwość, Itemktóra przechowuje model, jak klasa utworzona w ORM):

<StackPanel>
   <StackPanel DataContext=Item>
      <TextBox Text="{Binding FirstName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
      <TextBox Text="{Binding LastName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
   </StackPanel>
   <Button Command="{Binding SaveCommand}" />
   <Button Command="{Binding CancelCommand}" />
</StackPanel>

Dialogi: Dialogi i MVVM są trochę trudne. Wolę używać podejścia Mediator w oknach dialogowych, możesz przeczytać więcej na ten temat w tym pytaniu StackOverflow:
przykład okna dialogowego WPF MVVM

Moje zwykłe podejście, które nie jest do końca klasycznym MVVM, można podsumować w następujący sposób:

Klasa bazowa dla okna dialogowego ViewModel, która udostępnia polecenia dla akcji zatwierdzenia i anulowania, zdarzenie, które informuje widok, że okno dialogowe jest gotowe do zamknięcia i cokolwiek innego będzie potrzebne we wszystkich oknach dialogowych.

Ogólny widok okna dialogowego - może to być okno lub niestandardowa kontrolka typu nakładki „modalnej”. W gruncie rzeczy jest to prezenter treści, do którego zrzucamy viewmodel i obsługuje okablowanie do zamykania okna - na przykład przy zmianie kontekstu danych można sprawdzić, czy nowy ViewModel jest dziedziczony z klasy bazowej, a jeśli tak, zasubskrybuj odpowiednie zdarzenie zamknięcia (program obsługi przypisze wynik dialogu). Jeśli zapewniasz alternatywną uniwersalną funkcjonalność zamykania (na przykład przycisk X), powinieneś upewnić się, że uruchomiłeś również odpowiednie polecenie zamknięcia w ViewModel.

Gdzieś, gdzie musisz dostarczyć szablony danych dla swoich ViewModels, mogą one być bardzo proste, zwłaszcza, że ​​prawdopodobnie masz widok dla każdego okna dialogowego zamknięty w osobnej kontrolce. Domyślny szablon danych dla ViewModel wyglądałby wtedy mniej więcej tak:

<DataTemplate DataType="{x:Type vmodels:AddressEditViewModel}">
   <views:AddressEditView DataContext="{Binding}" />
</DataTemplate>

Widok okna dialogowego musi mieć do nich dostęp, ponieważ w przeciwnym razie nie będzie wiedział, jak wyświetlić ViewModel, poza wspólnym interfejsem okna dialogowego jego zawartość jest zasadniczo taka:

<ContentControl Content="{Binding}" />

Niejawny szablon danych zamapuje widok na model, ale kto go uruchamia?

To jest część niezbyt tak mvvm. Jednym ze sposobów jest użycie wydarzenia globalnego. Wydaje mi się, że lepiej jest użyć konfiguracji typu agregatora zdarzeń, dostarczanej przez wstrzykiwanie zależności - w ten sposób zdarzenie jest globalne dla kontenera, a nie dla całej aplikacji. Prism używa struktury Unity do semantyki kontenerów i wstrzykiwania zależności, i ogólnie bardzo lubię Unity.

Zwykle ma sens subskrybowanie tego zdarzenia przez okno główne - może otworzyć okno dialogowe i ustawić kontekst danych na ViewModel, który jest przekazywany z podniesionym zdarzeniem.

Skonfigurowanie tego w ten sposób pozwala ViewModels poprosić aplikację o otwarcie okna dialogowego i reagowanie na działania użytkownika w nim, nie wiedząc nic o interfejsie użytkownika, więc w większości MVVM-ność pozostaje kompletna.

Są jednak chwile, w których interfejs użytkownika musi podnieść okna dialogowe, co może nieco utrudnić sprawę. Zastanów się na przykład, czy pozycja okna dialogowego zależy od położenia przycisku, który je otwiera. W takim przypadku musisz mieć pewne informacje specyficzne dla interfejsu użytkownika, gdy żądasz otwarcia okna dialogowego. Generalnie tworzę oddzielną klasę, która zawiera ViewModel i kilka istotnych informacji o interfejsie użytkownika. Niestety, pewne sprzężenie wydaje się tam nieuniknione.

Pseudo kod programu obsługi przycisku, który wywołuje okno dialogowe wymagające danych pozycji elementu:

ButtonClickHandler(sender, args){
    var vm = DataContext as ISomeDialogProvider; // check for null
    var ui_vm = new ViewModelContainer();
    // assign margin, width, or anything else that your custom dialog might require
    ...
    ui_vm.ViewModel = vm.SomeDialogViewModel; // or .GetSomeDialogViewModel()
    // raise the dialog show event
}

Widok okna dialogowego zostanie powiązany z danymi pozycji i przekaże zawarty ViewModel do pliku wewnętrznego ContentControl. Sam ViewModel nadal nie wie nic o interfejsie użytkownika.

Ogólnie rzecz biorąc, nie używam DialogResultwłaściwości return ShowDialog()metody ani nie oczekuję, że wątek będzie blokowany, dopóki okno dialogowe nie zostanie zamknięte. Niestandardowe modalne okno dialogowe nie zawsze działa w ten sposób, aw złożonym środowisku często nie chcesz, aby program obsługi zdarzeń i tak blokował w ten sposób. Wolę pozwolić ViewModels zająć się tym - twórca ViewModel może subskrybować odpowiednie zdarzenia, ustawiać metody zatwierdzania / anulowania itp., Więc nie ma potrzeby polegać na tym mechanizmie interfejsu użytkownika.

Więc zamiast tego przepływu:

// in code behind
var result = somedialog.ShowDialog();
if (result == ...

Używam:

// in view model
var vm = new SomeDialogViewModel(); // child view model
vm.CommitAction = delegate { this.DoSomething(vm); } // what happens on commit 
vm.CancelAction = delegate { this.DoNothing(vm); } // what happens on cancel/close (optional)
// raise dialog request event on the container

Wolę to w ten sposób, ponieważ większość moich okien dialogowych to nieblokujące pseudo-modalne kontrolki i zrobienie tego w ten sposób wydaje się prostsze niż obejście tego. Łatwy do przeprowadzenia test jednostkowy.

Egor
źródło
Dzięki za szczegółową odpowiedź! Niedawno odkryłem, że moim największym problemem jest to, że muszę mieć MainViewModel komunikować się z innymi modelami widoku, aby obsłużyć przepływ aplikacji. Wydaje się jednak, że popularnym podejściem wydaje się być MVVM + Mediator.
jwarzech
2
Mediator zdecydowanie pomaga, wzorzec agregatora zdarzeń (Prism ma dobrą implementację) jest również bardzo pomocny, gdy celem jest niskie sprzężenie. Ponadto Twój główny model widoku zazwyczaj ma własne modele podrzędne i nie powinien mieć problemów z komunikacją z nimi. Musisz użyć mediatora i / lub agregatora zdarzeń, gdy Twoje podrzędne modele widoku muszą wchodzić w interakcje z innymi modułami w Twojej aplikacji, o których niekoniecznie wiedzą - w tym z interfejsem użytkownika (mój przykład okna dialogowego dotyczy tego konkretnego przypadku).
Egor
1
Bardzo pomocne okazały się wytyczne dotyczące pracy z oknami dialogowymi i oknami. Jednak utknąłem z kilkoma problemami: 1. Jak ustawić tytuł okna z widoku? 2. Jak radzisz sobie z ustawieniem okna właściciela?
djskinner
@Daniel Skinner: Zakładam, że mówisz tutaj o dialogach, popraw mnie, jeśli się mylę. Tytuł okna dialogowego jest po prostu kolejną właściwością i możesz go powiązać z czymkolwiek chcesz. Jeśli zastosowałeś moje podejście z klasą viewmodel okna bazowego (udawajmy, że ma ona właściwość title), to w całym swoim ogólnym oknie dialogowym możesz użyć powiązania UI z interfejsem użytkownika, aby ustawić tytuł na {Binding Path = DataContext.Title, ElementName = NameOfContentPresenter}. Okno właściciela jest trochę dziwne - oznacza to, że mediator, który faktycznie wyświetla okno dialogowe, musi wiedzieć o widoku aplikacji głównej.
Egor
W rzeczywistości cofam to - niezależnie od tego, jak to zorganizujesz w pewnym momencie, ktokolwiek faktycznie wyskakuje okno dialogowe, musi mieć odniesienie do okna / widoku aplikacji głównej. Zauważ, że powiedziałem: „Zwykle ma sens subskrybowanie tego zdarzenia przez okno główne - może otworzyć okno dialogowe i ustawić kontekst danych na model widoku, który jest przekazywany z podniesionym zdarzeniem”. Tutaj możesz ustawić właściciela.
Egor
6

Jason Dolinger wykonał dobry screencast z MVVM. Jak wspomniał Egor, nie ma jednego dobrego przykładu. Skończyły się. Większość z nich to dobre przykłady MVVM, ale nie dotyczy to złożonych problemów. Każdy ma swój własny sposób. Laurent Bugnion ma również dobry sposób komunikowania się między modelami widoków. http://blog.galasoft.ch/archive/2009/09/27/mvvm-light-toolkit-messenger-v2-beta.aspx Cinch jest również dobrym przykładem. Paul Stovel ma dobry post, który wyjaśnia również wiele z jego frameworku Magellana.

nportelli
źródło
3

Czy spojrzałeś na Caliburna ? Próbka ContactManager zawiera wiele dobrych rzeczy. Ogólne przykłady WPF zapewniają również dobry przegląd poleceń. Dokumentacja jest dość dobra, a fora są aktywne. Zalecana!

Andy S.
źródło
2

Przykładowy projekt w środowisku Cinch przedstawia podstawowe narzędzia CRUD i nawigacyjne. To dość dobry przykład wykorzystania MVVM i zawiera wieloczęściowy artykuł wyjaśniający jego użycie i motywacje.

Reed Copsey
źródło
2

Podzieliłem się też z twoją frustracją. Piszę aplikację i miałem te 3 wymagania:

  • Rozciągliwy
  • WPF z MVVM
  • Przykłady zgodne z GPL

Wszystko, co znalazłem, to kawałki i kawałki, więc zacząłem pisać to najlepiej, jak potrafiłem. Kiedy trochę się w to zagłębiłem, zdałem sobie sprawę, że mogą istnieć inne osoby (takie jak ty), które mogłyby korzystać z aplikacji referencyjnej, więc zrefaktorowałem ogólny materiał do frameworka aplikacji WPF / MVVM i wydałem go na licencji LGPL. Nazwałem go SoapBox Core . Jeśli przejdziesz do strony pobierania, zobaczysz, że jest dostarczana z małą aplikacją demonstracyjną, a kod źródłowy tej aplikacji demonstracyjnej jest również dostępny do pobrania. Mam nadzieję, że okaże się to pomocne. Jeśli chcesz uzyskać więcej informacji, napisz do mnie na adres scott {at} soapboxautomation.com.

EDYCJA : opublikował również artykuł w CodeProject wyjaśniający, jak to działa.

Scott Whitlock
źródło
2

Napisałem prosty przykład MVVM od podstaw na projekcie kodu tutaj jest link MVVM WPF krok po kroku . Zaczyna się od prostej architektury 3-warstwowej i umożliwia korzystanie z niektórych struktur, takich jak PRISM.

wprowadź opis obrazu tutaj

Shivprasad Koirala
źródło
1

Nawet ja podzielałem frustrację, dopóki nie wziąłem sprawy w swoje ręce. Zacząłem IncEditor.

IncEditor ( http://inceditor.codeplex.com ) to edytor, który próbuje wprowadzić programistów do WPF, MVVM i MEF. Uruchomiłem go i udało mi się uzyskać pewne funkcje, takie jak obsługa motywów. Nie jestem ekspertem od WPF, MVVM ani MEF, więc nie mogę włożyć w to wielu funkcji. Serdecznie proszę was, żebyście to ulepszyli, aby tacy wariaci jak ja mogli to lepiej zrozumieć.

Abdulsattar Mohammed
źródło