MVVM - Szybki przewodnik
Dobrze uporządkowanym i prawdopodobnie najbardziej wielokrotnym sposobem organizacji kodu jest użycie wzorca „MVVM”. PlikModel, View, ViewModel (MVVM pattern) polega na udzielaniu wskazówek dotyczących organizacji i struktury kodu w celu pisania aplikacji, które można konserwować, testować i rozszerzać.
Model - Po prostu przechowuje dane i nie ma nic wspólnego z żadną logiką biznesową.
ViewModel - Działa jako łącze / połączenie między modelem a widokiem i sprawia, że rzeczy wyglądają ładnie.
View - Po prostu przechowuje sformatowane dane i zasadniczo deleguje wszystko do Modelu.
Oddzielna prezentacja
Aby uniknąć problemów spowodowanych umieszczeniem logiki aplikacji w kodzie lub XAML, najlepiej jest użyć techniki znanej jako prezentacja oddzielna. Staramy się tego uniknąć, gdzie będziemy mieć kod XAML i kod z minimalnym wymaganiem do bezpośredniej pracy z obiektami interfejsu użytkownika. Klasy interfejsu użytkownika zawierają również kod dla złożonych zachowań interakcji, logikę aplikacji i wszystko inne, jak pokazano na poniższym rysunku po lewej stronie.
Dzięki oddzielnej prezentacji klasa interfejsu użytkownika jest znacznie prostsza. Ma oczywiście XAML, ale kod, który jest za nim, robi tak mało, jak jest to praktyczne.
Logika aplikacji należy do osobnej klasy, którą często nazywa się modelem.
Jednak to nie wszystko. Jeśli się tutaj zatrzymasz, prawdopodobnie powtórzysz bardzo częsty błąd, który doprowadzi Cię do szaleństwa wiązania danych.
Wielu programistów próbuje użyć powiązania danych, aby połączyć elementy w XAML bezpośrednio z właściwościami w modelu.
Czasami może to być w porządku, ale często nie. Problem polega na tym, że model jest całkowicie związany z tym, co robi aplikacja, a nie ze sposobem interakcji użytkownika z aplikacją.
Sposób, w jaki prezentujesz dane, często różni się nieco od ich wewnętrznej struktury.
Co więcej, większość interfejsów użytkownika ma stan, który nie należy do modelu aplikacji.
Na przykład, jeśli twój interfejs użytkownika używa przeciągania i upuszczania, coś musi śledzić rzeczy, takie jak miejsce, w którym jest przeciągany element, jak jego wygląd powinien się zmieniać, gdy przesuwa się nad potencjalnymi celami upuszczania, a także jak te cele upuszczania mogą również zmieniają się, gdy element jest nad nimi przeciągany.
Ten rodzaj stanu może stać się zaskakująco złożony i musi zostać dokładnie przetestowany.
W praktyce zazwyczaj chcesz, aby między interfejsem użytkownika a modelem znajdowały się inne zajęcia. Ma to dwie ważne role.
Po pierwsze, dostosowuje model aplikacji do określonego widoku interfejsu użytkownika.
Po drugie, jest to miejsce, w którym żyje każda nietrywialna logika interakcji, a przez to mam na myśli kod wymagany, aby Twój interfejs użytkownika zachowywał się tak, jak chcesz.
Wzorzec MVVM jest ostatecznie nowoczesną strukturą wzorca MVC, więc głównym celem jest nadal ten sam, aby zapewnić wyraźne oddzielenie logiki domeny od warstwy prezentacji. Oto niektóre zalety i wady wzorca MVVM.
Kluczową korzyścią jest umożliwienie prawdziwego oddzielenia Widoku i Modelu poza osiągnięciem separacji i wydajnością, jaką zyskujesz dzięki temu. W rzeczywistości oznacza to, że kiedy model wymaga zmiany, można go łatwo zmienić bez konieczności wyświetlania i odwrotnie.
Istnieją trzy ważne kluczowe rzeczy, które wynikają z zastosowania MVVM, które są następujące.
Konserwowalność
Czyste oddzielenie różnych rodzajów kodu powinno ułatwić przejście do jednej lub kilku bardziej szczegółowych i skoncentrowanych części i wprowadzanie zmian bez obaw.
Oznacza to, że możesz zachować elastyczność i szybko przechodzić na nowe wersje.
Testowalność
Dzięki MVVM każdy fragment kodu jest bardziej szczegółowy i jeśli jest prawidłowo zaimplementowany, Twoje zewnętrzne i wewnętrzne zależności są oddzielnymi fragmentami kodu z części z podstawową logiką, którą chciałbyś przetestować.
To znacznie ułatwia pisanie testów jednostkowych na podstawie logiki podstawowej.
Upewnij się, że działa poprawnie po napisaniu i działa nawet wtedy, gdy coś się zmieni podczas konserwacji.
Rozciągliwość
Czasami nakłada się to na łatwość utrzymania, ze względu na czyste granice separacji i bardziej szczegółowe fragmenty kodu.
Masz większe szanse na uczynienie którejkolwiek z tych części bardziej wielokrotnego użytku.
Ma również możliwość zastępowania lub dodawania nowych fragmentów kodu, które robią podobne rzeczy w odpowiednich miejscach w architekturze.
Oczywistym celem wzorca MVVM jest abstrakcja widoku, która zmniejsza ilość logiki biznesowej związanej z kodem. Jednak oto kilka innych solidnych zalet -
- ViewModel jest łatwiejszy do testowania jednostkowego niż kod związany z kodem lub kod sterowany zdarzeniami.
- Możesz to przetestować bez niezręcznej automatyzacji interfejsu użytkownika i interakcji.
- Warstwa prezentacji i logika są luźno powiązane.
Niedogodności
- Niektórzy uważają, że w przypadku prostych interfejsów użytkownika MVVM może być przesadą.
- Podobnie w większych przypadkach może być trudno zaprojektować ViewModel.
- Debugowanie byłoby nieco trudne, gdy mamy złożone powiązania danych.
Wzorzec MVVM składa się z trzech części - modelu, widoku i modelu widoku. Większość programistów na początku jest trochę zdezorientowanych co do tego, co Model, Widok i ViewModel powinny lub nie powinny zawierać i jakie są obowiązki każdej części.
W tym rozdziale poznamy obowiązki każdej części wzorca MVVM, abyś mógł jasno zrozumieć, jaki rodzaj kodu trafia do miejsca. MVVM jest w rzeczywistości warstwową architekturą po stronie klienta, jak pokazano na poniższym rysunku.
Warstwa prezentacji składa się z widoków.
Warstwa logiczna to modele widoków.
Warstwa prezentacji to połączenie obiektów modelu.
Usługi klienckie, które je generują i utrwalają, kierowały dostęp w aplikacji dwuwarstwowej lub za pośrednictwem wywołań usług w aplikacji, a następnie do niej.
Usługi klienta nie są oficjalnie częścią wzorca MVVM, ale często są używane z MVVM w celu uzyskania dalszych separacji i uniknięcia powielania kodu.
Obowiązki modelu
Ogólnie rzecz biorąc, model jest najłatwiejszy do zrozumienia. Jest to model danych po stronie klienta, który obsługuje widoki w aplikacji.
Składa się z obiektów z właściwościami i niektórymi zmiennymi zawierającymi dane w pamięci.
Niektóre z tych właściwości mogą odnosić się do innych obiektów modelu i tworzyć wykres obiektów, który jako całość jest obiektami modelu.
Obiekty modelu powinny zgłaszać powiadomienia o zmianie właściwości, które w WPF oznaczają powiązanie danych.
Ostatnim obowiązkiem jest walidacja, która jest opcjonalna, ale można osadzić informacje walidacyjne na obiektach modelu przy użyciu funkcji walidacji powiązań danych WPF za pośrednictwem interfejsów, takich jak INotifyDataErrorInfo / IDataErrorInfo
Wyświetl obowiązki
Głównym celem i obowiązkami widoków jest zdefiniowanie struktury tego, co użytkownik widzi na ekranie. Struktura może zawierać części statyczne i dynamiczne.
Części statyczne to hierarchia XAML, która definiuje kontrolki i układ kontrolek, z których składa się widok.
Część dynamiczna przypomina animacje lub zmiany stanu, które są definiowane jako część Widoku.
Głównym celem MVVM jest to, że w widoku nie powinno być żadnego kodu.
Niemożliwe jest, aby w polu widzenia nie było kodu. W związku z tym potrzebujesz przynajmniej konstruktora i wywołania do zainicjowania komponentu.
Pomysł polega na tym, że kod logiki obsługi zdarzeń, akcji i manipulacji danymi nie powinien znajdować się w kodzie w widoku.
Istnieją również inne rodzaje kodu, które muszą znajdować się w kodzie za każdym kodem, który jest wymagany, aby mieć odwołanie do elementu interfejsu użytkownika, który jest nieodłącznie wyświetlany.
Obowiązki ViewModel
ViewModel jest głównym punktem aplikacji MVVM. Podstawowym obowiązkiem ViewModel jest dostarczanie danych do widoku, aby widok mógł umieścić te dane na ekranie.
Pozwala również użytkownikowi na interakcję z danymi i zmianę danych.
Innym kluczowym obowiązkiem ViewModel jest hermetyzacja logiki interakcji dla widoku, ale nie oznacza to, że cała logika aplikacji powinna przejść do ViewModel.
Powinien być w stanie obsłużyć odpowiednią sekwencję wywołań, aby wykonać właściwą rzecz w zależności od użytkownika lub jakichkolwiek zmian w widoku.
ViewModel powinien również zarządzać dowolną logiką nawigacji, na przykład decydowaniem, kiedy nadszedł czas, aby przejść do innego widoku.
W tym rozdziale nauczymy się, jak używać wzorców MVVM do prostego ekranu wprowadzania i aplikacji WPF, do której być może byłeś już przyzwyczajony.
Spójrzmy na prosty przykład, w którym będziemy używać podejścia MVVM.
Step 1 - Utwórz nowy projekt aplikacji WPF MVVMDemo.
Step 2 - Dodaj trzy foldery (Model, ViewModel i Views) do projektu.
Step 3 - Dodaj klasę StudentModel w folderze Model i wklej poniższy kod w tej klasie
using System.ComponentModel;
namespace MVVMDemo.Model {
public class StudentModel {}
public class Student : INotifyPropertyChanged {
private string firstName;
private string lastName;
public string FirstName {
get {
return firstName;
}
set {
if (firstName != value) {
firstName = value;
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
}
public string LastName {
get {return lastName; }
set {
if (lastName != value) {
lastName = value;
RaisePropertyChanged("LastName");
RaisePropertyChanged("FullName");
}
}
}
public string FullName {
get {
return firstName + " " + lastName;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
Step 4 - Dodaj kolejną klasę StudentViewModel do folderu ViewModel i wklej następujący kod.
using MVVMDemo.Model;
using System.Collections.ObjectModel;
namespace MVVMDemo.ViewModel {
public class StudentViewModel {
public ObservableCollection<Student> Students {
get;
set;
}
public void LoadStudents() {
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { FirstName = "Mark", LastName = "Allain" });
students.Add(new Student { FirstName = "Allen", LastName = "Brown" });
students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
Students = students;
}
}
}
Step 5 - Dodaj nową kontrolę użytkownika (WPF), klikając prawym przyciskiem myszy folder Widoki i wybierz Dodaj> Nowy element…
Step 6- Kliknij przycisk Dodaj. Teraz zobaczysz plik XAML. Dodaj następujący kod do pliku StudentView.xaml, który zawiera różne elementy interfejsu użytkownika.
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
Step 7 - Teraz dodaj StudentView do pliku MainPage.xaml, używając następującego kodu.
<Window x:Class = "MVVMDemo.MainWindow"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local = "clr-namespace:MVVMDemo"
xmlns:views = "clr-namespace:MVVMDemo.Views"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Grid>
<views:StudentView x:Name = "StudentViewControl" Loaded = "StudentViewControl_Loaded"/>
</Grid>
</Window>
Step 8 - Oto implementacja zdarzenia Loaded w pliku MainPage.xaml.cs, która zaktualizuje widok z ViewModel.
using System.Windows;
namespace MVVMDemo {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
private void StudentViewControl_Loaded(object sender, RoutedEventArgs e) {
MVVMDemo.ViewModel.StudentViewModel studentViewModelObject =
new MVVMDemo.ViewModel.StudentViewModel();
studentViewModelObject.LoadStudents();
StudentViewControl.DataContext = studentViewModelObject;
}
}
}
Step 9 - Kiedy powyższy kod zostanie skompilowany i wykonany, w oknie głównym pojawi się następujący komunikat.
Zalecamy wykonanie powyższego przykładu krok po kroku w celu lepszego zrozumienia.
W tym rozdziale omówimy różne sposoby podłączania widoków do ViewModel. Najpierw spójrzmy na pierwszą konstrukcję View, w której możemy zadeklarować ją w XAML. Jak widzieliśmy na przykładzie w poprzednim rozdziale, w którym podłączyliśmy widok z okna głównego. Teraz zobaczymy inne sposoby łączenia widoków.
W tym rozdziale będziemy używać tego samego przykładu. Poniżej znajduje się ta sama implementacja klasy Model.
using System.ComponentModel;
namespace MVVMDemo.Model {
public class StudentModel {}
public class Student : INotifyPropertyChanged {
private string firstName;
private string lastName;
public string FirstName {
get { return firstName; }
set {
if (firstName != value) {
firstName = value;
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
}
public string LastName {
get { return lastName; }
set {
if (lastName != value) {
lastName = value;
RaisePropertyChanged("LastName");
RaisePropertyChanged("FullName");
}
}
}
public string FullName {
get {
return firstName + " " + lastName;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
Oto implementacja klasy ViewModel. Tym razem metoda LoadStudents jest wywoływana w domyślnym konstruktorze.
using MVVMDemo.Model;
using System.Collections.ObjectModel;
namespace MVVMDemo.ViewModel{
public class StudentViewModel {
public StudentViewModel() {
LoadStudents();
}
public ObservableCollection<Student> Students {
get;
set;
}
public void LoadStudents() {
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { FirstName = "Mark", LastName = "Allain" });
students.Add(new Student { FirstName = "Allen", LastName = "Brown" });
students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
Students = students;
}
}
}
Niezależnie od tego, czy widok jest oknem, kontrolą użytkownika czy stroną, parser generalnie działa od góry do dołu i od lewej do prawej. Wywołuje domyślny konstruktor dla każdego napotkanego elementu. Istnieją dwa sposoby tworzenia widoku. Możesz użyć na nich dowolnego.
- Wyświetl pierwszą konstrukcję w języku XAML
- Wyświetl pierwszą konstrukcję związaną z kodem
Wyświetl pierwszą konstrukcję w języku XAML
Jednym ze sposobów jest po prostu dodanie ViewModel jako elementu zagnieżdżonego w metodzie ustawiającej dla właściwości DataContext, jak pokazano w poniższym kodzie.
<UserControl.DataContext>
<viewModel:StudentViewModel/>
</UserControl.DataContext>
Oto pełny plik View XAML.
<UserControl x:Class="MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<UserControl.DataContext>
<viewModel:StudentViewModel/>
</UserControl.DataContext>
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
Wyświetl pierwszą konstrukcję związaną z kodem
Innym sposobem jest to, że możesz uzyskać pierwszą konstrukcję View, po prostu konstruując model widoku samodzielnie w kodzie znajdującym się za Twoim View, ustawiając właściwość DataContext w tym wystąpieniu.
Zazwyczaj właściwość DataContext jest ustawiana w metodzie konstruktora widoku, ale można również odroczyć konstrukcję do momentu uruchomienia zdarzenia Load widoku.
using System.Windows.Controls;
namespace MVVMDemo.Views {
/// <summary>
/// Interaction logic for StudentView.xaml
/// </summary>
public partial class StudentView : UserControl {
public StudentView() {
InitializeComponent();
this.DataContext = new MVVMDemo.ViewModel.StudentViewModel();
}
}
}
Jednym z powodów konstruowania modelu widoku w kodzie zamiast XAML jest to, że konstruktor modelu widoku przyjmuje parametry, ale analiza XAML może konstruować elementy tylko wtedy, gdy są zdefiniowane w domyślnym konstruktorze.
Teraz w tym przypadku plik XAML widoku będzie wyglądał tak, jak pokazano w poniższym kodzie.
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
mc:Ignorable = "d"
d:DesignHeight = "300"
d:DesignWidth = "300">
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal"<
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
Możesz zadeklarować ten widok w MainWindow, jak pokazano w pliku MainWindow.XAML.
<Window x:Class = "MVVMDemo.MainWindow"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local = "clr-namespace:MVVMDemo"
xmlns:views = "clr-namespace:MVVMDemo.Views"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Grid>
<views:StudentView x:Name = "StudentViewControl"/>
</Grid>
</Window>
Gdy powyższy kod zostanie skompilowany i wykonany, w oknie głównym zobaczysz następujące dane wyjściowe.
Zalecamy wykonanie powyższego przykładu krok po kroku w celu lepszego zrozumienia.
W tym rozdziale omówimy, jak podłączyć ViewModel. Jest to kontynuacja ostatniego rozdziału, w którym omówiliśmy pierwszą konstrukcję View. Teraz następną formą pierwszej konstrukcji jestmeta-pattern który jest znany jako ViewModelLocator. Jest to pseudo wzór i jest nałożony na wzór MVVM.
W MVVM każdy widok musi być podłączony do swojego ViewModel.
ViewModelLocator to proste podejście do scentralizowania kodu i oddzielenia widoku więcej.
Oznacza to, że nie musi jawnie wiedzieć o typie ViewModel i jak go zbudować.
Istnieje wiele różnych podejść do korzystania z ViewModelLocator, ale tutaj używamy najbardziej podobnego do tego, który jest częścią frameworka PRISM.
ViewModelLocator zapewnia standardowy, spójny, deklaratywny i luźno powiązany sposób wykonywania pierwszej konstrukcji widoku, który automatyzuje proces podłączania ViewModel do widoku. Poniższy rysunek przedstawia proces wysokiego poziomu ViewModelLocator.
Step 1 - Dowiedz się, który typ widoku jest konstruowany.
Step 2 - Zidentyfikuj ViewModel dla tego konkretnego typu widoku.
Step 3 - Skonstruuj ten ViewModel.
Step 4 - Ustaw widoki DataContext na ViewModel.
Aby zrozumieć podstawową koncepcję, spójrzmy na prosty przykład ViewModelLocator, kontynuując ten sam przykład z poprzedniego rozdziału. Jeśli spojrzysz na plik StudentView.xaml, zobaczysz, że statycznie podłączyliśmy ViewModel.
Teraz, jak pokazano w poniższym programie, skomentuj ten kod XAML również usuń kod z kodu związanego z kodem.
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
<!--<UserControl.DataContext>
<viewModel:StudentViewModel/>
</UserControl.DataContext>-->
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
Teraz utwórzmy nowy folder VML i dodajmy nową publiczną klasę ViewModelLocator, która będzie zawierać pojedynczą dołączoną właściwość (właściwość zależności) AutoHookedUpViewModel, jak pokazano w poniższym kodzie.
public static bool GetAutoHookedUpViewModel(DependencyObject obj) {
return (bool)obj.GetValue(AutoHookedUpViewModelProperty);
}
public static void SetAutoHookedUpViewModel(DependencyObject obj, bool value) {
obj.SetValue(AutoHookedUpViewModelProperty, value);
}
// Using a DependencyProperty as the backing store for AutoHookedUpViewModel.
//This enables animation, styling, binding, etc...
public static readonly DependencyProperty AutoHookedUpViewModelProperty =
DependencyProperty.RegisterAttached("AutoHookedUpViewModel",
typeof(bool), typeof(ViewModelLocator), new PropertyMetadata(false,
AutoHookedUpViewModelChanged));
Teraz możesz zobaczyć podstawową definicję właściwości dołączania. Aby dodać zachowanie do właściwości, musimy dodać zmienioną procedurę obsługi zdarzeń dla tej właściwości, która zawiera automatyczny proces podłączania ViewModel for View. Kod służący do tego jest następujący -
private static void AutoHookedUpViewModelChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e) {
if (DesignerProperties.GetIsInDesignMode(d)) return;
var viewType = d.GetType();
string str = viewType.FullName;
str = str.Replace(".Views.", ".ViewModel.");
var viewTypeName = str;
var viewModelTypeName = viewTypeName + "Model";
var viewModelType = Type.GetType(viewModelTypeName);
var viewModel = Activator.CreateInstance(viewModelType);
((FrameworkElement)d).DataContext = viewModel;
}
Poniżej znajduje się pełna implementacja klasy ViewModelLocator.
using System;
using System.ComponentModel;
using System.Windows;
namespace MVVMDemo.VML {
public static class ViewModelLocator {
public static bool GetAutoHookedUpViewModel(DependencyObject obj) {
return (bool)obj.GetValue(AutoHookedUpViewModelProperty);
}
public static void SetAutoHookedUpViewModel(DependencyObject obj, bool value) {
obj.SetValue(AutoHookedUpViewModelProperty, value);
}
// Using a DependencyProperty as the backing store for AutoHookedUpViewModel.
//This enables animation, styling, binding, etc...
public static readonly DependencyProperty AutoHookedUpViewModelProperty =
DependencyProperty.RegisterAttached("AutoHookedUpViewModel",
typeof(bool), typeof(ViewModelLocator), new
PropertyMetadata(false, AutoHookedUpViewModelChanged));
private static void AutoHookedUpViewModelChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e) {
if (DesignerProperties.GetIsInDesignMode(d)) return;
var viewType = d.GetType();
string str = viewType.FullName;
str = str.Replace(".Views.", ".ViewModel.");
var viewTypeName = str;
var viewModelTypeName = viewTypeName + "Model";
var viewModelType = Type.GetType(viewModelTypeName);
var viewModel = Activator.CreateInstance(viewModelType);
((FrameworkElement)d).DataContext = viewModel;
}
}
}
Pierwszą rzeczą do zrobienia jest dodanie przestrzeni nazw, abyśmy mogli dostać się do tego typu ViewModelLocator w katalogu głównym naszego projektu. Następnie w elemencie trasy, który jest typem widoku, dodaj właściwość AutoHookedUpViewModel i ustaw ją na true.
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
Oto pełna implementacja pliku StudentView.xaml.
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
<!--<UserControl.DataContext>
<viewModel:StudentViewModel/>
</UserControl.DataContext>-->
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
Gdy powyższy kod zostanie skompilowany i wykonany, zobaczysz, że ViewModelLocator podłącza ViewModel dla tego konkretnego widoku.
Kluczową rzeczą, na którą należy zwrócić uwagę, jest to, że widok nie jest już powiązany w sposób z typem jego ViewModel lub w jaki sposób jest konstruowany. To wszystko zostało przeniesione do centralnej lokalizacji wewnątrz ViewModelLocator.
W tym rozdziale dowiemy się, jak powiązanie danych obsługuje wzorzec MVVM. Powiązanie danych jest kluczową funkcją, która odróżnia MVVM od innych wzorców separacji interfejsu użytkownika, takich jak MVC i MVP.
W przypadku powiązania danych musisz mieć skonstruowany widok lub zestaw elementów interfejsu użytkownika, a następnie potrzebujesz innego obiektu, na który będą wskazywać powiązania.
Elementy interfejsu użytkownika w widoku są powiązane z właściwościami, które są uwidaczniane przez ViewModel.
Kolejność, w jakiej są konstruowane View i ViewModel, zależy od sytuacji, ponieważ najpierw omówiliśmy widok.
View i ViewModel są konstruowane, a DataContext of the View jest ustawiana na ViewModel.
Powiązania mogą być powiązaniami danych OneWay lub TwoWay do przesyłania danych między View i ViewModel.
Przyjrzyjmy się powiązaniom danych w tym samym przykładzie. Poniżej znajduje się kod XAML StudentView.
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
<!--<UserControl.DataContext>
<viewModel:StudentViewModel/>
</UserControl.DataContext>-->
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
Jeśli spojrzysz na powyższy kod XAML, zobaczysz, że ItemsControl jest powiązany z kolekcją Students uwidocznioną przez ViewModel.
Możesz również zobaczyć, że właściwość modelu ucznia ma również swoje własne indywidualne powiązania, które są powiązane z Textboxami i TextBlock.
ItemSource of ItemsControl można powiązać z właściwością Students, ponieważ ogólny DataContext dla widoku jest ustawiony na ViewModel.
Poszczególne powiązania właściwości w tym miejscu są również powiązaniami DataContext, ale nie są one powiązane z samym ViewModel ze względu na sposób działania elementu ItemSource.
Gdy źródło elementu wiąże się ze swoją kolekcją, renderuje kontener dla każdego elementu podczas renderowania i ustawia DataContext tego kontenera na element. Zatem ogólny DataContext dla każdego pola tekstowego i bloku tekstowego w wierszu będzie stanowić indywidualnego Studenta w kolekcji. Możesz również zobaczyć, że te powiązania dla TextBox to powiązania danych TwoWay, a dla TextBlock jest to powiązanie danych OneWay, ponieważ nie możesz edytować TextBlock.
Po ponownym uruchomieniu tej aplikacji zobaczysz następujące dane wyjściowe.
Zmieńmy teraz tekst w drugim polu tekstowym pierwszego wiersza z Allain na Upston i naciśnij klawisz Tab, aby stracić fokus. Zobaczysz, że tekst TextBlock jest również aktualizowany.
Dzieje się tak, ponieważ powiązania TextBox są ustawione na TwoWay i aktualizuje również Model, a następnie z modelu ponownie aktualizowany jest TextBlock.
Szablon opisuje ogólny wygląd i wygląd kontrolki. Z każdą kontrolką powiązany jest domyślny szablon, który nadaje wygląd tej kontrolce. W aplikacji WPF można łatwo tworzyć własne szablony, gdy chcesz dostosować zachowanie wizualne i wygląd kontrolki. Łączność między logiką a szablonem można osiągnąć za pomocą powiązania danych.
W MVVM istnieje inna podstawowa forma, która jest znana jako pierwsza konstrukcja ViewModel.
Pierwsze podejście do konstrukcji ViewModel wykorzystuje możliwości niejawnych szablonów danych w WPF.
Niejawne szablony danych mogą automatycznie wybierać odpowiedni szablon z bieżącego słownika zasobów dla elementu, który używa powiązania danych. Robią to na podstawie typu obiektu danych, który jest renderowany przez powiązanie danych. Po pierwsze, musisz mieć jakiś element, który jest powiązany z obiektem danych.
Przyjrzyjmy się ponownie naszemu prostemu przykładowi, w którym zrozumiesz, jak możesz najpierw wyświetlić model, wykorzystując szablony danych, w szczególności niejawne szablony danych. Oto implementacja naszej klasy StudentViewModel.
using MVVMDemo.Model;
using System.Collections.ObjectModel;
namespace MVVMDemo.ViewModel {
public class StudentViewModel {
public StudentViewModel() {
LoadStudents();
}
public ObservableCollection<Student> Students {
get;
set;
}
public void LoadStudents() {
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { FirstName = "Mark", LastName = "Allain" });
students.Add(new Student { FirstName = "Allen", LastName = "Brown" });
students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
Students = students;
}
}
}
Widać, że powyższy ViewModel pozostaje niezmieniony. Będziemy kontynuować ten sam przykład z poprzedniego rozdziału. Ta klasa ViewModel po prostu uwidacznia właściwość kolekcji Students i wypełnia ją podczas budowy. Przejdźmy do pliku StudentView.xaml, usuń istniejącą implementację i zdefiniujmy szablon danych w sekcji Zasoby.
<UserControl.Resources>
<DataTemplate x:Key = "studentsTemplate">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
Teraz Dodaj pole listy i powiązanie danych tego pola listy z właściwością Students, jak pokazano w poniższym kodzie.
<ListBox ItemsSource = "{Binding Students}" ItemTemplate = "{StaticResource studentsTemplate}"/>
W sekcji Resource DataTemplate ma klucz StudentsTemplate, a następnie, aby faktycznie użyć tego szablonu, musimy użyć właściwości ItemTemplate elementu ListBox. Teraz możesz zobaczyć, że poleciliśmy polu listy, aby użyło tego konkretnego szablonu do renderowania tych Studentów. Poniżej znajduje się pełna implementacja pliku StudentView.xaml.
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
<UserControl.Resources>
<DataTemplate x:Key = "studentsTemplate">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ListBox
ItemsSource = "{Binding Students}"
ItemTemplate = "{StaticResource studentsTemplate}"/>
</Grid>
</UserControl>
Kiedy powyższy kod zostanie skompilowany i wykonany, zobaczysz następujące okno, które zawiera jeden ListBox. Każdy ListBoxItem zawiera dane obiektu klasy Student, które są wyświetlane w polach TextBlock i Text.
Aby uczynić ten szablon niejawnym, musimy usunąć właściwość ItemTemplate z pola listy i dodać właściwość DataType w naszej definicji szablonu, jak pokazano w poniższym kodzie.
<UserControl.Resources>
<DataTemplate DataType = "{x:Type data:Student}">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource = "{Binding Students}"/>
</Grid>
W DataTemplate rozszerzenie znaczników x: Type jest bardzo ważne, podobnie jak typ operatora w języku XAML. Zasadniczo musimy więc wskazać typ danych Student, który znajduje się w przestrzeni nazw MVVMDemo.Model. Poniżej znajduje się zaktualizowany kompletny plik XAML.
<UserControl x:Class="MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:data = "clr-namespace:MVVMDemo.Model"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
<UserControl.Resources>
<DataTemplate DataType = "{x:Type data:Student}">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource = "{Binding Students}"/>
</Grid>
</UserControl>
Po ponownym uruchomieniu tej aplikacji nadal uzyskasz to samo renderowanie Studentów z szablonem danych, ponieważ automatycznie mapuje typ renderowanego obiektu, lokalizując odpowiedni DataTemplate.
Zalecamy wykonanie powyższego przykładu metodą krok po kroku w celu lepszego zrozumienia.
W tym rozdziale dowiemy się, jak dodać interaktywność do aplikacji MVVM i jak czysto wywoływać logikę. Zobaczysz również, że wszystko to odbywa się poprzez zachowanie luźnego powiązania i dobrej strukturyzacji, która jest sercem wzorca MVVM. Aby to wszystko zrozumieć, najpierw poznajmy polecenia.
Komunikacja View / ViewModel za pomocą poleceń
Wzorzec poleceń został dobrze udokumentowany i od kilku dekad często wykorzystuje wzorzec projektowy. W tym wzorze występują dwaj główni aktorzy, wywołujący i odbierający.
Invoker
Wywołujący jest fragmentem kodu, który może wykonać jakąś imperatywną logikę.
Zwykle jest to element interfejsu użytkownika, z którym użytkownik wchodzi w interakcje w kontekście struktury interfejsu użytkownika.
Może to być po prostu kolejny fragment kodu logicznego w innym miejscu aplikacji.
Odbiorca
Odbiornik jest logiką, która jest przeznaczona do wykonania, gdy wywołujący odpala.
W kontekście MVVM odbiornikiem jest zwykle metoda w Twoim ViewModel, którą należy wywołać.
Pomiędzy tymi dwoma masz warstwę przeszkód, co oznacza, że wywołujący i odbierający nie muszą o sobie jawnie wiedzieć. Zwykle jest to reprezentowane jako abstrakcja interfejsu ujawniona wywołującemu, a konkretna implementacja tego interfejsu jest w stanie wywołać odbiorcę.
Rzućmy okiem na prosty przykład, w którym nauczysz się poleceń i jak ich używać do komunikowania się między widokiem a ViewModel. W tym rozdziale będziemy kontynuować ten sam przykład z poprzedniego rozdziału.
W pliku StudentView.xaml mamy ListBox, który łączy dane uczniów z ViewModel. Teraz dodajmy przycisk do usuwania ucznia z ListBox.
Ważną rzeczą jest to, że praca z poleceniami na przycisku jest bardzo łatwa, ponieważ mają one właściwość command do podłączenia do ICommand.
Możemy więc uwidocznić właściwość w naszym ViewModel, która ma ICommand i jest z nią powiązana z właściwości polecenia przycisku, jak pokazano w poniższym kodzie.
<Button Content = "Delete"
Command = "{Binding DeleteCommand}"
HorizontalAlignment = "Left"
VerticalAlignment = "Top"
Width = "75" />
Dodajmy do projektu nową klasę, która zaimplementuje interfejs ICommand. Poniżej przedstawiono implementację interfejsu ICommand.
using System;
using System.Windows.Input;
namespace MVVMDemo {
public class MyICommand : ICommand {
Action _TargetExecuteMethod;
Func<bool> _TargetCanExecuteMethod;
public MyICommand(Action executeMethod) {
_TargetExecuteMethod = executeMethod;
}
public MyICommand(Action executeMethod, Func<bool> canExecuteMethod){
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}
public void RaiseCanExecuteChanged() {
CanExecuteChanged(this, EventArgs.Empty);
}
bool ICommand.CanExecute(object parameter) {
if (_TargetCanExecuteMethod != null) {
return _TargetCanExecuteMethod();
}
if (_TargetExecuteMethod != null) {
return true;
}
return false;
}
// Beware - should use weak references if command instance lifetime
is longer than lifetime of UI objects that get hooked up to command
// Prism commands solve this in their implementation
public event EventHandler CanExecuteChanged = delegate { };
void ICommand.Execute(object parameter) {
if (_TargetExecuteMethod != null) {
_TargetExecuteMethod();
}
}
}
}
Jak widać, jest to prosta implementacja delegowania ICommand, w której mamy dwóch delegatów, jednego dla executeMethod, a drugiego dla canExecuteMethod, które można przekazać podczas konstrukcji.
W powyższej implementacji istnieją dwa przeciążone konstruktory, jeden tylko dla executeMethod i jeden dla obu executeMethod i I canExecuteMethod.
Dodajmy właściwość typu MyICommand w klasie StudentView Model. Teraz musimy skonstruować wystąpienie w StudentViewModel. Użyjemy przeciążonego konstruktora MyICommand, który przyjmuje dwa parametry.
public MyICommand DeleteCommand { get; set;}
public StudentViewModel() {
LoadStudents();
DeleteCommand = new MyICommand(OnDelete, CanDelete);
}
Teraz dodaj implementację metod OnDelete i CanDelete.
private void OnDelete() {
Students.Remove(SelectedStudent);
}
private bool CanDelete() {
return SelectedStudent != null;
}
Musimy również dodać nowego SelectedStudent, aby użytkownik mógł usunąć wybrany element z ListBox.
private Student _selectedStudent;
public Student SelectedStudent {
get {
return _selectedStudent;
}
set {
_selectedStudent = value;
DeleteCommand.RaiseCanExecuteChanged();
}
}
Poniżej znajduje się pełna implementacja klasy ViewModel.
using MVVMDemo.Model;
using System.Collections.ObjectModel;
using System.Windows.Input;
using System;
namespace MVVMDemo.ViewModel {
public class StudentViewModel {
public MyICommand DeleteCommand { get; set;}
public StudentViewModel() {
LoadStudents();
DeleteCommand = new MyICommand(OnDelete, CanDelete);
}
public ObservableCollection<Student> Students {
get;
set;
}
public void LoadStudents() {
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { FirstName = "Mark", LastName = "Allain" });
students.Add(new Student { FirstName = "Allen", LastName = "Brown" });
students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
Students = students;
}
private Student _selectedStudent;
public Student SelectedStudent {
get {
return _selectedStudent;
}
set {
_selectedStudent = value;
DeleteCommand.RaiseCanExecuteChanged();
}
}
private void OnDelete() {
Students.Remove(SelectedStudent);
}
private bool CanDelete() {
return SelectedStudent != null;
}
}
}
W StudentView.xaml musimy dodać właściwość SelectedItem w ListBox, która zostanie powiązana z właściwością SelectStudent.
<ListBox ItemsSource = "{Binding Students}" SelectedItem = "{Binding SelectedStudent}"/>
Poniżej znajduje się pełny plik XAML.
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:data = "clr-namespace:MVVMDemo.Model"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<UserControl.Resources>
<DataTemplate DataType = "{x:Type data:Student}">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<StackPanel Orientation = "Horizontal">
<ListBox ItemsSource = "{Binding Students}"
SelectedItem = "{Binding SelectedStudent}"/>
<Button Content = "Delete"
Command = "{Binding DeleteCommand}"
HorizontalAlignment = "Left"
VerticalAlignment = "Top"
Width = "75" />
</StackPanel>
</Grid>
</UserControl>
Kiedy powyższy kod zostanie skompilowany i wykonany, zobaczysz następujące okno.
Widać, że przycisk usuwania jest wyłączony. Zostanie włączony po wybraniu dowolnego elementu.
Po wybraniu dowolnego elementu i naciśnięciu klawisza usuń. Zobaczysz, że wybrana lista pozycji została usunięta, a przycisk usuwania zostanie ponownie wyłączony.
Zalecamy wykonanie powyższego przykładu krok po kroku w celu lepszego zrozumienia.
Podczas tworzenia aplikacji MVVM zazwyczaj rozkładasz złożone ekrany informacji na zestaw widoków nadrzędnych i podrzędnych, w których widoki podrzędne są zawarte w widokach nadrzędnych w panelach lub kontrolkach kontenera i same tworzą hierarchię użycia.
Po zdekomponowaniu złożonych widoków nie oznacza to, że każdy element zawartości podrzędnej, który jest oddzielany do własnego pliku XAML, musi być widokiem MVVM.
Fragment zawartości po prostu zapewnia strukturę do renderowania czegoś na ekranie i nie obsługuje żadnych danych wejściowych ani manipulacji przez użytkownika dla tej zawartości.
Może nie potrzebować oddzielnego ViewModel, ale może to być po prostu fragment XAML, który jest renderowany na podstawie właściwości uwidocznionych przez ViewModel nadrzędnych.
Na koniec, jeśli masz hierarchię widoków i ViewModels, nadrzędny ViewModel może stać się centrum komunikacji, tak aby każdy podrzędny ViewModel mógł pozostać oddzielony od innych podrzędnych modeli ViewModel i ich nadrzędnych w jak największym stopniu.
Spójrzmy na przykład, w którym zdefiniujemy prostą hierarchię między różnymi widokami. Utwórz nowy projekt aplikacji WPFMVVMHierarchiesDemo
Step 1 - Dodaj trzy foldery (Model, ViewModel i Views) do projektu.
Step 2 - Dodaj klasy Customer i Order w folderze Model, CustomerListView i OrderView w folderze Views oraz CustomerListViewModel i OrderViewModel w folderze ViewModel, jak pokazano na poniższej ilustracji.
Step 3- Dodaj bloki tekstowe w CustomerListView i OrderView. Oto plik CustomerListView.xaml.
<UserControl x:Class="MVVMHierarchiesDemo.Views.CustomerListView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<TextBlock Text = "Customer List View"/>
</Grid>
</UserControl>
Poniżej znajduje się plik OrderView.xaml.
<UserControl x:Class = "MVVMHierarchiesDemo.Views.OrderView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc ="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d ="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views" mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<TextBlock Text = "Order View"/>
</Grid>
</UserControl>
Teraz potrzebujemy czegoś do hostowania tych widoków i dobrego miejsca na to w naszym MainWindow, ponieważ jest to prosta aplikacja. Potrzebujemy kontrolki kontenera, abyśmy mogli umieszczać nasze widoki i przełączać je w sposób nawigacyjny. W tym celu musimy dodać ContentControl w naszym pliku MainWindow.xaml i będziemy używać jego właściwości content i powiązać ją z referencją ViewModel.
Teraz zdefiniuj szablony danych dla każdego widoku w słowniku zasobów. Poniżej znajduje się plik MainWindow.xaml. Zwróć uwagę, jak każdy szablon danych mapuje typ danych (typ ViewModel) do odpowiedniego widoku.
<Window x:Class = "MVVMHierarchiesDemo.MainWindow"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local = "clr-namespace:MVVMHierarchiesDemo"
xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views"
xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
<views:CustomerListView/>
</DataTemplate>
<DataTemplate DataType = "{x:Type viewModels:OrderViewModel}">
<views:OrderView/>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content = "{Binding CurrentView}"/>
</Grid>
</Window>
Za każdym razem, gdy bieżący model widoku jest ustawiony na wystąpienie CustomerListViewModel, wyrenderuje CustomerListView z podłączonym ViewModel. Jest to ViewModel zamówienia, wyrenderuje OrderView i tak dalej.
Teraz potrzebujemy ViewModel, który ma właściwość CurrentViewModel oraz pewną logikę i polecenia, aby móc przełączać bieżące odwołanie do ViewModel wewnątrz właściwości.
Utwórzmy ViewModel dla tego MainWindow o nazwie MainWindowViewModel. Możemy po prostu utworzyć wystąpienie naszego ViewModel z XAML i użyć go do ustawienia właściwości DataContext okna. W tym celu musimy utworzyć klasę bazową, aby hermetyzować implementację INotifyPropertyChanged dla naszych ViewModels.
Główną ideą tej klasy jest hermetyzacja implementacji INotifyPropertyChanged i udostępnienie metod pomocniczych do klasy pochodnej, tak aby mogły one łatwo wyzwalać odpowiednie powiadomienia. Poniżej przedstawiono implementację klasy BindableBase.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo {
class BindableBase : INotifyPropertyChanged {
protected virtual void SetProperty<T>(ref T member, T val,
[CallerMemberName] string propertyName = null) {
if (object.Equals(member, val)) return;
member = val;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
}
Teraz nadszedł czas, aby faktycznie rozpocząć przełączanie widoków za pomocą naszej właściwości CurrentViewModel. Potrzebujemy tylko sposobu, aby sterować ustawieniem tej właściwości. I zrobimy to tak, aby użytkownik końcowy mógł wydać polecenie przejścia do listy klientów lub widoku zamówienia. Najpierw dodaj nową klasę do projektu, która zaimplementuje interfejs ICommand. Poniżej przedstawiono implementację interfejsu ICommand.
using System;
using System.Windows.Input;
namespace MVVMHierarchiesDemo {
public class MyICommand<T> : ICommand {
Action<T> _TargetExecuteMethod;
Func<T, bool> _TargetCanExecuteMethod;
public MyICommand(Action<T> executeMethod) {
_TargetExecuteMethod = executeMethod;
}
public MyICommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod) {
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}
public void RaiseCanExecuteChanged() {
CanExecuteChanged(this, EventArgs.Empty);
}
#region ICommand Members
bool ICommand.CanExecute(object parameter) {
if (_TargetCanExecuteMethod != null) {
T tparm = (T)parameter;
return _TargetCanExecuteMethod(tparm);
}
if (_TargetExecuteMethod != null) {
return true;
}
return false;
}
// Beware - should use weak references if command instance lifetime is
longer than lifetime of UI objects that get hooked up to command
// Prism commands solve this in their implementation
public event EventHandler CanExecuteChanged = delegate { };
void ICommand.Execute(object parameter) {
if (_TargetExecuteMethod != null) {
_TargetExecuteMethod((T)parameter);
}
}
#endregion
}
}
Musimy teraz skonfigurować nawigację najwyższego poziomu do tych modeli ViewModels, a logika dla tego przełączania powinna znajdować się w MainWindowViewModel. W tym celu użyjemy metody wywoływanej on navigate, która przyjmuje miejsce docelowe w postaci ciągu znaków i zwraca właściwość CurrentViewModel.
private void OnNav(string destination) {
switch (destination) {
case "orders":
CurrentViewModel = orderViewModelModel;
break;
case "customers":
default:
CurrentViewModel = custListViewModel;
break;
}
}
Aby poruszać się po tych różnych widokach, musimy dodać dwa przyciski w naszym pliku MainWindow.xaml. Poniżej znajduje się pełna implementacja pliku XAML.
<Window x:Class = "MVVMHierarchiesDemo.MainWindow"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local = "clr-namespace:MVVMHierarchiesDemo"
xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views"
xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
<views:CustomerListView/>
</DataTemplate>
<DataTemplate DataType = "{x:Type viewModels:OrderViewModel}">
<views:OrderView/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height = "Auto" />
<RowDefinition Height = "*" />
</Grid.RowDefinitions>
<Grid x:Name = "NavBar">
<Grid.ColumnDefinitions>
<ColumnDefinition Width = "*" />
<ColumnDefinition Width = "*" />
<ColumnDefinition Width = "*" />
</Grid.ColumnDefinitions>
<Button Content = "Customers"
Command = "{Binding NavCommand}"
CommandParameter = "customers"
Grid.Column = "0" />
<Button Content = "Order"
Command = "{Binding NavCommand}"
CommandParameter = "orders"
Grid.Column = "2" />
</Grid>
<Grid x:Name = "MainContent" Grid.Row = "1">
<ContentControl Content = "{Binding CurrentViewModel}" />
</Grid>
</Grid>
</Window>
Poniżej znajduje się pełna implementacja MainWindowViewModel.
using MVVMHierarchiesDemo.ViewModel;
using MVVMHierarchiesDemo.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo {
class MainWindowViewModel : BindableBase {
public MainWindowViewModel() {
NavCommand = new MyICommand<string>(OnNav);
}
private CustomerListViewModel custListViewModel = new CustomerListViewModel();
private OrderViewModel orderViewModelModel = new OrderViewModel();
private BindableBase _CurrentViewModel;
public BindableBase CurrentViewModel {
get {return _CurrentViewModel;}
set {SetProperty(ref _CurrentViewModel, value);}
}
public MyICommand<string> NavCommand { get; private set; }
private void OnNav(string destination) {
switch (destination) {
case "orders":
CurrentViewModel = orderViewModelModel;
break;
case "customers":
default:
CurrentViewModel = custListViewModel;
break;
}
}
}
}
Wyprowadź wszystkie ViewModels z klasy BindableBase. Gdy powyższy kod zostanie skompilowany i wykonany, zobaczysz następujące dane wyjściowe.
Jak widać, dodaliśmy tylko dwa przyciski i CurrentViewModel w naszym MainWindow. Kliknięcie dowolnego przycisku spowoduje przejście do tego konkretnego widoku. Kliknijmy przycisk Klienci, a zobaczysz, że wyświetla się CustomerListView.
Zalecamy wykonanie powyższego przykładu krok po kroku w celu lepszego zrozumienia.
W tym rozdziale dowiemy się o walidacji. Przyjrzymy się również czystemu sposobowi sprawdzania poprawności z tym, co już obsługują powiązania WPF, ale wiążąc je ze składnikami MVVM.
Walidacja w MVVM
Kiedy aplikacja zaczyna akceptować dane wejściowe od użytkowników końcowych, należy rozważyć walidację tych danych wejściowych.
Upewnij się, że jest zgodny z ogólnymi wymaganiami.
WPF ma kilka świetnych kompilacji i funkcji w systemie powiązań do sprawdzania poprawności danych wejściowych i nadal można wykorzystać wszystkie te funkcje podczas wykonywania MVVM.
Należy pamiętać, że logika, która obsługuje walidację i definiuje, jakie reguły istnieją dla właściwości, które powinny być częścią modelu lub ViewModel, a nie samego widoku.
Nadal można używać wszystkich sposobów wyrażania walidacji, które są obsługiwane przez powiązanie danych WPF, w tym -
- Zgłaszanie wyjątków dla właściwości jest ustawione.
- Implementacja interfejsu IDataErrorInfo.
- Implementacja INotifyDataErrorInfo.
- Użyj reguł walidacji WPF.
Ogólnie rzecz biorąc, INotifyDataErrorInfo jest zalecane i zostało wprowadzone do WPF .net 4.5 i obsługuje zapytania obiektu pod kątem błędów związanych z właściwościami, a także naprawia kilka niedociągnięć we wszystkich innych opcjach. W szczególności umożliwia walidację asynchroniczną. Dzięki temu właściwościom można przypisać więcej niż jeden błąd.
Dodawanie walidacji
Spójrzmy na przykład, w którym dodamy obsługę walidacji do naszego widoku wejściowego, aw dużej aplikacji prawdopodobnie będziesz potrzebować tego w wielu miejscach w swojej aplikacji. Czasami w widokach, czasami w ViewModels, a czasami w tych obiektach pomocniczych wokół obiektów modelu znajdują się opakowania.
Jest to dobra praktyka umieszczania obsługi walidacji we wspólnej klasie bazowej, którą można następnie dziedziczyć z różnych scenariuszy.
Klasa bazowa będzie obsługiwać INotifyDataErrorInfo, dzięki czemu ta walidacja zostanie wyzwolona po zmianie właściwości.
Utwórz dodaj nową klasę o nazwie ValidatableBindableBase. Ponieważ mamy już klasę bazową do obsługi zmiany właściwości, wyprowadźmy z niej klasę bazową, a także zaimplementujmy interfejs INotifyDataErrorInfo.
Poniżej przedstawiono implementację klasy ValidatableBindableBase.
using System;
using System.Collections.Generic;
using System.ComponentModel;
//using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace MVVMHierarchiesDemo {
public class ValidatableBindableBase : BindableBase, INotifyDataErrorInfo {
private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();
public event EventHandler<DataErrorsChangedEventArgs>
ErrorsChanged = delegate { };
public System.Collections.IEnumerable GetErrors(string propertyName) {
if (_errors.ContainsKey(propertyName))
return _errors[propertyName];
else
return null;
}
public bool HasErrors {
get { return _errors.Count > 0; }
}
protected override void SetProperty<T>(ref T member, T val,
[CallerMemberName] string propertyName = null) {
base.SetProperty<T>(ref member, val, propertyName);
ValidateProperty(propertyName, val);
}
private void ValidateProperty<T>(string propertyName, T value) {
var results = new List<ValidationResult>();
//ValidationContext context = new ValidationContext(this);
//context.MemberName = propertyName;
//Validator.TryValidateProperty(value, context, results);
if (results.Any()) {
//_errors[propertyName] = results.Select(c => c.ErrorMessage).ToList();
} else {
_errors.Remove(propertyName);
}
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
}
}
Teraz dodaj AddEditCustomerView i AddEditCustomerViewModel w odpowiednich folderach. Poniżej znajduje się kod AddEditCustomerView.xaml.
<UserControl x:Class = "MVVMHierarchiesDemo.Views.AddEditCustomerView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height = "Auto" />
<RowDefinition Height = "Auto" />
</Grid.RowDefinitions>
<Grid x:Name = "grid1"
HorizontalAlignment = "Left"
DataContext = "{Binding Customer}"
Margin = "10,10,0,0"
VerticalAlignment = "Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width = "Auto" />
<ColumnDefinition Width = "Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height = "Auto" />
<RowDefinition Height = "Auto" />
<RowDefinition Height = "Auto" />
<RowDefinition Height = "Auto" />
</Grid.RowDefinitions>
<Label Content = "First Name:"
Grid.Column = "0"
HorizontalAlignment = "Left"
Margin = "3"
Grid.Row = "0"
VerticalAlignment = "Center" />
<TextBox x:Name = "firstNameTextBox"
Grid.Column = "1"
HorizontalAlignment = "Left"
Height = "23"
Margin = "3"
Grid.Row = "0"
Text = "{Binding FirstName, ValidatesOnNotifyDataErrors = True}"
VerticalAlignment = "Center"
Width = "120" />
<Label Content = "Last Name:"
Grid.Column = "0"
HorizontalAlignment = "Left"
Margin = "3"
Grid.Row = "1"
VerticalAlignment = "Center" />
<TextBox x:Name = "lastNameTextBox"
Grid.Column = "1"
HorizontalAlignment = "Left"
Height = "23"
Margin = "3"
Grid.Row = "1"
Text = "{Binding LastName, ValidatesOnNotifyDataErrors = True}"
VerticalAlignment = "Center"
Width = "120" />
<Label Content = "Email:"
Grid.Column = "0"
HorizontalAlignment = "Left"
Margin = "3"
Grid.Row = "2"
VerticalAlignment = "Center" />
<TextBox x:Name = "emailTextBox"
Grid.Column = "1"
HorizontalAlignment = "Left"
Height = "23"
Margin = "3"
Grid.Row = "2"
Text = "{Binding Email, ValidatesOnNotifyDataErrors = True}"
VerticalAlignment = "Center"
Width = "120" />
<Label Content = "Phone:"
Grid.Column = "0"
HorizontalAlignment = "Left"
Margin = "3"
Grid.Row = "3"
VerticalAlignment = "Center" />
<TextBox x:Name = "phoneTextBox"
Grid.Column = "1"
HorizontalAlignment = "Left"
Height = "23"
Margin = "3"
Grid.Row = "3"
Text = "{Binding Phone, ValidatesOnNotifyDataErrors = True}"
VerticalAlignment = "Center"
Width = "120" />
</Grid>
<Grid Grid.Row = "1">
<Button Content = "Save"
Command = "{Binding SaveCommand}"
HorizontalAlignment = "Left"
Margin = "25,5,0,0"
VerticalAlignment = "Top"
Width = "75" />
<Button Content = "Add"
Command = "{Binding SaveCommand}"
HorizontalAlignment = "Left"
Margin = "25,5,0,0"
VerticalAlignment = "Top"
Width = "75" />
<Button Content = "Cancel"
Command = "{Binding CancelCommand}"
HorizontalAlignment = "Left"
Margin = "150,5,0,0"
VerticalAlignment = "Top"
Width = "75" />
</Grid>
</Grid>
</UserControl>
Poniżej znajduje się implementacja AddEditCustomerViewModel.
using MVVMHierarchiesDemo.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.ViewModel {
class AddEditCustomerViewModel : BindableBase {
public AddEditCustomerViewModel() {
CancelCommand = new MyIcommand(OnCancel);
SaveCommand = new MyIcommand(OnSave, CanSave);
}
private bool _EditMode;
public bool EditMode {
get { return _EditMode; }
set { SetProperty(ref _EditMode, value);}
}
private SimpleEditableCustomer _Customer;
public SimpleEditableCustomer Customer {
get { return _Customer; }
set { SetProperty(ref _Customer, value);}
}
private Customer _editingCustomer = null;
public void SetCustomer(Customer cust) {
_editingCustomer = cust;
if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged;
Customer = new SimpleEditableCustomer();
Customer.ErrorsChanged += RaiseCanExecuteChanged;
CopyCustomer(cust, Customer);
}
private void RaiseCanExecuteChanged(object sender, EventArgs e) {
SaveCommand.RaiseCanExecuteChanged();
}
public MyIcommand CancelCommand { get; private set; }
public MyIcommand SaveCommand { get; private set; }
public event Action Done = delegate { };
private void OnCancel() {
Done();
}
private async void OnSave() {
Done();
}
private bool CanSave() {
return !Customer.HasErrors;
}
}
}
Poniżej przedstawiono implementację klasy SimpleEditableCustomer.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.Model {
public class SimpleEditableCustomer : ValidatableBindableBase {
private Guid _id;
public Guid Id {
get { return _id; }
set { SetProperty(ref _id, value); }
}
private string _firstName;
[Required]
public string FirstName {
get { return _firstName; }
set { SetProperty(ref _firstName, value); }
}
private string _lastName;
[Required]
public string LastName {
get { return _lastName; }
set { SetProperty(ref _lastName, value); }
}
private string _email;
[EmailAddress]
public string Email {
get { return _email; }
set { SetProperty(ref _email, value); }
}
private string _phone;
[Phone]
public string Phone {
get { return _phone; }
set { SetProperty(ref _phone, value); }
}
}
}
Kiedy powyższy kod zostanie skompilowany i wykonany, zobaczysz następujące okno.
Po naciśnięciu przycisku Dodaj klienta zobaczysz następujący widok. Gdy użytkownik pozostawi dowolne pole puste, zostanie ono podświetlone, a przycisk zapisywania zostanie wyłączony.
W tym rozdziale pokrótce omówimy wstrzykiwanie zależności. Omówiliśmy już powiązanie danych oddzielające widoki i modele widoków od siebie, co pozwala im komunikować się bez wyraźnej wiedzy o tym, co dzieje się na drugim końcu komunikacji.
Teraz potrzebujemy czegoś podobnego, aby oddzielić nasz ViewModel od usług klienta.
We wczesnych dniach programowania obiektowego programiści stawiali czoła problemowi tworzenia i pobierania wystąpień klas w aplikacjach. Zaproponowano różne rozwiązania tego problemu.
W ciągu ostatnich kilku lat wstrzykiwanie zależności i inwersja kontroli (IoC) zyskały popularność wśród programistów i mają pierwszeństwo przed niektórymi starszymi rozwiązaniami, takimi jak wzorzec Singleton.
Dependency Injection / IoC Containers
IoC i iniekcja zależności to dwa wzorce projektowe, które są ściśle ze sobą powiązane, a kontener to w zasadzie fragment kodu infrastruktury, który wykonuje oba te wzorce za Ciebie.
Wzorzec IoC dotyczy delegowania odpowiedzialności za konstrukcję, a wzorzec iniekcji zależności dotyczy dostarczania zależności do obiektu, który został już skonstruowany.
Oba można traktować jako dwufazowe podejście do konstruowania. Kiedy używasz kontenera, kontener ma kilka obowiązków, które są następujące:
- Na żądanie konstruuje obiekt.
- Kontener określi, od czego zależy ten obiekt.
- Konstruowanie tych zależności.
- Wstrzykiwanie ich do budowanego obiektu.
- Proces rekurencyjny.
Przyjrzyjmy się, jak możemy użyć iniekcji zależności, aby przerwać rozdzielanie między ViewModels a usługami klienta. Połączymy formularz AddEditCustomerViewModel obsługi zapisywania za pomocą powiązanego z tym wstrzykiwania zależności.
Najpierw musimy utworzyć nowy interfejs w naszym projekcie w folderze Services. Jeśli nie masz folderu usług w swoim projekcie, najpierw utwórz go i dodaj następujący interfejs do folderu Services.
using MVVMHierarchiesDemo.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.Services {
public interface ICustomersRepository {
Task<List<Customer>> GetCustomersAsync();
Task<Customer> GetCustomerAsync(Guid id);
Task<Customer> AddCustomerAsync(Customer customer);
Task<Customer> UpdateCustomerAsync(Customer customer);
Task DeleteCustomerAsync(Guid customerId);
}
}
Poniżej znajduje się implementacja ICustomersRepository.
using MVVMHierarchiesDemo.Model;
using System;
using System.Collections.Generic;
using System.Linq; using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.Services {
public class CustomersRepository : ICustomersRepository {
ZzaDbContext _context = new ZzaDbContext();
public Task<List<Customer>> GetCustomersAsync() {
return _context.Customers.ToListAsync();
}
public Task<Customer> GetCustomerAsync(Guid id) {
return _context.Customers.FirstOrDefaultAsync(c => c.Id == id);
}
public async Task<Customer> AddCustomerAsync(Customer customer){
_context.Customers.Add(customer);
await _context.SaveChangesAsync();
return customer;
}
public async Task<Customer> UpdateCustomerAsync(Customer customer) {
if (!_context.Customers.Local.Any(c => c.Id == customer.Id)) {
_context.Customers.Attach(customer);
}
_context.Entry(customer).State = EntityState.Modified;
await _context.SaveChangesAsync();
return customer;
}
public async Task DeleteCustomerAsync(Guid customerId) {
var customer = _context.Customers.FirstOrDefault(c => c.Id == customerId);
if (customer != null) {
_context.Customers.Remove(customer);
}
await _context.SaveChangesAsync();
}
}
}
Prostym sposobem wykonania obsługi zapisywania jest dodanie nowego wystąpienia ICustomersRepository w AddEditCustomerViewModel i przeciążenie konstruktora AddEditCustomerViewModel i CustomerListViewModel.
private ICustomersRepository _repo;
public AddEditCustomerViewModel(ICustomersRepository repo) {
_repo = repo;
CancelCommand = new MyIcommand(OnCancel);
SaveCommand = new MyIcommand(OnSave, CanSave);
}
Zaktualizuj metodę OnSave, jak pokazano w poniższym kodzie.
private async void OnSave() {
UpdateCustomer(Customer, _editingCustomer);
if (EditMode)
await _repo.UpdateCustomerAsync(_editingCustomer);
else
await _repo.AddCustomerAsync(_editingCustomer);
Done();
}
private void UpdateCustomer(SimpleEditableCustomer source, Customer target) {
target.FirstName = source.FirstName;
target.LastName = source.LastName;
target.Phone = source.Phone;
target.Email = source.Email;
}
Poniżej znajduje się pełny AddEditCustomerViewModel.
using MVVMHierarchiesDemo.Model;
using MVVMHierarchiesDemo.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.ViewModel {
class AddEditCustomerViewModel : BindableBase {
private ICustomersRepository _repo;
public AddEditCustomerViewModel(ICustomersRepository repo) {
_repo = repo;
CancelCommand = new MyIcommand(OnCancel);
SaveCommand = new MyIcommand(OnSave, CanSave);
}
private bool _EditMode;
public bool EditMode {
get { return _EditMode; }
set { SetProperty(ref _EditMode, value); }
}
private SimpleEditableCustomer _Customer;
public SimpleEditableCustomer Customer {
get { return _Customer; }
set { SetProperty(ref _Customer, value); }
}
private Customer _editingCustomer = null;
public void SetCustomer(Customer cust) {
_editingCustomer = cust;
if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged;
Customer = new SimpleEditableCustomer();
Customer.ErrorsChanged += RaiseCanExecuteChanged;
CopyCustomer(cust, Customer);
}
private void RaiseCanExecuteChanged(object sender, EventArgs e) {
SaveCommand.RaiseCanExecuteChanged();
}
public MyIcommand CancelCommand { get; private set; }
public MyIcommand SaveCommand { get; private set; }
public event Action Done = delegate { };
private void OnCancel() {
Done();
}
private async void OnSave() {
UpdateCustomer(Customer, _editingCustomer);
if (EditMode)
await _repo.UpdateCustomerAsync(_editingCustomer);
else
await _repo.AddCustomerAsync(_editingCustomer);
Done();
}
private void UpdateCustomer(SimpleEditableCustomer source, Customer target) {
target.FirstName = source.FirstName;
target.LastName = source.LastName;
target.Phone = source.Phone;
target.Email = source.Email;
}
private bool CanSave() {
return !Customer.HasErrors;
}
private void CopyCustomer(Customer source, SimpleEditableCustomer target) {
target.Id = source.Id;
if (EditMode) {
target.FirstName = source.FirstName;
target.LastName = source.LastName;
target.Phone = source.Phone;
target.Email = source.Email;
}
}
}
}
Gdy powyższy kod zostanie skompilowany i wykonany, zobaczysz te same dane wyjściowe, ale teraz ViewModels są luźniej oddzielone.
Po naciśnięciu przycisku Dodaj klienta zobaczysz następujący widok. Gdy użytkownik pozostawi dowolne pole puste, zostanie ono podświetlone, a przycisk zapisywania zostanie wyłączony.
Zdarzenie to konstrukcja programistyczna, która reaguje na zmianę stanu, powiadamiając wszystkie punkty końcowe, które zostały zarejestrowane w celu powiadamiania. Przede wszystkim zdarzenia służą do informowania użytkownika o wejściu za pomocą myszy i klawiatury, ale ich przydatność nie ogranicza się do tego. Za każdym razem, gdy zostanie wykryta zmiana stanu, na przykład po załadowaniu lub zainicjowaniu obiektu, można wywołać zdarzenie w celu ostrzeżenia wszystkich zainteresowanych stron trzecich.
W aplikacji WPF, która używa wzorca projektowego MVVM (Model-View-ViewModel), model widoku jest składnikiem odpowiedzialnym za obsługę logiki i stanu prezentacji aplikacji.
Plik związany z kodem widoku nie powinien zawierać kodu do obsługi zdarzeń, które są wywoływane z dowolnego elementu interfejsu użytkownika (UI), takiego jak Button lub ComboBox, ani nie powinien zawierać żadnej logiki specyficznej dla domeny.
Idealnie byłoby, gdyby kod związany z View zawierał tylko konstruktora, który wywołuje metodę InitializeComponent i być może jakiś dodatkowy kod do kontrolowania lub interakcji z warstwą widoku, która jest trudna lub nieefektywna do wyrażenia w języku XAML, np. Złożone animacje.
Przyjrzyjmy się prostemu przykładowi zdarzeń kliknięcia przycisku w naszej aplikacji. Poniżej znajduje się kod XAML pliku MainWindow.xaml, w którym zobaczysz dwa przyciski.
<Window x:Class = "MVVMHierarchiesDemo.MainWindow"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local = "clr-namespace:MVVMHierarchiesDemo"
xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views"
xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
<views:CustomerListView/>
</DataTemplate>
<DataTemplate DataType = "{x:Type viewModels:OrderViewModel}">
<views:OrderView/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height = "Auto" />
<RowDefinition Height = "*" />
</Grid.RowDefinitions>
<Grid x:Name = "NavBar">
<Grid.ColumnDefinitions>
<ColumnDefinition Width = "*" />
<ColumnDefinition Width = "*" />
<ColumnDefinition Width = "*" />
</Grid.ColumnDefinitions>
<Button Content = "Customers"
Command = "{Binding NavCommand}"
CommandParameter = "customers"
Grid.Column = "0" />
<Button Content = "Order"
Command = "{Binding NavCommand}"
CommandParameter = "orders"
Grid.Column = "2" />
</Grid>
<Grid x:Name = "MainContent" Grid.Row = "1">
<ContentControl Content = "{Binding CurrentViewModel}" />
</Grid>
</Grid>
</Window>
Widać, że właściwość Click przycisku nie jest używana w powyższym pliku XAML, ale właściwości Command i CommandParameter są używane do ładowania różnych widoków po naciśnięciu przycisku. Teraz musisz zdefiniować implementację poleceń w pliku MainWindowViewModel.cs, ale nie w pliku widoku. Poniżej znajduje się pełna implementacja MainWindowViewModel.
using MVVMHierarchiesDemo.ViewModel;
using MVVMHierarchiesDemo.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo {
class MainWindowViewModel : BindableBase {
public MainWindowViewModel() {
NavCommand = new MyICommand<string>(OnNav);
}
private CustomerListViewModel custListViewModel = new CustomerListViewModel();
private OrderViewModel orderViewModelModel = new OrderViewModel();
private BindableBase _CurrentViewModel;
public BindableBase CurrentViewModel {
get { return _CurrentViewModel; }
set { SetProperty(ref _CurrentViewModel, value); }
}
public MyICommand<string> NavCommand { get; private set; }
private void OnNav(string destination) {
switch (destination) {
case "orders":
CurrentViewModel = orderViewModelModel;
break;
case "customers":
default:
CurrentViewModel = custListViewModel;
break;
}
}
}
}
Wyprowadź wszystkie ViewModels z klasy BindableBase. Gdy powyższy kod zostanie skompilowany i wykonany, zobaczysz następujące dane wyjściowe.
Jak widać, dodaliśmy tylko dwa przyciski i CurrentViewModel w naszym MainWindow. Teraz, jeśli klikniesz dowolny przycisk, przejdzie do tego konkretnego widoku. Kliknijmy przycisk Klienci, a zobaczysz, że wyświetla się CustomerListView.
Zalecamy wykonanie powyższego przykładu metodą krok po kroku w celu lepszego zrozumienia.
Ideą testów jednostkowych jest pobranie dyskretnych fragmentów kodu (jednostek) i napisanie metod testowych, które używają kodu w oczekiwany sposób, a następnie przetestowanie, czy dają oczekiwane wyniki.
Będąc samym kodem, testy jednostkowe są kompilowane tak samo jak reszta projektu.
Są one również wykonywane przez oprogramowanie do uruchamiania testów, które może przyspieszyć każdy test, skutecznie pokazując kciuk w górę lub w dół, aby wskazać, czy test przeszedł lub nie powiódł się.
Spójrzmy na przykład utworzony wcześniej. Poniżej znajduje się implementacja modelu studenckiego.
using System.ComponentModel;
namespace MVVMDemo.Model {
public class StudentModel {}
public class Student : INotifyPropertyChanged {
private string firstName;
private string lastName;
public string FirstName {
get { return firstName; }
set {
if (firstName != value) {
firstName = value;
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
}
public string LastName {
get { return lastName; }
set {
if (lastName != value) {
lastName = value;
RaisePropertyChanged("LastName");
RaisePropertyChanged("FullName");
}
}
}
public string FullName {
get {
return firstName + " " + lastName;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
Poniżej znajduje się implementacja StudentView.
<UserControl x:Class="MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:data = "clr-namespace:MVVMDemo.Model"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<UserControl.Resources>
<DataTemplate DataType = "{x:Type data:Student}">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<StackPanel Orientation = "Horizontal">
<ListBox ItemsSource = "{Binding Students}"
SelectedItem = "{Binding SelectedStudent}"/>
<Button Content = "Delete"
Command = "{Binding DeleteCommand}"
HorizontalAlignment = "Left"
VerticalAlignment = "Top"
Width = "75" />
</StackPanel>
</Grid>
</UserControl>
Poniżej znajduje się implementacja StudentViewModel.
using MVVMDemo.Model;
using System.Collections.ObjectModel;
using System.Windows.Input;
using System;
namespace MVVMDemo.ViewModel {
public class StudentViewModel {
public MyICommand DeleteCommand { get; set;}
public StudentViewModel() {
LoadStudents();
DeleteCommand = new MyICommand(OnDelete, CanDelete);
}
public ObservableCollection<Student> Students {
get;
set;
}
public void LoadStudents() {
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { FirstName = "Mark", LastName = "Allain" });
students.Add(new Student { FirstName = "Allen", LastName = "Brown" });
students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
Students = students;
}
private Student _selectedStudent;
public Student SelectedStudent {
get {
return _selectedStudent;
}
set {
_selectedStudent = value;
DeleteCommand.RaiseCanExecuteChanged();
}
}
private void OnDelete() {
Students.Remove(SelectedStudent);
}
private bool CanDelete() {
return SelectedStudent != null;
}
public int GetStudentCount() {
return Students.Count;
}
}
}
Poniżej znajduje się plik MainWindow.xaml.
<Window x:Class = "MVVMDemo.MainWindow"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local = "clr-namespace:MVVMDemo"
xmlns:views = "clr-namespace:MVVMDemo.Views"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Grid>
<views:StudentView x:Name = "StudentViewControl"/>
</Grid>
</Window>
Poniżej znajduje się implementacja MyICommand, która implementuje interfejs ICommand.
using System;
using System.Windows.Input;
namespace MVVMDemo {
public class MyICommand : ICommand {
Action _TargetExecuteMethod;
Func<bool> _TargetCanExecuteMethod;
public MyICommand(Action executeMethod) {
_TargetExecuteMethod = executeMethod;
}
public MyICommand(Action executeMethod, Func<bool> canExecuteMethod) {
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}
public void RaiseCanExecuteChanged() {
CanExecuteChanged(this, EventArgs.Empty);
}
bool ICommand.CanExecute(object parameter) {
if (_TargetCanExecuteMethod != null) {
return _TargetCanExecuteMethod();
}
if (_TargetExecuteMethod != null) {
return true;
}
return false;
}
// Beware - should use weak references if command instance lifetime
is longer than lifetime of UI objects that get hooked up to command
// Prism commands solve this in their implementation
public event EventHandler CanExecuteChanged = delegate { };
void ICommand.Execute(object parameter) {
if (_TargetExecuteMethod != null) {
_TargetExecuteMethod();
}
}
}
}
Gdy powyższy kod zostanie skompilowany i wykonany, w oknie głównym zobaczysz następujące dane wyjściowe.
Aby napisać test jednostkowy dla powyższego przykładu, dodajmy nowy projekt testowy do rozwiązania.
Dodaj odniesienie do projektu, klikając prawym przyciskiem myszy References.
Wybierz istniejący projekt i kliknij OK.
Dodajmy teraz prosty test, który sprawdzi liczbę uczniów, jak pokazano w poniższym kodzie.
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MVVMDemo.ViewModel;
namespace MVVMTest {
[TestClass]
public class UnitTest1 {
[TestMethod]
public void TestMethod1() {
StudentViewModel sViewModel = new StudentViewModel();
int count = sViewModel.GetStudentCount();
Assert.IsTrue(count == 3);
}
}
}
Aby wykonać ten test, wybierz opcję menu Test → Uruchom → Wszystkie testy.
W Eksploratorze testów można zobaczyć, że test został zaliczony, ponieważ w StudentViewModel dodano trzech uczniów. Zmień warunek zliczania z 3 na 4, jak pokazano w poniższym kodzie.
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MVVMDemo.ViewModel;
namespace MVVMTest {
[TestClass]
public class UnitTest1 {
[TestMethod] public void TestMethod1() {
StudentViewModel sViewModel = new StudentViewModel();
int count = sViewModel.GetStudentCount();
Assert.IsTrue(count == 4);
}
}
}
Gdy plan testu zostanie ponownie wykonany, zobaczysz, że test się nie powiódł, ponieważ liczba uczniów nie jest równa 4.
Zalecamy wykonanie powyższego przykładu metodą krok po kroku w celu lepszego zrozumienia.
W tym rozdziale omówimy dostępne zestawy narzędzi lub struktury MVVM. Możesz również użyć tych struktur, aby nie musieć pisać wielu powtarzających się kodów, aby samodzielnie zaimplementować wzorzec MVVM. Oto niektóre z najpopularniejszych frameworków -
- Prism
- MVVM Light
- Caliburn Micro
Pryzmat
Prism zapewnia wskazówki w postaci przykładów i dokumentacji, które ułatwiają projektowanie i tworzenie rozbudowanych, elastycznych i łatwych w utrzymaniu aplikacji klasycznych Windows Presentation Foundation (WPF). Bogate aplikacje internetowe (RIA) utworzone za pomocą wtyczki przeglądarki Microsoft Silverlight i aplikacji systemu Windows.
Prism wykorzystuje wzorce projektowe, które obejmują ważne zasady projektowania architektonicznego, takie jak oddzielenie problemów i luźne powiązania.
Prism pomaga projektować i budować aplikacje przy użyciu luźno powiązanych komponentów, które mogą ewoluować niezależnie, ale które można łatwo i bezproblemowo zintegrować z całą aplikacją.
Tego typu aplikacje są znane jako aplikacje złożone.
Prism ma wiele funkcji, które są gotowe do użycia. Oto niektóre z ważnych cech Prism.
Wzorzec MVVM
Prism obsługuje wzorzec MVVM. Ma klasę Bindablebase podobną do tej, która została zaimplementowana we wcześniejszych rozdziałach.
Ma elastyczny ViewModelLocator, który ma konwencje, ale umożliwia przesłonięcie tych konwencji i deklaratywne podłączenie widoków i modeli widoków w luźny sposób.
Modułowość
Jest to możliwość rozbicia kodu na całkowicie luźno powiązane biblioteki klas na części i połączenie ich w czasie wykonywania w spójną całość dla użytkownika końcowego, podczas gdy kod pozostaje w pełni oddzielony.
Skład / regiony interfejsu użytkownika
Jest to możliwość podłączania widoków do kontenerów bez widoku, który wykonuje podłączanie, wymagając jawnego odniesienia do samego kontenera interfejsu użytkownika.
Nawigacja
Prism ma funkcje nawigacyjne, które nakładają się na regiony, takie jak nawigacja do przodu i do tyłu oraz stos nawigacji, który umożliwia modelom widoku bezpośredni udział w procesie nawigacji.
Polecenia
Prism ma polecenia, więc ma polecenie delegata, które jest bardzo podobne do MyICommand, którego używaliśmy we wcześniejszych rozdziałach, z wyjątkiem tego, że ma dodatkową solidność, aby chronić cię przed wyciekami pamięci.
Wydarzenia Pub / Sub
Prism obsługuje również wydarzenia Pub / Sub. Są to luźno powiązane zdarzenia, w których wydawca i subskrybent mogą mieć różne okresy istnienia i nie muszą mieć do siebie jawnych odniesień, aby komunikować się za pośrednictwem zdarzeń.
MVVM Light
MVVM Light jest produkowany przez Laurent Bugnion i pomaga oddzielić widok od modelu, który tworzy aplikacje, które są czystsze i łatwiejsze w utrzymaniu i rozszerzaniu.
Tworzy również testowalne aplikacje i pozwala mieć znacznie cieńszą warstwę interfejsu użytkownika (która jest trudniejsza do automatycznego testowania).
Ten zestaw narzędzi kładzie szczególny nacisk na otwieranie i edytowanie interfejsu użytkownika w programie Blend, w tym na tworzenie danych czasu projektowania, aby umożliwić użytkownikom programu Blend „zobaczenie czegoś” podczas pracy z kontrolkami danych.
Caliburn Micro
Jest to kolejna mała platforma typu open source, która pomaga implementować wzorzec MVVM, a także obsługuje wiele rzeczy po wyjęciu z pudełka.
Caliburn Micro to mała, ale potężna platforma przeznaczona do tworzenia aplikacji na wszystkich platformach XAML.
Dzięki silnemu wsparciu dla MVVM i innych sprawdzonych wzorców interfejsu użytkownika, Caliburn Micro umożliwi szybkie zbudowanie rozwiązania bez konieczności poświęcania jakości kodu lub testowalności.