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 INotifyPropertyChanged
do 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 IDataErrorInfo
celu 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ą IDataErrorInfo
i 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ść, Item
któ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 DialogResult
wł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.