Programowanie Windows MVVM w WPF ale najpierw MVC
















































- Slides: 48
Programowanie Windows MVVM w WPF … ale najpierw MVC i MVP Jacek Matulewski 9 października 2019 http: //www. fizyka. umk. pl/~jacek/dydaktyka/winprog_v 2/
Inżynieria oprogramowania – Wzorce projektowe Jacek Matulewski Instytut Fizyki, UMK WWW: http: //www. fizyka. umk. pl/~jacek E-mail: jacek@fizyka. umk. pl Reklama Inżynieria oprogramowania Wzorzec architekt. MVC WWW: http: //www. fizyka. umk. pl/~jacek/dydaktyka/inzynieria/index. html semestr letni 2016
Inżynieria oprogramowania – Wzorce projektowe Główna lektura 1 Głównym materiałem źródłowym jest książka tzw. gangu czworga pt. „Wzorce projektowe”.
Inżynieria oprogramowania – Wzorce projektowe Główna lektura 1 Głównym materiałem źródłowym jest książka tzw. gangu czworga pt. „Wzorce projektowe”.
Inżynieria oprogramowania – Wzorce projektowe Główna lektura 2 Dodatkowo wykorzystamy adaptacje do C# wzorców G 4 opisane w książce S. J. Metskera
Inżynieria oprogramowania – Wzorce projektowe Inspiracja Christopher Alexander – architekt, teoretyk architektura zorientowana na człowieka np. kształtowanie przestrzeni
Inżynieria oprogramowania – Wzorce projektowe Inspiracja Władimir Propp, Joseph Cambell – konstrukcja opowieści (mit/bajka/powieść/film) „Wyprawa Bohatera” – zbiór stałych elementów (typy bohaterów) i budowy fabuły (etapy) Christopher Vogler – „Podróż autora”, poradnik
Inżynieria oprogramowania – Wzorce architektoniczne Przykładowy projekt: „Labirynt” • Aplikacja konsolowa zaprojektowana zgodnie ze wzorcem architektonicznym MVC, czyli Model-View-Controler • Model – dane i logika labiryntu • Widok – moduł rysujący Użytkownik labirynt w konsoli używa jest oglądany • Kontroler – przyjmuje Kontroler Widok wejście z klawiatury, modyfikuje model modyfikuje aktualizuje Model
Inżynieria oprogramowania – Wzorce architektoniczne Architektura MVC Kontrola oraz przepływ informacji między modułami aplikacji w architekturze MVC Użytkownik używa jest oglądany Kontroler Widok modyfikuje aktualizuje Model
Inżynieria oprogramowania – Wzorce architektoniczne Architektura MVC • Jest wiele wersji samego MVC (+ MVP) • My zaimplementujemy wersję z pasywnymi modelem i widokiem (passive) oraz nadzorującym kontrolerem (supervising) lepiej pasujący do konsoli Użytkownik (bez zdarzeń) używa jest oglądany • Kontrolera zredukujemy do funkcji main aktualizuje Kontroler Widok modyfikuje Model
Inżynieria oprogramowania – Wzorce architektoniczne Przykładowy projekt: „Labirynt”
Inżynieria oprogramowania – Wzorce architektoniczne Model (klasa Labirynt) Pasywny model przechowujący stan aplikacji VC++: Solution Explorer, View Class Diagram
Inżynieria oprogramowania – Wzorce architektoniczne Model (klasa Labirynt) Klasy modelu (C++) class Miejsce. WLabiryncie { public: virtual Rezultat. Próby. Wejścia Spróbuj. Wejść() = 0; virtual int Wejdź(int indeks. Bieżącej. Komórki) { return -1; } virtual bool Otwórz() { return false; } }; class Komórka : public Miejsce. WLabiryncie {. . . enum Rezultat. Próby. Wejścia { Nieokreślony = 0, Powodzenie, Nie. Można. Wejść, Zamknięte }; enum Kierunek { Północ = 0, Południe = 1, Wschód = 2, Zachód = 3 }; enum Stan. Gry { Niezakończona = 0, Śmierć, Wygrana };
Inżynieria oprogramowania – Wzorce architektoniczne Model (klasa Labirynt) Klasy modelu (C++) class Komórka : public Miejsce. WLabiryncie { private: int indeks; Miejsce. WLabiryncie* sąsiednie. Miejsca[4]; public: Komórka(int indeks); Miejsce. WLabiryncie* Pobierz. Miejsce. Po. Stronie(Kierunek kierunek) const; void PowiążZMiejscem(Kierunek kierunek, Miejsce. WLabiryncie* miejsce); virtual Rezultat. Próby. Wejścia Spróbuj. Wejść(); virtual int Wejdź(int indeks. Bieżącej. Komórki); int Pobierz. Indeks(); bool Otwórz. Drzwi(Kierunek kierunek); bool Otwórz. Drzwi(); };
Inżynieria oprogramowania – Wzorce architektoniczne Model (klasa Labirynt) Klasy modelu (C++) class Labirynt { private: int liczba. Komórek; PKomórka* komórki; int indeks. Bieżącej. Komórki, indeks. Celu; Stan. Gry stan. Gry = Niezakończona; public: Labirynt(int liczba. Komórek, int indeks. Początkowej. Komórki, int indeks. Celu); ~Labirynt(); void Dodaj. Komórkę(int indeks, Komórka* komórka); Komórka* Pobierz. BieżącąKomórkę(); Rezultat. Próby. Wejścia PrzejdźWKierunku(Kierunek kierunek); void Zakończ(); Stan. Gry Pobierz. Stan. Gry(); };
Inżynieria oprogramowania – Wzorce architektoniczne Widok (klasa Widok) Klasa widoku (zbiór funkcji bez własnego stanu) #pragma once #include "Model. h" class Widok { private: Labirynt* model; public: Widok(Labirynt* model); static void Wyświetl. InformacjęOKomórce(Komórka* komórka); void Wyświetl. InformacjęOBieżącej. Komórce() const; static void Wyświetl. InformacjęOPróbie. Przejścia. WKierunku(Kierunek kierunek); void Wyświetl. InformacjęORezultacie. Próby. Przejścia(Rezultat. Próby. Wejścia) const; void Wyświetl. InformacjęOPróbie. Otwarcia. Drzwi() const; void Wyświetl. InformacjęORezultacie. Próby. Otwarcia. Drzwi(bool wynik) const; void Wyświetl. InformacjęOStanie. Gry() const; };
Inżynieria oprogramowania – Wzorce architektoniczne Kontroler (funkcja main) #pragma once #include "Model. h" #include "Widok. h" class Kontroler { protected: Labirynt* model; Widok* widok; void Spróbuj. Przejść(Kierunek kierunek); void Spróbuj. OtworzyćDrzwi(); public: Kontroler(void); ~Kontroler(void); void Uruchom(); };
Inżynieria oprogramowania – Wzorce architektoniczne Funkcja main widzi tylko kontroler. Kontroler tworzy instancje modelu i widoku: #include "Kontroler. h" int main(int argc, char* argv[]) { Kontroler kontroler; kontroler. Uruchom(); return 0; }
Wzorzec MVP (model-view-presenter) - wzorzec architektoniczny dla aplikacji z graficznym interfejsem użytkownika (GUI) stworzony w Microsoft na potrzeby aplikacji Windows Forms Model – przechowuje dane (stan aplikacji), które mogą być prezentowane lub edytowane w GUI Widok – pasywny moduł prezentujący dane i przyjmująca żądania użytkownika poprzez kontrolki W projekcie być wiele widoków, Presenter – świadomy modelu i widoku, może pośredniczy między nimi ma swój jeden presenter – wiąże model wazkażdy widokiem rozszerzając zakres kodu, który może być objęty Widoktestami nie znajednostkowymi, źródła danych, tworzy wiązania. umie je tylko pokazywać. Danymi zapełnia go presenter.
Wzorzec MVP ? http: //msdn. microsoft. com/en-us/library/ff 647859. aspx
MVC, MVP, MVVM https: //tomyrhymond. wordpress. com/2011/09/16/mvc-mvp-and-mvvm/
Warstwy wzorca MVVM
Warstwy wzorca MVVM Widok – kod XAML (bez własnego kodu C#!) Model – czysty kod C# (nie jest świadomy ani wzorca, ani wyższych „technologii”)
Warstwy wzorca MVVM Model widoku – reprezentuje model przed widokiem (filtrowanie, przekształcanie i udostępnianie danych). Jednocześnie jest abstrakcją widoku.
Warstwy wzorca MVVM Odpowiedzialność grafika (kod XAML) Osobne narzędzie Blend for VS 2017 Nie wymaga VM, a jedynie wiązań (kontrakt) Odpowiedzialność programisty C# Testy jednostkowe (także VM) Model może być pasywny lub aktywny Rozwój M i VM nie wymaga widoku (ustalane są tylko udostępniane w VM własności i polecenia). Mniejsza zależność i liczba kontaktów
Przykład aplikacji MVVM Model using System; namespace Asystent. Zakupów. WPF. Model { public class Sumowanie. Kwot { public decimal Limit { get; private set; } public decimal Suma { get; private set; } public Sumowanie. Kwot(decimal limit, decimal suma = 0) { this. Limit = limit; this. Suma = suma; }. . . } }
Przykład aplikacji MVVM Model using System; namespace Asystent. Zakupów. WPF. Model { public class Sumowanie. Kwot {. . . public bool Czy. Kwota. Jest. Poprawna(decimal kwota) { bool czy. Dodatnia = kwota > 0; bool czy. Przekroczy. Limit = Suma + kwota > Limit; return czy. Dodatnia && !czy. Przekroczy. Limit; }. . . } }
Przykład aplikacji MVVM Model using System; namespace Asystent. Zakupów. WPF. Model { public class Sumowanie. Kwot {. . . public void Dodaj(decimal kwota) { if (!Czy. Kwota. Jest. Poprawna(kwota)) throw new Argument. Out. Of. Range. Exception( "Kwota zbyt duża lub ujemna"); Suma += kwota; } } }
Przykład aplikacji MVVM Widok Text. Block Text. Box, tb. Kwota Button, btn. Dodaj
Przykład aplikacji MVVM Widok <Window x: Class="Asystent. Zakupów. WPF. Main. Window" 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: Asystent. Zakupów. WPF" mc: Ignorable="d" Resize. Mode = "No. Resize" Title="Asystent zakupów" Height="200" Width="200"> <Grid. Row. Definitions> <Row. Definition Height="1*" /> </Grid. Row. Definitions>. . . </Grid> </Window>
Przykład aplikacji MVVM Widok <Window x: Class="Asystent. Zakupów. WPF. Main. Window". . . > <Grid. Row. Definitions>. . . </Grid. Row. Definitions> <Text. Block Horizontal. Alignment="Left" Grid. Row="0" Font. Size="25" Foreground="Navy" Margin="10"> Suma: <Run Foreground="Black" Font. Family="Courier New" Text="0" /> </Text. Block> <Text. Box x: Name="tb. Kwota" Font. Size="30" Font. Family="Courier New" Text. Alignment="Right" Margin="10" Grid. Row="1" Text="0" /> <Button x: Name="btn. Dodaj" Content="Dodaj" Font. Size="20" Margin="10" Grid. Row="2" /> </Grid> </Window>
Przykład aplikacji MVVM Model widoku using System. Windows; using System. Windows. Input; using System. Component. Model; namespace Asystent. Zakupów. WPF. Model. Widoku { using Model; Instancja modelu public class Model. Widoku : INotify. Property. Changed { private Sumowanie. Kwot model = new Sumowanie. Kwot(1000); public string Suma { get { return model. Suma. To. String(); } } Udostępnianie danych modelu Zamiast konwersji konwerter!
Przykład aplikacji MVVM Model widoku using. . . namespace Asystent. Zakupów. WPF. Model. Widoku { using Model; Powiadamianie widoku o zmianach własności public class Model. Widoku : INotify. Property. Changed {. . . public event Property. Changed. Event. Handler Property. Changed; private void on. Property. Changed(string nazwa. Własności) { Property. Changed? . Invoke( this, new Property. Changed. Event. Args(nazwa. Własnoscinazwa. Własności)); }
Przykład aplikacji MVVM Model widoku public class Model. Widoku : INotify. Property. Changed {. . . private ICommand dodaj. KwotęCommand; public ICommand Dodaj. Kwotę { get { if (dodaj. KwotęCommand == null) dodaj. KwotęCommand = new Relay. Command(. . . ); return dodaj. KwotęCommand; } } Polecenie (reagowanie na działania użytkownika w widoku) } Klasa Relay. Command (nie ma w. NET) Jej implementacja będzie na ćwiczeniach
Przykład aplikacji MVVM Model widoku using. . . namespace Asystent. Zakupów. WPF. Model. Widoku { using Model; public class Model. Widoku : INotify. Property. Changed {. . . Metoda pomocnicza private bool czyŁańcuch. Kwoty. Jest. Poprawny(string s) { if (string. Is. Null. Or. White. Space(s)) return false; decimal kwota; if (!decimal. Try. Parse(s, out kwota)) return false; else return model. Czy. Kwota. Jest. Poprawna(kwota); }
Przykład aplikacji MVVM Model widoku public class Model. Widoku : INotify. Property. Changed {. . . private ICommand dodaj. KwotęCommand; public ICommand Dodaj. Kwotę { get { if (dodaj. KwotęCommand == null) dodaj. KwotęCommand = new Relay. Command(execute, can. Execute); return dodaj. KwotęCommand; } } Polecenie (reagowanie na działania użytkownika w widoku) }
Przykład aplikacji MVVM Model widoku Wyrażenia Lambda: () => { } dodaj. KwotęCommand = new Relay. Command( Przekazanie działania do modelu (model odpowiada za stan aplikacji) (object argument) => //execute { decimal kwota = decimal. Parse((string)argument); model. Dodaj(kwota); on. Property. Changed("Suma"); }, (object argument) => //can execute { return CzyŁańcuch. Kwoty. Jest. Poprawny((string)argument); } ); Weryfikacja możliwości wykonania działania (wynik zwracany poprzez własność Can. Execute → informacja zwrotna w widoku)
Przykład aplikacji MVVM Widok – wiązania <Window x: Class="Asystent. Zakupów. WPF. Main. Window" 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: Asystent. Zakupów. WPF" xmlns: mw="clr-namespace: Asystent. Zakupów. WPF. Model. Widoku" mc: Ignorable="d" Title="Asystent zakupów" Height="200" Width="200"> <Window. Data. Context> <mw: Model. Widoku /> </Window. Data. Context> <Grid>. . . </Grid> </Window> Instancja modelu widoku w widoku
Przykład aplikacji MVVM Widok – wiązania <Grid>. . . <Text. Block Horizontal. Alignment="Left" Vertical. Alignment="Top" Font. Size="25" Foreground="Navy" Grid. Row="0" Margin="10, 10, 10"> Suma: <Run Foreground="Black" Font. Family="Courier New" Text="{Binding Path=Suma, Mode=One. Way}" /> </Text. Block> <Text. Box x: Name="tb. Kwota" Font. Size="30" Font. Family="Courier New" Text. Alignment="Right" Margin="10, 10, 10" Grid. Row="1" Text="0" /> <Button x: Name="btn. Dodaj" Content="Dodaj" Font. Size="20" Margin="10, 10, 10" Grid. Row="2" Command="{Binding Dodaj. Kwotę}" Command. Parameter="{Binding Element. Name=tb. Kwota, Path=Text}" /> </Grid>
Przykład aplikacji MVVM Widok – konwerter namespace Asystent. Zakupów. WPF { class Bool. To. Brush. Converter : IValue. Converter { public object Convert(object value, Type target. Type, object parameter, System. Globalization. Culture. Info culture) { bool b = (bool)value; return b ? Brushes. Black : Brushes. Red; } public object Convert. Back(object value, Type target. Type, object parameter, System. Globalization. Culture. Info culture) { throw new Not. Implemented. Exception(); } } } Konwertery także można testować testami jednostkowymi
Przykład aplikacji MVVM Widok – konwerter <Window x: Class="Asystent. Zakupów. WPF. Main. Window" 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: Asystent. Zakupów. WPF" xmlns: mw="clr-namespace: Asystent. Zakupów. WPF. Model. Widoku" mc: Ignorable="d" Title="Asystent zakupów" Height="200" Width="200"> <Window. Resources> <local: Bool. To. Brush. Converter x: Key="bool. To. Brush" /> </Window. Resources>. . . <Text. Box x: Name="tb. Kwota" Font. Size="30" Font. Family="Courier New" Text. Alignment="Right" Margin="10, 10, 10" Grid. Row="1" Text="0" Foreground="{Binding Element. Name=btn. Dodaj, Path=Is. Enabled, Mode=One. Way, Converter={Static. Resource bool. To. Brush}}" />. . . Multibinding – IMulti. Value. Converter → na ćwiczeniach
Wiązania Text="{Binding Source={Static. Resource źródło}, Path=Własność, Mode=One. Way, Converter={Static. Resource konwerter} String. Format=Cena (zł): {0: C}}" Path – własność (zależności) obiektu „dowiązywanego” (źródło wiązania, binding source); nazwa atrybutu może być pominięta Source – jawne wskazanie na źródło wiązania (np. z zasobów); domyślnie odczytywany z Data. Context Relative. Source – źródło w drzewie widoku np. {Binding Relative. Source={Relative. Source Self}, Path=Własność}" Element. Name – wskazanie na element XAML/kontrolkę (zamiast użycia kontekstu danych); wiązanie w obrębie widoku Mode = Two. Way (domyślne WPF) | One. Way (domyślne UWP) | One. Way. To. Source | One. Time Fallback. Value – wartość domyślna Update. Source. Trigger = Lost. Focus | Property. Changed | Explicit (tylko w powiadamianiu z widoku do źródła wiązania)
Wiązania Text="{Binding Source={Static. Resource źródło}, Path=Własność, Mode=One. Way, Converter={Static. Resource konwerter} String. Format=Cena (zł): {0: C}}" Liczba {0} to numeru wiązanej własności, ważne przy multibindingu – kolejne własności 1, 2, … String. Format – formatowanie łańcuchów (por. string. Format) np. String. Format="{}{0} + {1}" Przykłady: String. Format=Liczba: {0} metrów String. Format=Liczba: {0: F 2} //1234. 56 String. Format=Liczba: {0: F 0} //1235 String. Format=Liczba: {0: N 2} //1, 234. 56 String. Format=Liczba: {0: #, #. 0} z przecinkiem String. Format=Cena: {0: C} zł String. Format=Data: {0: dddd, MMMM dd}, Converter. Culture=pl-PL String. Format=Czas: {0: HH: mm} String. Format={}{0: #, #. 0} <-- gdy nie ma dodatkowe tekstu z przodu!! IData. Error. Info (od. NET 3. 5), INotify. Data. Error (od. NET 4. 5) umożliwiają monitorowanie danych w ramach wiązań <Text. Block Text="{Binding Source={x: Static system: Date. Time. Now}, Converter. Culture='pl-PL', String. Format=Polska data: {0: D}}" />
Przykład aplikacji MVVM Model widoku – standard (powtórzenie) public class Model. Widoku : INotify. Property. Changed { private Sumowanie. Kwot model = new Sumowanie. Kwot(1000); public string Suma { get { return model. Suma. To. String(); } }. . .
Przykład aplikacji MVVM Model widoku – alternatywa 1 (gorsza, ale kolekcje…) public class Model. Widoku : INotify. Property. Changed //niezmieniony interfejs klasy { public Model. Widoku() { //model nie jest trwale przechowywany Sumowanie. Kwot model = Sumowanie. Kwot. Czytaj(); Suma = model. Suma; //kopiowanie stanu z do modelu widoku } public string Suma { get; private set; }. . . public void Zapisz() { Sumowanie. Kwot model = new Sumowanie. Kwot(Suma); model. Zapisz(); } }
Przykład aplikacji MVVM Model widoku – alternatywa 2 (czyste zło) public class Model. Widoku : INotify. Property. Changed { private readonly Sumowanie. Kwot model = Sumowanie. Kwot. Czytaj(); public Sumowanie. Kwot Model { get { return model; } } Wiązanie do Model. Suma → model musiałby implementować interfejs INotify. Property. Changed . . . public void Zapisz() { model. Zapisz(); } }
Warstwy wzorca MVVM
Wzorce architektoniczne http: //geekswithblogs. net/dlussier/archive/2009/11/21/136454. aspx