Uniwersytet Mikołaja Kopernika. Wydział Fizyki, Astronomii i Informatyki Stosowanej

Wielkość: px
Rozpocząć pokaz od strony:

Download "Uniwersytet Mikołaja Kopernika. Wydział Fizyki, Astronomii i Informatyki Stosowanej"

Transkrypt

1 XAML i MVVM w WPF Autor: Jacek Matulewski, jacek@fizyka.umk.pl Wersja z 31 stycznia 2015 Materiały dydaktyczne dla uczestników Podyplomowego Studium Programowania i Zastosowań Komputerów, sekcja.net (rocznik 2014/2015, WWW oraz do kursu.net studentów stacjonarnych WFAiIS UMK. Uwaga! Bardzo proszę studentów i słuchaczy studium o nieumieszczanie tego skryptu w Internecie. Materiały są fragmentem pierwszej części książki pt. XAML i MVVM w Visual Studio 2015 przygotowywanej na zamówienie Wydawnictwa Helion. Uniwersytet Mikołaja Kopernika Wydział Fizyki, Astronomii i Informatyki Stosowanej Projekt współfinansowany ze środków Unii Europejskiej w ramach Europejskiego Funduszu Społecznego

2 Spis treści Spis treści... 2 Rozdział 1. Szybkie wprowadzenie do XAML. Wzorzec widoku autonomicznego... 4 Tworzenie projektu... 4 Projektowanie interfejsu... 6 Kilka uwag na temat kodu XAML okna... 8 Zdarzenia... 9 Własności Zapisywanie i odtwarzanie stanu aplikacji Zadanie Rozdział 2. Wzorzec MVVM Rozdział 3. Implementacja modelu i model widoku Model Warstwa dostępu do danych Model widoku Alternatywne rozwiązania Ratujmy widok Zadanie Rozdział 4. Wiązanie danych (data binding) Instancja modelu widoku i kontekst danych Alternatywne rozwiązanie Wiązanie pozycji suwaków i koloru prostokąta Zmiany w code-behind Implementacja interfejsu INotifyPropertyChanged Powiadomienia w alternatywnych modelach widoku Rozdział 5. Konwersja danych w wiązaniu Prosta konwersja typów Konwersja klas Color i SolidColorBrush Multibinding Wiązanie między kontrolkami Zadanie Rozdział 6. Polecenia (commands) Interfejs ICommand Użycie polecenia w przycisku Sprawdzanie możliwości wykonania polecenia Resetowanie stanu po naciśnięciu klawisza Klasa RelayCommand Zdarzenia w MVVM a polecenia... 47

3 Zamykanie okna Zadanie Rozdział 7. Zachowania, własności zależności i własności doczepione Zachowania (behaviors) Własność zależności (dependency property) Własność doczepiona (attached property) i zachowanie doczepione (attached behavior) Zadanie Rozdział 8. Powtórzenie Model Prototyp widoku Model widoku Wiązanie Konwerter Wzorzec MVVM Zadania... 64

4 Rozdział 1. Szybkie wprowadzenie do XAML. Wzorzec widoku autonomicznego Osoby, które dopiero uczą się XAML i WPF, a mają wcześniejsze doświadczenia w programowaniu aplikacji z użyciem biblioteki Windows Forms, mogą mieć pokusę korzystania z cieszącego się złą sławą tzw. wzorca autonomicznego widoku (ang. autonomous view, AV). Jest to wzorzec, w którym cała logika i dane odpowiedzialne za stan aplikacji przechowywane są w klasach widoku, bez żadnej separacji, czyli tak, jak zwykle programuje się właśnie aplikacje Windows Forms, za to do określania działania aplikacji wykorzystywane są bardzo wygodne zdarzenia kontrolek. Brak separacji poszczególnych modułów wprawdzie utrudnia testowanie kodu. W praktyce możliwe jest tylko testowanie funkcjonalne całego produktu. To nie musi być złe rozwiązanie. W tym wzorcu aplikacje tworzy się szybko, szczególnie w początkowej fazie projektu, póki nie dowiadujemy się o poważnych zmianach, które należy wprowadzić. Zresztą nie zawsze warto dbać o rozdzielanie modułów i najlepsze praktyki. Czasem ważne jest, aby aplikacja powstała szybko i zadziałała. Jeżeli na tym kończy się jej cykl życia, to wysiłek włożony w jej czystość, zostałby w pewnym sensie zmarnowany. W tym rozdziale przedstawię przykład tak napisanej aplikacji. W kolejnych będę ją natomiast stopniowo przekształcał w aplikację napisaną zgodnie ze wzorcem MVVM. Ponieważ będzie to w gruncie rzeczy bardzo prosty projekt, obawiam się, że Czytelnik odniesie wrażenie, że użycie wzorca MVVM jest przerostem formy nad treścią. Może i tak będzie w tym przypadku, ale łatwiej uczyć się złożonych rzeczy na prostych przykładach, aby nie przykrywać zasadniczej idei stertą drugorzędnych szczegółów. Pierwsze rozdziały będą dotyczyć XAML w Windows Presentation Foundation (WPF), ale potem pokażę, że to wszystko, czego nauczymy się na przykładzie WPF, będzie także aktualne w XAML używanym w projektach innego typu: Windows Store Apps i Windows Phone Apps. Tworzenie projektu Proponuję zbudować prostą aplikację, w której kontrolować będziemy kolor widocznego w oknie prostokąta za pomocą trzech suwaków. To m.in. pozwoli na zapoznanie się z narzędziami projektowania wizualnego przeznaczonymi dla aplikacji WPF. 1. Uruchamiamy Visual Studio (VS). Ja używam wersji 2015, ale aplikacje WPF można budować już od wersji W menu File wybieramy podmenu New, a następnie polecenie Project. 3. W oknie New Project (rysunek 1.1): a) zaznaczamy ikonę WPF Application, b) w polu edycyjnym Name podajemy nazwę aplikacji KoloryWPF, c) klikamy OK.

5 Rysunek 1.1. Zbiór projektów dostępnych w Visual Studio 2015 Utworzyliśmy projekt o nazwie KoloryWPF, którego pliki domyślnie zostały umieszczone w katalogu [User]\Documents\Visual Studio 2015\Projects\ KoloryWPF\ KoloryWPF. W katalogu projektu znajdziemy dwie pary plików: MainWindow.xaml/MainWindow.xaml.cs oraz App.xaml/App.xaml.cs. Ta pierwsza odpowiada za klasę głównej formy (okna), druga za klasę całej aplikacji. W tej części książki skupimy się przede wszystkim na tej pierwszej. Po utworzeniu projektu, w głównym oknie Visual Studio zobaczymy dwie zakładki o nazwach MainWindow.xaml oraz MainWindow.xaml.cs (rysunek 1.2). Na pierwszej z nich widzimy podgląd formy (oznaczony numerem 1 na rysunku 1.2) oraz edytor kodu XAML (numer 3). Wielkość podglądu formy i wielkość czcionki w edytorze możemy dowolnie skalować korzystając z rolki myszy (należy przytrzymać klawisz Ctrl). Z lewej strony znajduje się podokno Toolbox zawierające zbiór kontrolek WPF (numer 4 na rysunku). Z prawej widzimy podokno zawierające listę wszystkich plików rozwiązania i znajdującego się w nim jednego projektu. Pod nim widoczne jest podokno własności (Properties, numer 5). Rysunek 1.2. Widok projektowania aplikacji

6 Projektowanie interfejsu Interfejs aplikacji będzie składał się z prostokąta (element Rectangle) i trzech suwaków (elementy Slider). Możemy zbudować go dwoma sposobami: albo używając podglądu formy, umieszczając na nim elementy widoczne w panelu Toolbox, albo wprost pisząc kod XAML w edytorze. Te dwa sposoby wcale się wzajemnie nie wykluczają. W domyślnym widoku Visual Studio widoczny jest zarówno podgląd formy, jak i edytor XAML. Wszelkie zmiany wprowadzone w jednym z okien są natychmiast widoczne w drugim. Zacznijmy od projektowania z użyciem podglądu formy. Na koniec zobaczymy do jakiego kodu XAML to doprowadziło. W kolejnych aplikacjach, będę już tylko podawał ostateczny kod XAML, który można umieścić w pliku MainWindows.xaml. Po nabraniu wprawy, bezpośrednia edycja kodu XAML okazuje się szybszym rozwiązaniem. 1. W podoknie Toolbox, w grupie All WPF Controls odnajdujemy komponent Rectangle (prostokąt) i zaznaczamy go. 2. Następnie przeciągamy go na podgląd formy. 3. Teraz możemy dopasować jego rozmiar. Proponuję zwiększyć go tak, żeby między jego krawędzią, a krawędzią okna pojawił się różowy pasek sygnalizujący zalecaną odległość kontrolki od krawędzi formy (rysunek 1.3). Zróbmy tak z lewą, prawą i górną krawędzią prostokąta. Rysunek 1.3. Różowy pasek sygnalizuje, że odległość kontrolki od krawędzi ekranu jest optymalna 4. Następnie postępując podobnie, umieśćmy na formie trzy kontrolki Slider (suwaki) jedną pod drugą (rysunek 1.4). Ich lewą i prawą krawędź ustawmy w odpowiedniej odległości od brzegów okna. Następnie położenie najniższej dopasujmy do dolnej krawędzi okna. Wyższą umieśćmy nad najniższą. Również i w tym przypadku optymalna odległość między dwoma kontrolkami będzie sygnalizowana różowym paskiem. Będzie ona nieco mniejsza niż zalecana odległość od krawędzi okna. Podobnie ustawiamy położenie trzeciego suwaka. 5. Na koniec ustawiamy wysokość prostokąta w taki sposób, żeby i jego odległość była optymalna.

7 Rysunek 1.4. Projekt formy Powyższe czynności doprowadziły do kodu XAML, który widoczny jest na listingu 1.1. Kontrolkom widocznym na rysunku 1.4 odpowiadają elementy zawarte w elemencie Grid (siatka). Siatka określa sposób ułożenia kontrolek kontrolki można w niej ustawiać w kolumnach i rzędach. My tego w tej chwili nie wykorzystujemy określając bezwzględnie ich odległości od lewej i górnej krawędzi całego okna (atrybuty Margin kontrolek) siatka ma bowiem tylko jedną komórkę. Każda z kontrolek zawiera atrybuty VerticalAlignment i HorizontalAlignment. W tej chwili ustawione są one odpowiednio na Left i Top, co oznacza, że położenie kontrolek wyznaczane przez margines ustalane jest względem lewej i górnej krawędzi. Natomiast ich szerokość i wysokość wyznaczają atrybuty Width i Height, a nie odległość od brzegów okna. Dlatego dwie ostatnie wartości a atrybucie Margin są ignorowane i w tej chwili równe są po prostu zeru. Listing 1.1. Kod XAML opisujący wygląd formy ZWERYFIKOWAĆ W VS2015 (Czy jest local i d?) <Window x:class="kolorywpf.mainwindow" <Grid> xmlns=" xmlns:x=" xmlns:d=" xmlns:mc=" xmlns:local="clr-namespace:kolorywpf" mc:ignorable="d" Title="MainWindow" Height="350" Width="525"> <Rectangle Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="221" Margin="10,10,0,0" Stroke="Black" VerticalAlignment="Top" Width="498"/> <Slider x:name="slider" HorizontalAlignment="Left" Margin="10,236,0,0" VerticalAlignment="Top" Width="498"/> <Slider x:name="slider1" HorizontalAlignment="Left" Margin="10,263,0,0" VerticalAlignment="Top" Width="498"/> <Slider x:name="slider2" HorizontalAlignment="Left" Margin="10,290,0,0" VerticalAlignment="Top" Width="498"/> </Grid> </Window> Taki sposób wyznaczania geometrii kontrolek ma pewną wadę. Uruchommy aplikację (klawisz F5), aby się o tym przekonać. Każda zmiana rozmiaru okna powoduje, że rozmiary kontrolek przestają do niego pasować. Aby temu zaradzić zamiast ustalać ich bezwzględny rozmiar, zakotwiczmy je do brzegów okna. Po zamknięciu aplikacji przejdźmy do widoku projektowania i w przypadku wszystkich kontrolek po kolei, zmieńmy ustawienie określające ich zaczepienie do prawego brzegu formy. To samo zróbmy w przypadku dolnej krawędzi. Prostokąt będzie w ten sposób zaczepiony do wszystkich czterech krawędzi i dlatego wraz ze

8 zmianą rozmiaru okna, zmieniać się będzie także zarówno jego szerokość, jak i wysokość. Natomiast wyłączmy domyślnie ustawione zaczepienie suwaków do górnej krawędzi. Te czynności spowodują zmiany w kodzie XAML widoczne na listingu 1.2. Zniknęły atrybuty VerticalAlignment, a w zamian w atrybucie Margin pojawiły się odległości od prawej i dolnej krawędzi. Po uruchomieniu aplikacji przekonamy się, że zmiana rozmiaru okna nie psuje już interfejsu rozmiary kontrolek będą dopasowywane do rozmiaru okna. Rysunek 1.6. Z lewej ikona mówiąca, że kontrolka nie jest zaczepiona do brzegu okna. Z prawej zakotwiczenie jest włączone. Listing 1.2. Szarym tłem zostały zaznaczone zmiany względem listingu 1.1 <Grid> </Grid> <Rectangle Fill="#FFF4F4F5" Margin="10,10,10.4,91.4" Stroke="Black"/> <Slider x:name="slider" Margin="10,0,10.4,64.4" Height="22" VerticalAlignment="Bottom"/> <Slider x:name="slider1" Margin="10,0,10.4,37.4" Height="22" VerticalAlignment="Bottom"/> <Slider x:name="slider2" Margin="10,0,10.4,10.4" Height="22" VerticalAlignment="Bottom"/> Kilka uwag na temat kodu XAML okna Co jeszcze możemy zobaczyć w pliku MainWindows.xaml? Cały kod widoczny jest na listingu 1.1 ze zmianami z listingu 1.2. Elementem nadrzędnym jest element Window reprezentujący okno aplikacji. W nim zagnieżdżony jest element Grid odpowiadający za organizację kontrolek na formie. W nim są pozostałe kontrolki: prostokąt i trzy suwaki. Zagnieżdżenie elementów oznacza, że zewnętrzna kontrolka jest pojemnikiem, w którym znajdują się kontrolki reprezentowane przez wewnętrzne elementy 1. Warto zwrócić uwagę na atrybuty elementu Window. Atrybut x:class tworzy pomost między elementem Window, określającym opisywane w pliku okno, a klasą C# o nazwie MainWindow w przestrzeni nazw KoloryWPF, której jeszcze nie edytowaliśmy, a która znajduje się w pliku MainWindow.xaml.cs. Atrybut xmlns (od XML namespace) określa domyślną przestrzeń nazw używaną w bieżącym elemencie XAML odpowiada instrukcji using w kodzie C#. Z kodu wynika, że dostępne jest pięć przestrzeni nazw. Pierwsza jest przestrzenią domyślną, zadeklarowana jako Zawiera definicje większości elementów XAML m.in. Rectangle i Slider. Drugą ważną przestrzenią jest ta dostępna pod nazwą x. To w tej przestrzeni jest np. domyślnie używany przed edytor atrybut Name (dlatego w kodzie widzimy x:name). Bardzo ważną jest też przestrzeń nazw local. Pod tą nazwą widoczna jest przestrzeń nazw KoloryWPF, w której jest m.in. klasa okna. Znaczenia pozostałych atrybutów elementu Window są bardziej oczywiste: Title określa etykietę okna widoczną na pasku tytułu, a Height i Width jego rozmiary. Możemy je swobodnie zmieniać, przypisując im nowe wartości, np. 1 Odpowiada to mechanizmowi rodziców w Windows Forms. W kontrolce-pojemniku, np. panelu, pełniącej rolę rodzica, może znajdować się kilka innych kontrolek (wszystkie dodawane są do własności Controls pojemnika). Natomiast każda kontrolka-dziecko ma zapisaną referencję do swojego rodzica we własności Parent. W WPF ten mechanizm został zastąpiony przez hierarchię pliku XAML.

9 Title="Kolory WPF" Height="480" Width="640"> Element Window ma jeszcze jeden atrybut, którego wprawdzie nie będziemy używać, ale warto o nim wspomnieć słowo: mc:ignorable="d" Jest to atrybut zdefiniowany w przestrzeni nazw mc, czyli Wskazuje on prefiks atrybutów, które mają być ignorowane przed kompilator, ale używane są w trakcie projektowania. Wykorzystuje to środowisko projektowe Expression Blend, w którym można otworzyć każdy projekt interfejsu napisany w XAML. Zdarzenia Załóżmy, że etap budowania interfejsu aplikacji jest już ukończony. Zajmijmy się teraz jej dynamiką. Chcemy, aby suwaki umożliwiały ustalanie koloru prostokąta, a konkretnie żeby możliwe było ustawianie z ich pomocą wartości trzech składowych RGB koloru. Proponuję zacząć od rzeczy z pozoru może mało istotnej, a jednak bardzo potrzebnej nazw kontrolek. Zmieńmy nazwy suwaków tak, żeby odpowiadały składowym koloru, z którymi będą związane. Nazwijmy je sliderr, sliderg i sliderb. Musimy też nadać nazwę prostokątowi, który w tej chwili w ogóle jej nie ma. Bez tego nie będziemy mogli modyfikować jego własności z poziomu kodu C#. Aby nadać nazwę elementowi XAML, trzeba ustalić nowe wartości atrybutów x:name tych czterech kontrolek: <Rectangle x:name="rectangle" Fill="#FFF4F4F5" Margin=... /> <Slider x:name="sliderr" Margin=... /> <Slider x:name="sliderg" Margin=... /> <Slider x:name="sliderb" Margin=... /> Kolejnym krokiem będzie związanie z kontrolką metody zdarzeniowej reagującej na zmianę pozycji suwaka. Ta z pozoru naturalna operacja niepostrzeżenie kieruje nas do wzorca, który odbiega od obecnych standardów w WPF, WinRT, czy Silverlight do wspomnianego na początku rozdziału wzorca autonomicznego widoku. Kod metody zdarzeniowej umieszczony zostanie w klasie KoloryWPF.MainWindow związanej z oknem, czyli w klasie widoku. Często używa się na jej określenie sformułowania code-behind, czyli kod stojący za widokiem. Określenie to ma obecnie zdecydowanie negatywny wydźwięk. Zgodnie z najbardziej rygorystyczną egzegezą wzorca MVVM, projekt aplikacji WPF w ogóle nie powinien zawierać code-behind. A to oznacza mniej więcej tyle, że powinniśmy zrezygnować z używania mechanizmu zdarzeń. Póki co jednak ze zdarzenia skorzystamy. Dwukrotne kliknięcie najwyższego suwaka na podglądzie formy tworzy domyślną metodę zdarzeniową i przenosi nas do edytora kodu C#, ustawiając kursor w nowoutworzonej metodzie klasy MainWindow (plik MainWindow.xaml.cs) Metoda zostanie nazwana sliderr_valuechanged (łączy nazwę kontrolki i nazwę zdarzenia). Jeżeli wrócimy do kodu XAML, zobaczymy, że jednocześnie do elementu Slider dodany został atrybut ValueChanged, którego wartość ustalona zostaje na nazwę metody slider1_valuechanged: <Slider x:name="sliderr" Margin="10,0,10.4,64.4" Height="22" VerticalAlignment="Bottom" ValueChanged="sliderR_ValueChanged"/> Do nowej metody wstawmy polecenie zmieniające kolor prostokąta na zielonożółty (listing 1.3). To oczywiście tymczasowe rozwiązanie, które ma nam pozwolić na sprawdzenie czy mechanizm zdarzeń w ogóle działa. Listing 1.3. Zmiana pozycji najwyższego suwaka spowoduje zmianę koloru prostokąta na zielonożółty. private void sliderr_valuechanged(object sender, RoutedPropertyChangedEventArgs<double> e) rectangle.fill = Brushes.GreenYellow;

10 Wykorzystany przez nas prostokąt można pokolorować, zmieniając zarówno jego brzeg (Stroke), jak i wypełnienie (Fill). My zmieniamy wypełnienie. Przekonajmy się o tym, kompilując projekt, uruchamiając aplikację (F5) i zmieniając pozycję pierwszego suwaka (rysunek 1.7). Rozwiązanie, którego użyliśmy nie jest najlepsze, ale jak wspomniałem, jest tymczasowe. Rysunek 1.7. Zmiana koloru po przesunięciu suwaka Zwiążmy teraz stworzoną przed chwilą metodę zdarzeniową z dwoma pozostałymi suwakami. Możemy to zrobić używając zakładki Events podokna Properties, podobnie jak w Windows Forms, ale w praktyce wygodniej wiązanie to wykonać edytując kod XAML. Ogromną zaletą Visual Studio jest technologia podpowiadania kodu IntelliSence, która działa nie tylko w przypadku kodu C#, ale również XAML. Dodajmy zatem do drugiego i trzeciego elementu (do tych, w których atrybuty x:name równe są sliderg i sliderb) atrybuty ValueChanged. Za nimi umieśćmy operator przypisania = i cudzysłów ". Wówczas pojawi się okienko z listą metod, których sygnatury pasują do sygnatury zdarzenia, a także pozycję <New Event Handler> (rysunek 1.8). Wybranie tej ostatniej spowodowałoby utworzenie nowej metody zdarzeniowej w pliku MainWindow.xaml.cs. My jednak wybierzmy z tej listy istniejącą metodę slider1_valuechanged. Jeżeli zrobimy to dla obu suwaków, to po uruchomieniu aplikacji, zmiana pozycji każdego z nich spowoduje zmianę koloru prostokąta. Rysunek 1.8. Edycja kodu XAML z pomocą IntelliSence

11 Aby metoda uzyskała zaplanowaną przez nas funkcjonalność musi zmieniać kolor prostokąta zgodnie z pozycjami suwaków. Musimy zatem odczytać ich wartości Value, ustalić na ich podstawie kolor i przypisać go prostokątowi. Wartość Value jest typu double, podobnie jak wiele własności odpowiadających za pozycję i wygląd w WPF. Domyślnie przyjmuje wartości z zakresu od 0 do 10. Zmieńmy górną granicę zakresu na 255, tak aby umożliwiała swobodne dobieranie wartości liczb byte bez skalowania (za to z koniecznym rzutowaniem). Możemy to zrobić albo zmieniając wartość własności Maximum z okna Properties, albo edytując kod XAML. Zachęcam do tego drugiego rozwiązania warto już teraz przyzwyczajać się do edytowania kodu XAML. Zmiany widoczne są na listingu 1.4. Przy okazji warto połamać znaczniki tak, aby widoczne były w całości w kodzie edytora. Następnie zmieńmy metodę zdarzeniową zgodnie ze wzorem z listingu 1.5. Listing 1.4. Zmieniona wartość własności Maximum z poziomu kodu XAML <Slider x:name="sliderr" Margin="10,0,10.4,64.4" Height="22" VerticalAlignment="Bottom" Maximum="255" ValueChanged="sliderR_ValueChanged"/> <Slider x:name="sliderg" Margin="10,0,10.4,37.4" Height="22" VerticalAlignment="Bottom" Maximum="255" ValueChanged="sliderR_ValueChanged"/> <Slider x:name="sliderb" Margin="10,0,10.4,10.4" Height="22" VerticalAlignment="Bottom" Maximum="255" ValueChanged="sliderR_ValueChanged"/> Listing 1.5. Kolor prostokąta zależy od pozycji suwaków private void sliderr_valuechanged(object sender, RoutedPropertyChangedEventArgs<double> e) Color kolor = Color.FromRgb( (byte)(sliderr.value), (byte)(sliderg.value), (byte)(sliderb.value)); rectangle.fill = new SolidColorBrush(kolor); Aby zsynchronizować początkowy kolor prostokąta z pozycją suwaków, po uruchomieniu programu wywołajmy metodę slider1_valuechanged z konstruktora klasy MainWindow (listing 1.6). Listing 1.6. Inicjowanie koloru prostokąta po uruchomieniu aplikacji public MainWindow() InitializeComponent(); sliderr_valuechanged(null, null); Metoda z listingu 1.5 może nie spodobać się każdemu programiście C++, który widziałby w niej źródło wycieku pamięci na wielką skalę. Także w C# nie jest to najlepsze rozwiązanie. Tworzenie nowego obiektu typu SolidColorBrush (typ referencyjny) przy każdym poruszeniu suwakiem jest bowiem sporym wyzwaniem dla garbate collectora, który musi zwalniać z pamięci poprzednio używane obiekty. Jednak proste testy przeprowadzone za pomocą Menedżera zadań pokazują, że z tym zadaniem odśmiecacz platformy.net radzi sobie bardzo dobrze. Można oczywiście zmodyfikować kod tak, aby tworzyć jedną trwałą instancję klasy SolidColorBrush i tylko zmieniać jej własność Color. To powoduje, że kod stanie się nieco mniej przejrzysty, ale za to będzie bardziej optymalny. W tym celu do konstruktora klasy MainWindow należy przenieść polecenie tworzące obiekt: rectangle.fill = new SolidColorBrush(Colors.Black);

12 W metodzie slider1_valuechanged należy natomiast zastąpić obecne polecenie, przez polecenie modyfikujące własność Color (listing 1.7). Listing 1.7. Obiekt Color jest lżejszy niż SolidColorBrush, więc teoretycznie zmiana powinna poprawić wydajność private void sliderr_valuechanged(object sender, RoutedPropertyChangedEventArgs<double> e) Color kolor = Color.FromRgb( (byte)(sliderr.value), (byte)(sliderg.value), (byte)(sliderb.value)); (rectangle.fill as SolidColorBrush).Color = kolor; W zasadzie to inicjowanie własności Fill prostokąta wcale nie jest konieczne. W końcu instancja klasy SolidColorBrush powstaje, gdy interpretowany jest kod XAML, a konkretnie atrybut Fill znacznika Rectangle. Przypisujemy mu tam wartość #FFF4F4F5. Aby jeszcze raz przećwiczyć tworzenie metod zdarzeniowych, tym razem korzystając tylko z kodu XAML, stwórzmy metodę zamykającą okno, a tym samym i aplikację, po wciśnięciu klawisza Escape. 1. Przechodzimy do widoku projektowania (zakładka MainWindow.xaml). 2. Korzystając z mechanizmu uzupełniania kodu, do elementu Window (w kodzie XAML) dodajemy atrybut KeyDown, a następnie, nadal korzystając z mechanizmu uzupełniania kodu, przypisujemy mu pozycję <New Event Handler> z okienka podpowiedzi. Wartość atrybutu zostanie ustalona na Window_KeyDown, a w pliku z kodem C# pojawi się metoda o tej nazwie. 3. Kliknijmy nowy atrybut w kodzie XAML i naciśnijmy klawisz F12. Przeniesie nas to do definicji utworzonej przed chwilą metody. 4. Umieszczamy w niej polecenie z listingu 1.8. Listing 1.8. Reakcja na naciśnięcie klawisza Escape. private void Window_KeyDown(object sender, KeyEventArgs e) if (e.key == Key.Escape) this.close(); Własności Zakładam, że Czytelnik zna C# przynajmniej na podstawowym poziomie. Nie będę wobec tego opisywał jego konstrukcji. Jednak zanim przejdziemy do kolejnych rozdziałów warto przypomnieć sobie choćby to, jak definiuje się własności. Dobrym do tego pretekstem jest dość zawiły sposób dostępu do koloru prostokąta. Aby go uprościć, zdefiniujemy własność widoczną na listingu 1.9. Listing 1.9. Tworzenie własności KolorProstokąta private Color KolorProstokąta get set return (rectangle.fill as SolidColorBrush).Color;

13 (rectangle.fill as SolidColorBrush).Color = value; Definicja własności może zawierać dwie sekcje (musi zawierać przynajmniej jedną z nich). Sekcja get powinna zwracać obiekt typu Color zadeklarowany w sygnaturze własności. Natomiast sekcja set otrzymuje taki obiekt w postaci predefiniowanej zmiennej value. Zwykle własności towarzyszy prywatne pole, które przechowuje wartość. W naszym przypadku własność jest tylko pomostem do własności Color przechowywanej w obiekcie rectangle.fill, o którym zakładamy, że jest typu SolidColorBrush (takiego typu obiekt tworzymy w konstruktorze). Dzięki tej własności ostatnie polecenie w metodzie sliderr_valuechanged z listingu 1.7 może być zamienione po prostu na KolorProstokąta = kolor; (por. listing 1.10). Listing Zmieniona metoda zdarzeniowa private void sliderr_valuechanged(object sender, RoutedPropertyChangedEventArgs<double> e) Color kolor = Color.FromRgb( (byte)(sliderr.value), (byte)(sliderg.value), (byte)(sliderb.value)); KolorProstokąta = kolor; Zapisywanie i odtwarzanie stanu aplikacji Zachowaniem, którego oczekujemy od nowoczesnych aplikacji, jest odtwarzanie stanu aplikacji po jej zamknięciu i ponownym otwarciu. W przypadku tak prostej aplikacji, jak nasza, w której stan aplikacji to w istocie trzy wartości typu byte, do zapisania jej stanu w zupełności wystarczy mechanizm ustawień aplikacji. Należy go wcześniej odpowiednio skonfigurować. 1. Z menu Project wybieramy KoloryWPF Properties... i przechodzimy na zakładkę Settings (rysunek 1.9). 2. Następnie do tabeli widocznej na tej zakładce dodajemy trzy wiersze opisujące trzy wartości, które będą mogły być przechowywane w ustawieniach. Nazwijmy te wartości R, G i B i ustalmy ich typ na byte. Rysunek 1.9. Edycja ustawień aplikacji

14 Po tej zmianie, warto się przyjrzeć plikowi ustawień app.config, co można łatwo zrobić, klikając go dwukrotnie w podoknie Solution Explorer. Jest to plik zapisany w formacie XML. Jego elementem nadrzędnym jest configuration. W nim znajduje się usersettings, a w nim zdefiniowane są elementy dla poszczególnych ustawień. Każdy z takich elementów nazywa się setting. Nazwa ustawienia przechowywana jest w atrybucie name, a jej wartość domyślna w podelemencie value. Gdybyśmy użyli zakresu aplikacji zamiast zakresu użytkownika (kolumna Scope w tabeli widocznej na rysunku 9.18), to w pliku app.config oprócz elementu usersettings powstałby także element applicationsettings, w którym byłyby przechowywane ustawienia o tym zakresie. Z naszego punktu widzenia, ustawienia tego drugiego rodzaju różnią się one przede wszystkim tym, że nie mogą być zmieniane w trakcie działania programu. Jeżeli przyjrzymy się dokładnie plikowi app.config, zauważymy, że nie zawiera kompletnych informacji o ustawieniach. Przede wszystkim nie ma w nim zapisanych typów zmiennych. Są one bowiem przechowywane w pliku Settings.settings z podkatalogu Properties i po kompilacji znajdą się w pliku wykonywalnym.exe. Plik Settings.settings nie jest niczym więcej, jak wiernie zapisaną tabelą widoczną na rysunku 1.9. Ponadto w podkatalogu Properties znajdziemy także plik Settings.Designer.cs z automatycznie zdefiniowaną klasą Settings odwzorowującą ustawienia zdefiniowane w tabeli z rysunku 1.9. Dla wygody użytkownika tworzona jest jej instancja dostępna jako Properties.Settings.Default. Po kompilacji plik app.config kopiowany jest do katalogu bin/debug, gdzie znajdziemy go pod nazwą UstawieniaAplikacji.exe.config. Dzieje się to niezależnie od ustawienia własności Copy to Output Directory, która w tym wypadku powinna pozostać ustawiona na Do not copy. Tak naprawdę plik UstawieniaAplikacji.exe.config to nie jest miejsce, w którym przechowywane będą ustawienia o zakresie użytkownika. Wyjaśnię to jednak dopiero za chwilę. Pomimo, że ustawienia są łatwo dostępne poprzez wspomniany obiekt Properties.Settings.Default, to żeby ich odczyt i zapis jeszcze bardziej uprościć, przygotujemy dwie wykonujące te zadania metody statyczne umieszczone w osobnej klasie statycznej. 1. Z menu Project wybieramy polecenie Add Class... i dodajemy do projektu klasę o nazwie Ustawienia. Jej kod modyfikujemy zgodnie ze wzorem z listingu Proszę zwrócić uwagę na dodaną przestrzeń nazw, w której zdefiniowana jest klasa Color używana w WPF. Listing Odczyt i zapis danych z ustawień aplikacji using System.Windows.Media; namespace KoloryWPF static class Ustawienia public static Color Czytaj() Properties.Settings ustawienia = Properties.Settings.Default; Color kolor = new Color() A = 255, R = ustawienia.r, G = ustawienia.g, B = ustawienia.b ; return kolor; public static void Zapisz(Color kolor) Properties.Settings ustawienia = Properties.Settings.Default;

15 ustawienia.r = kolor.r; ustawienia.g = kolor.g; ustawienia.b = kolor.b; ustawienia.save(); 2. Korzystając z metody Ustawienia.Zapisz zapiszmy kolor prostokąta w momencie zamykania okna, a tym samym zamykania całej aplikacji. Użyjemy do tego zdarzenia Closed okna. Postępując podobnie, jak w przypadku zdarzenia Window.KeyDown, stwórzmy metodę związaną ze zdarzeniem Window.Closed i umieśćmy w niej polecenia widoczne na listingu Listing Zapisywanie ustawień tuż przed zamknięciem aplikacji private void Window_Closed(object sender, EventArgs e) Ustawienia.Zapisz(KolorProstokąta); 3. Trochę więcej zamieszania będzie z odczytywaniem ustawień po uruchomieniu aplikacji. Łatwo możemy zmienić kolor prostokąta tworząc pędzel. Jednak dodatkowo musimy także ustalić położenia suwaków. A to oznacza, że trzy razy wywoływana będzie metoda zdarzeniowa związana ze zdarzeniem zmieniania ich wartości. Można tego uniknąć definiując zmienną logiczną, tzw. flagę, którą podnosilibyśmy na czas wykonywania kodu konstruktora, a która blokowałaby wykonywanie zawartości metody zdarzeniowej, ale chyba nie warto. I tak docelowo cały kod z klasy MainWindow zostanie usunięty. Listing Zmodyfikowany konstruktor klasy MainWindow public MainWindow() InitializeComponent(); Color kolor = Ustawienia.Czytaj(); rectangle.fill = new SolidColorBrush(kolor); sliderr.value = kolor.r; sliderg.value = kolor.g; sliderb.value = kolor.b; Ponieważ wszystkie ustawienia aplikacji, które zapisujemy w programie, należą do ustawień użytkownika, wykonanie metody Ustawienia.Zapisz spowoduje, że platforma.net utworzy dla nich plik w katalogu domowym użytkownika (np. C:\Users\Jacek\ lub C:\Documents and Settings\Jacek), a dokładniej w jego podkatalogu AppData\Local\ (względnie Ustawienia lokalne\dane aplikacji). Powstanie tam katalog o nazwie aplikacji, z podkatalogiem oznaczającym konkretny plik wykonywalny i jeszcze jednym podkatalogiem zawierającym numer wersji aplikacji. Dopiero w tym miejscu powstanie plik XML o nazwie user.config. Plik user.config zawiera sekcję usersettings, czyli ustawienia aplikacji z zakresu użytkownika. Taki sam zbiór ustawień znajdziemy w pliku KoloryWPF.exe.config, który umieszczony jest w katalogu aplikacji i powinien być z nią rozpowszechniany. Ustawienia z pliku user.config są jednak dynamicznie modyfikowane przez metodę z listingu 1.11, podczas gdy plik KoloryWPF.exe.config przechowuje tylko ustawienia domyślne, jakie wprowadziliśmy w projekcie. Do pliku user.config nie są natomiast zapisywane ustawienia z zakresu aplikacji (element applicationsettings) pozostają one dla aplikacji ustawieniami tylko do odczytu. Zadanie 4. W ustawieniach zastąp trzy wartości R, G i B typu byte, jedną własnością typu System.Media.Color.

16 Rozdział 2. Wzorzec MVVM Opisany w poprzednim rozdziale projekt będziemy teraz krok po kroku modyfikować tak, żeby jego architektura stała się zgodna ze wzorcem MVVM. We wzorcu tym zakłada się obecność trzech warstw: modelu, modelu widoku i widoku (rysunek 2.1). W najprostszym przypadku, takim jak w naszej aplikacji, poszczególne warstwy mogą składać się tylko z jednej klasy, ale zwykle jest ich wiele. Rysunek 2.1. Warstwy aplikacji we wzorcu MVVM. Z lewej polska, a z prawej angielska terminologia Warstwa modelu jest najbardziej intuicyjna z grubsza odpowiada modelom w innych wzorcach projektowych, chociażby w klasycznej dwuwarstwowej architekturze model-widok lub we wzorcach MVC i MVP. Dobrym pomysłem jest tworzenie tej warstwy w metodologii projektowania domenowego (ang. domain-driven design, DDD). W wielkim uproszczeniu oznacza to, że projektujemy zbiór klas składających się na model razem z ekspertem w dziedzinie, której program ma dotyczyć. Często jest to klient lub osoba przez niego wskazana. Wówczas należy uważnie słuchać słownictwa, jakiego ów ekspert używa, bo często stosowane przez niego rzeczowniki są dobrymi kandydatami na nazwy podstawowych klas modelu. Z kolei czasowniki towarzyszące tym rzeczownikom będą prawdopodobnie nazwami kluczowych metod. To oczywiście trywializacja, ale nie tak daleka od idei DDD. Modele domenowe powinny być możliwie proste i lekkie. Nie powinny korzystać z żadnych konkretnych mechanizmów platformy.net najlepiej, gdyby jedyną przestrzenią nazw używaną była System. W tym podejściu klasy modelu powinny stanowić tylko proste nośniki danych przekazywanych z bazy danych lub innego źródła danych do wyższych warstw aplikacji. Klasy modelu nie mogą, i to jest bardzo ważne, znać żadnych szczegółów dotyczących owych wyższych warstw powinny być całkowicie autonomiczne. W takim podejściu klasy modelu stają się bardzo proste, a tym samym łatwe do testowania. Klarowne są też relacje między nimi. Kluczowy w projektowaniu warstwy modelu jest podział odpowiedzialności należy jasno ustalić za co odpowiedzialna jest która klasa modelu. Część odpowiedzialności może, lub nawet powinna, być wydzielona do osobnych modułów w warstwie modelu. Za zapis danych można uczynić odpowiedzialnym pod-warstwę dostępu do danych (ang. data access layer, DAL), która np. w postaci klasy statycznej przyjmuje instancje klas domenowych i zapisuje ich stan (podobnie, jak klasa Ustawienia z poprzedniego rozdziału). Podobnie logika modelu może być wydzielona do osobnego modułu tzw. logiki biznesowej (ang. buissness logic layer, BLL), która operuje na instancjach domenowych klas modelu. Widok odpowiedzialny jest za kontakt z użytkownikiem. W WPF, a także w aplikacjach Windows Phone i WinRT, widokiem jest kod XAML opisujący graficzny interfejs użytkownika (ang. graphical user interface, GUI). Z widokiem związana jest klasa okna, w której w poprzednim rozdziale umieszczaliśmy metody zdarzeniowe. Tworzy ona tak zwany kod zaplecza widoku, czyli code-behind. Zgodnie z zaleceniami wzorca MVVM kod ten powinien być ograniczony do minimum, a najlepiej żeby go w ogóle nie było. W tym sensie wzorzec MVVM całkowicie odwraca wzorzec AV. Głównym powodem unikania kodu C# w warstwie widoku, a przynajmniej w klasie okna, jest to, że kod ten, jako silnie związany z kontrolkami, jest trudny do przetestowania. Ponadto zanurzenie logiki prezentacyjnej w widoku znacząco utrudnia współpracę między

17 projektantami interfejsu tworzącymi widok, a programistami odpowiedzialnymi za niższe warstwy aplikacji. Zmniejsza też elastyczność projektu, utrudniając tym samym jego zmiany. Model widoku jest abstrakcją widoku. Jeżeli możemy sobie wyobrazić kilka wariantów graficznego interfejsu naszej aplikacji, dla różnych środowisk i platform, to model widoku w tych wszystkich przypadkach powinien pozostawać taki sam. Analogicznie, jak możemy sobie wyobrazić różne stoły, różnej wielkości i kształtu, z różną liczbą nóg, tak projektów widoku może być bardzo wiele. Model widoku musi być jak definicja stołu, jego zapisana idea powinien zawierać tylko to, co konieczne do określenia czym stół naprawdę jest. W przypadku stołu byłoby to coś na kształt: miejsce, przy którym można usiąść i coś położyć. W przypadku modelu widoku warto wykonać wysiłek, żeby doprowadzić kod do tego samego poziomu abstrakcji. Z powyższych rozważań wynika, że najlepszym sprawdzianem poprawności modelu widoku są zmiany wprowadzane w widoku. Tych w trakcie rozwijania projektu zwykle nie brakuje. Jeżeli model widoku jest dobrze zaprojektowany, takie zmiany widoku powinny się obyć bez jego modyfikacji. Ale to oczywiście raczej cel, do którego dążymy, niż twarde wymaganie. które stawiamy osobie projektującej model widoku. Funkcją modelu widoku jest udostępnienie widokowi instancji klas z warstwy modelu (na rysunku 2.1 odpowiada to ruchowi do góry) oraz zmienianie stanu tych instancji w wyniku działań użytkownika wykrytych w warstwie widoku (ruch w dół). W tym drugim przypadku, model widoku odpowiedzialny jest m.in. za weryfikację przekazywanych danych. Model widoku pełni więc w pewnym sensie rolę pośrednika między modelem i widokiem, a jednocześnie adaptatora przekazywanych danych. Owo pośredniczenie najczęściej odbywa w taki sposób, że obiekty modelu są prywatnymi polami modelu widoku. Model widoku udostępnia je lub ich części w swoich własnościach. Model widoku jest wobec tego świadomy warstwy modelu. Nie powinien być natomiast świadomy warstwy widoku. To widok powinien być świadom modelu widoku. Połączenie między modelem widoku a widokiem jest bardzo luźne. Oparte jest nie na odwołaniach w kodzie C#, a na wiązaniach danych umieszczonych w kodzie XAML. To luźne wiązanie ułatwia niezależną pracę nad widokiem i modelem widoku i znakomicie zwiększa łatwość wprowadzania wszelkich zmian w poszczególnych warstwach, do całkowitego ich przebudowywania włącznie. Ta druga zaleta jest szczególnie warta docenienia, choć jest ona w większym lub mniejszym stopniu zaletą wszystkich wzorców z wyraźnie rozdzielonymi warstwami (modułami). W modelu widoku zapisana jest cała logika prezentacyjna określająca procedury kontaktu z użytkownikiem z uwzględnieniem weryfikacji danych. Mimo tego pozostaje łatwa do testowania. Nie ma w niej bowiem odwołań do kontrolek, ani założonej bezpośredniej interakcji z użytkownikiem. Doskonale zdaję sobie sprawę, że dla osób, które nie miały jeszcze kontaktu ze wzorcem MVVM, większość powyższych zdań o modelu widoku jest trudna do zrozumienia. Zadaniem kolejnych rozdziałów będzie ich wyjaśnienie na konkretnym przykładzie. Po ich przeczytaniu warto wrócić do tego rozdziału i przeczytać go jeszcze raz w całości lub przynajmniej część dotyczącą modelu widoku. To powinno pomóc poukładać sobie wiedzę zdobytą w kolejnych rozdziałach z pierwszej części książki. W przypadku aplikacji KoloryWPF modelem może być prosta klasa opisująca kolor, zawierająca tylko trzy składowe typu byte. Odpowiedzialność za zapis stanu modelu pozostawimy osobnej klasie statycznej należącej do warstwy modelu. Prostota naszej aplikacji spowoduje, że model widoku będzie z początku równie prosty i w istocie bardzo podobny do samego modelu. Z czasem dodamy do niego jednak elementy charakterystyczne dla klas modelu widoku m.in. polecenia i mechanizm powiadomień. Ponieważ podstawowym celem aplikacji jest możliwość kontrolowania trzech składowych koloru, model widoku musi udostępniać własności reprezentujące te składowe. Oprócz tego wyposażymy go w metodę, które potem przekształcimy w tzw. polecenie, umożliwiające zapis stanu aplikacji (czyli de facto stanu modelu). To nie jest oczywiście jedyna architektura, jaką można sobie wyobrazić dla tej aplikacji. Dobrym modelem mogłaby być przecież klasa Properties.Settings stworzona przez Visual Studio w momencie określania ustawień aplikacji. Przy takim założeniu naszym jedynym zadaniem pozostaje napisanie modelu widoku, który tę klasę udostępniłby widokowi. Można również rozważyć klasę System.Windows.Media.Color, jako klasę modelu, ale nie uważam żeby korzystanie z klas przeznaczonych do budowania interfejsu było dobrym pomysłem. Pozostaniemy przy rozwiązaniu kanonicznym, ale pamiętając, że wzorzec MVVM pozwala na pewne wariacje. Ostrzegałem już kilka razy, że aplikacja, którą od tego momentu będziemy przebudowywać jest bardzo prosta. To jednak jest moim zdaniem jej zaletą. Brak szczegółów związanych z bardziej skomplikowanym projektem pozwoli nam łatwiej dostrzec istotę wzorca MVVM.

18 Rozdział 3. Implementacja modelu i model widoku Model Zacznijmy od zaprojektowania modelu. Jak pisałem w poprzednim rozdziale, dobrym sposobem na jego zaprojektowanie jest metodologia DDD. Oznacza to, że w przypadku aplikacji KoloryWPF, będzie on składał się tylko z jednej klasy opisującej kolor. W zasadzie sposób przechowywania w niej koloru jest dowolny mogą to być trzy lub cztery składowe typu byte lub jedna liczba typu int. Unikałbym natomiast używania klasy System.Windows.Media.Color jako zbyt zależnej od biblioteki WPF. Jak już podkreślałem w poprzednim rozdziale, klasa modelu powinna być w jak największym stopniu wolna od wszelkich zależności. Powinno się ją dać skompilować w dowolnym typie projektu.net. W tym samym celu, tj. aby zachować czystość modelu, warto z niego wydzielić warstwę dostępu do danych. Przejdźmy do przygotowania kodu modelu. Proponuję do przechowania składowych użyć trzech liczb typu byte o nazwach R, G i B. Zwykle dla warstw modelu i modelu widoku tworzy się osobny projekt biblioteki DLL lub PCL, ale my się do tego nie posuniemy. Konieczne byłoby wówczas ponowne definiowanie ustawień aplikacji, tym razem w projekcie biblioteki modelu. Odrębność warstw zaznaczymy natomiast w nazwach przestrzeni nazw. 1. Do projektu dodajmy nowy plik klasy i nadajmy mu nazwę Model.Kolor.cs. 2. W kodzie tego pliku zmieńmy przestrzeń nazw na KoloryWPF.Model, a nazwę klasy na Kolor. 3. Następnie zdefiniujmy trzy automatycznie implementowane własności (ang. auto-implemented properties) typu byte przechowujące składowe o nazwach R, G i B. 4. Dodajmy także konstruktor pozwalający ustalać wartości poszczególnych składowych. Cała ta prosta klasa widoczna jest na listingu 3.1. Listing 3.1. Jedna klasa modelu w aplikacji Kolory namespace KoloryWPF.Model public class Kolor public byte R get; set; public byte G get; set; public byte B get; set; public Kolor(byte r, byte g, byte b) this.r = r; this.g = g; this.b = b;

19 Jak widać klasa modelu nie wie nic ani o widoku, ani o modelu widoku. Korzysta tylko z przestrzeni nazw System, w której zdefiniowany jest typ System.Byte. W bardziej skomplikowanym projekcie zależności miedzy klasami modelu muszą się oczywiście pojawić, ale powinny być ograniczone do tej jednej warstwy. Nawet klasy obsługujące zapis stanu modelu raczej powinny korzystać z klas modeli, a nie odwrotnie. Warstwa dostępu do danych Nazwa warstwa dostępu do danych (ang. data access layer, DAL) używana jest pewnie w przypadku naszej aplikacji znacznie na wyrost. Do przechowywania danych nadal będziemy używać klasy Ustawienia. Należy ją jednak zmodyfikować tak, żeby zamiast klasy System.Windows.Media.Color obsługiwała klasę KoloryWPF.Model.Kolor (listing 3.2). Listing 3.2. Klasa Ustawienia przeniesiona zostaje do przestrzeni nazw warstwy modelu using KoloryWPF.Model; namespace KoloryWPF static class Ustawienia public static Kolor Czytaj() Properties.Settings ustawienia = Properties.Settings.Default; return new Kolor(ustawienia.R, ustawienia.g, ustawienia.b); public static void Zapisz(Kolor kolor) Properties.Settings ustawienia = Properties.Settings.Default; ustawienia.r = kolor.r; ustawienia.g = kolor.g; ustawienia.b = kolor.b; ustawienia.save(); Model widoku Teraz przejdźmy do zaprojektowania modelu widoku. Jego podstawową funkcją jest udostępnienie danych przechowywanych w modelu warstwie widoku. Możemy to zrobić na kilka sposobów. Pierwszym jest stworzenie w modelu widoku publicznie dostępnej instancji klasy KoloryWPF.Model.Kolor, do której bezpośrednio mogą odnosić się wiązania widoku. To rozwiązania ma jednak wady związane z mechanizmem powiadomień, które niżej omówię. Zamiast tego lepiej stworzyć prywatną instancję modelu i jego własności udostępniać poprzez własności zdefiniowane w modelu widoku. W ten sposób utrzymywana w modelu widoku instancja modelu odpowiada za stan całej aplikacji. Możliwe jest także nieco odmienne rozwiązanie stan modelu przepisywany jest do modelu widoku i to już tylko on odpowiada za przechowywanie stanu aplikacji. Instancja modelu powstaje wówczas tylko na krótko podczas uruchamiania aplikacji i ponownie podczas zapisywania zmienionych danych. Dwa ostatnie rozwiązania (utrzymywanie lub nie instancji modelu) nie ma wpływu na interfejs modelu widoku, ani na kod widoku. Ja preferuję raczej pierwsze z nich i dlatego przedstawię tylko je.

20 1. Ponownie z menu Project wybierzmy polecenie Add Class... i dodajmy do projektu plik ModelWidoku.EdycjaKoloru.cs. 2. I tym razem zmieńmy przestrzeń nazw na KoloryWPF.ModelWidoku, a nazwę klasy na EdycjaKoloru. 3. W klasie modelu widoku tworzymy prywatne pole-instancję klasy modelu KoloryWPF.Model.Kolor. 4. Jego stan udostępnimy przez trzy własności typu byte o nazwach R, G i B zdefiniowane w klasie EdycjaKoloru. 5. Oprócz tego tymczasowo zdefiniujemy własność Color udostępniającą kolor skonwertowany do typu System.Windows.Media.Color. Samą konwersję umieścimy w metodzie rozszerzającej ToColor zdefiniowanej w klasie Rozszerzenia w tym samym pliku. 6. Do zapisu i odczytu danych wykorzystamy zmodyfikowane przed chwilą metody klasy Ustawienia. Wszystkie te elementy widoczne są na listingu 3.3. Listing 3.3. Klasa modelu widoku EdycjaKoloru i klasa Rozszerzenia using System.Windows.Media; namespace KoloryWPF.ModelWidoku public class EdycjaKoloru private Model.Kolor kolor = Ustawienia.Czytaj(); public byte R get return kolor.r; set kolor.r = value; public byte G get return kolor.g; set kolor.g = value; public byte B

21 get return kolor.b; set kolor.b = value; public Color Color get return kolor.tocolor(); public void Zapisz() Ustawienia.Zapisz(kolor); static class Rozszerzenia public static Color ToColor(this Model.Kolor kolor) return new Color() A = 255, R = kolor.r, G = kolor.g, B = kolor.b ; Alternatywne rozwiązania Rozwiązanie z listingu 3.3 wydaje się dość naturalne. Zastanówmy się jednak także nad drugą ze wspomnianych wcześniej możliwości, w której nie tworzymy trwałej instancji klasy modelu, a jego stan jest kopiowany do modelu widoku. Klasa modelu pełni więc jedynie ograniczoną rolę nośnika. Takie alternatywne rozwiązanie widoczne jest na listingu 3.4 w klasie EdycjaKoloru2. Kod wydaje się znacznie prostszy, bo możliwe jest,

22 przynajmniej na razie, użycie domyślnie implementowanych własności. Różnice między klasami EdycjaKoloru i EdycjaKoloru2 nie wpływa na ich interfejsy, które nie różnią się od siebie. Listing 3.4. Nieco odmienne rozwiązanie modelu widoku public class EdycjaKoloru2 public EdycjaKoloru2() Model.Kolor kolor = Ustawienia.Czytaj(); R = kolor.r; G = kolor.g; B = kolor.b; public byte R get; set; public byte G get; set; public byte B get; set; public Color Color get return Color.FromRgb(R, G, B); public void Zapisz() Model.Kolor kolor = new Model.Kolor(R, G, B); Ustawienia.Zapisz(kolor); Trzecie ze wspomnianych wcześniej rozwiązań z pozoru może wydawać się jeszcze bardziej atrakcyjne ze względu na swoją zwięzłość. Polega ono na zdefiniowaniu w modelu widoku publicznie dostępnej instancji modelu (listing 3.5). Jednak z powodów, które wspominałem w poprzednim rozdziale, a bardziej szczegółowo omówię w kolejnym, to rozwiązanie stanie się niezbyt wygodne w momencie, w którym będziemy chcieli zaimplementować mechanizm powiadamiania widoku o zmianach, jakie zachodzą w modelu widoku, a więc w momencie implementacji interfejsu INotifyPropertyChanged. Ponieważ widok ma bezpośredni dostęp do instancji modelu i sam zmienia jego stan; model widoku pełni tu tylko rolę pośrednika i nie ma możliwości wychwytywania zmian stanu aplikacji. W tej sytuacji interfejs INotifyPropertyChanged należałoby dodać do klasy modelu, co pewnie nie jest rozwiązaniem godnym polecenia, choć czasem stosowanym. Listing 3.5. Kolejna alternatywna wersja modelu widoku public class EdycjaKoloru3 private Model.Kolor kolor = Ustawienia.Czytaj(); public Model.Kolor Kolor get

23 return kolor; public Color Color get return Kolor.ToColor(); public void Zapisz() Ustawienia.Zapisz(Kolor); Model widoku może zawierać wiele klas tyle ile widoków ma nasza aplikacja. Widokami może być całe okno, ale także jego części składowe (np. pasek narzędzi i menu mogą odwzorowywać jeden widok, zawartość okna inny). W naszej prostej aplikacji mamy tylko jeden widok i w konsekwencji tylko jeden model widoku. Co więcej żaden ze zdefiniowanych powyżej modeli widoku nie podejmuje się zadania weryfikacji danych otrzymywanych z widoku. A przynajmniej nie w sposób, jaki zwykle przeprowadzana jest walidacja. Za rodzaj kontroli danych uznany być może użyty w modelu widoku typ danych byte wymusza to, że składowe koloru są liczbami całkowitymi z zakresu od 0 do 255. Ratujmy widok Po zmianach, jakie wprowadziliśmy w projekcie, w szczególności po zmianie metod klasy Ustawienia kod klasy MainWindows nie będzie chciał się skompilować. Aby to było możliwe, należy w konstruktorze klasy MainWindow zmienić polecenie odczytu ustawień: Color kolor = Ustawienia.Czytaj().ToColor(); a w metodzie Window_Close polecenie zapisu: Ustawienia.Zapisz( new Model.Kolor(KolorProstokąta.R, KolorProstokąta.G, KolorProstokąta.B)); To tylko rozwiązanie tymczasowe. W końcu dążymy do usunięcia całego kodu z klasy MainWindow. Jak wspomniałem w poprzednim rozdziale, moglibyśmy uniknąć tworzenia modelu, jeżeli za model uznalibyśmy tworzoną automatycznie klasę Properties.Settings ze zdefiniowanymi składowymi koloru. W tej chwili, razem z klasą Ustawienia pełni ona rolę warstwy dostępu do danych (ang. data access layer, DAL), ale jeżeli się jej przyjrzymy, to jej zawartość jest w zasadzie taka sama, jak klasy Kolor. Zadanie 1. Umieść model i model widoku w osobnych projektach typu Class Library, czyli bibliotekach DLL. Model można umieścić nawet w projekcie typu Class Library (Portable), czyli biblioteki PCL. Zweryfikuj listę bibliotek, do których dodawane są referencje z obu tych projektów.

24 Rozdział 4. Wiązanie danych (data binding) Instancja modelu widoku i kontekst danych Zwiążmy teraz kod modelu widoku, czyli klasę EdycjaKoloru z widokiem. Do tego potrzebujemy instancji modelu widoku, która będzie widoczna w kodzie XAML widoku. Jeżeli to nam się uda, będziemy mogli zacząć stopniowo eliminować code-behind, czyli kod klasy MainWindow.cs. Zacznijmy wobec tego od utworzenia w widoku instancji modelu widoku. Umieśćmy ją w zasobach okna dodając do kodu XAML fragment widoczny na listingu 4.1. Obecność klucza (atrybut x:key) jest konieczna, bo Window.Resources jest słownikiem, a każdy element słownika musi posiadać klucz typu string. Listing 4.1. Dodanie instancji modelu widoku do zasobów okna <Window x:class="kolorywpf.mainwindow" xmlns=" xmlns:x=" xmlns:local="clr-namespace:kolorywpf" xmlns:vm="clr-namespace:kolorywpf.modelwidoku" Title="Kolory WPF" Height="480" Width="640" Closed="Window_Closed"> <Window.Resources> <vm:edycjakoloru x:key="edycjakoloru" /> </Window.Resources>... Kolejnym krokiem będzie ustanowienie tego obiektu kontekstem danych siatki. Siatka zawiera pozostałe kontrolki, a zatem oddziedziczą one jej kontekst danych, chyba że zostanie w nich nadpisany. Jeżeli instancję modelu widoku umieściliśmy w zasobach okna, to nie możemy jej ustanowić kontekstem danych tego okna. Zasoby dostępne są bowiem tylko w elementach zagnieżdżonych. Warto jeszcze raz podkreślić, że nie jest wcale konieczne, aby wszystkie kontrolki miały wspólny kontekst danych. Możliwe jest ustalanie osobnego kontekstu nawet dla każdej z nich (każda kontrolka ma własność DataContext). Wspólny kontekst dla większej grupy kontrolek jest jednak wygodnym rozwiązaniem i sprawdza się doskonale w większości przypadków, także w naszej aplikacji. Aby ustalić kontekst danych siatki należy użyć atrybutu DataContext w odpowiadającym mu elemencie XAML przypisując mu obiekt utworzony w zasobach okna. W przypadku siatki będzie to: <Grid DataContext="StaticResource edycjakoloru"> Alternatywne rozwiązanie Jeżeli zależy nam, żeby i samo okno miało kontekst wiązania, możemy albo umieścić instancję modelu w zasobach aplikacji (plik App.xaml), albo pominąć w ogóle zasoby i stworzyć instancję modelu widoku, którą od razu przypisujemy do własności DataContext. Pokazuje to listing 4.2. Wybór jednego z tych sposobów w żaden sposób nie wpływa na sposób wiązania kontrolek. Listing 4.2. Inne miejsc utworzenia instancji modelu widoku <Window x:class="kolorywpf.mainwindow" xmlns=" xmlns:x="

25 xmlns:local="clr-namespace:kolorywpf" xmlns:vm="clr-namespace:kolorywpf.modelwidoku" Title="Kolory WPF" Height="480" Width="640" Closed="Window_Closed"> <Window.DataContext> <vm:edycjakoloru /> </Window.DataContext> <Grid>... Wiązanie pozycji suwaków i koloru prostokąta Zwiążmy pozycję suwaków z własnościami R, G i B modelu widoku. W tym celu modyfikujemy w kodzie XAML trzy elementy typu Slider: <Slider x:name="sliderr" Margin="10,0,10,56" Height="18" VerticalAlignment="Bottom" Maximum="255" Value="Binding Path=R, Mode=TwoWay" /> <Slider x:name="sliderg" Margin="10,0,10,33" Height="18" VerticalAlignment="Bottom" Maximum="255" Value="Binding Path=G, Mode=TwoWay" /> <Slider x:name="sliderb" Margin="10,0,10,10" Height="18" VerticalAlignment="Bottom" Maximum="255" Value="Binding Path=B, Mode=TwoWay" /> Wiązanie suwaków z własnościami modelu jest dwustronne, o czym świadczy atrybut Mode=TwoWay wiązania. Tak musi być, żeby model widoku mógł wyznaczać pozycję suwaków np. po uruchomieniu aplikacji, ale aby jednocześnie czuł gdy pozycja suwaków zostanie zmieniona przez użytkownika. Taki sposób wiązania jest domyślny w WPF. Proszę zwrócić uwagę, że z elementów Slider usunąłem zdarzenie Value_Changed. To spowoduje, że na razie kolor prostokąta nie będzie zmieniany po zmianie pozycji suwaków. Jeżeli dodatkowo usuniemy metodę zdarzeniową sliderr_valuechanged z klasy MainWindow, to nazwy suwaków nie będą w ogóle używane można je usunąć. Spróbujmy także podłączyć własność Fill prostokąta do modelu widoku. To nie będzie już takie proste, bo własność Fill nie jest typu Color, a Brush (z ang. pędzel). A konkretnie jej typ to dziedzicząca z abstrakcyjnej klasy Brush klasa SolidColorBrush, która reprezentuje pędzel zapełniający figurę jednolitym kolorem. Możemy jednak sięgnąć głębiej i związać własność Color pędzla z własnością Color zdefiniowaną w modelu widoku. Pozwala na to następujące wiązanie: <Rectangle x:name="rectangle" Margin="10,10,10,79" Stroke="Black"> <Rectangle.Fill> <SolidColorBrush Color="Binding Color, Mode=OneWay" /> </Rectangle.Fill> </Rectangle> Tym razem użyłem wiązania jednostronnego (atrybut Mode=OneWay). W efekcie model widoku może zmieniać kolor prostokąta, ale nie ma sposobu, aby wystąpił proces odwrotny. Raz, że kontrolka Rectangle nie daje takich możliwości, a dwa własność Color zdefiniowana w modelu widoku jest tylko do odczytu. Zmiany w code-behind Skompilujmy aplikację i przetestujmy ją. Niestety nie działa! Co więcej nawet nie zapisuje pozycji suwaków przy zamknięciu i ponownym otwarciu aplikacji. To ostatnie na szczęście możemy łatwo naprawić korzystając z tego, że działa już wiązanie suwaków z modelem widoku (listing 4.3). W metodzie Window_Closed odczytujemy instancję modelu widoku z zasobów lub z kontekstu wiązania, następnie tworzymy tymczasową instancję modelu, kopiujemy do niej wartości składowych koloru i używamy jej do zapisania koloru w

26 ustawieniach. Zdaję sobie sprawę, że duplikujemy instancję koloru, jaka jest zdefiniowana w modelu widoku. Nie chcę jej jednak upubliczniać, żeby zachować czystość niższych warstw. To tylko tymczasowe rozwiązanie mające utrzymać działanie aplikacji w trakcie transformacji projektu od wzorca AV do MVVM. Listing 4.3. Łatanie code-behind using KoloryWPF.ModelWidoku; namespace KoloryWPF /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window public MainWindow() InitializeComponent(); private void Window_KeyDown(object sender, KeyEventArgs e) if (e.key == Key.Escape) this.close(); private void Window_Closed(object sender, EventArgs e) EdycjaKoloru edycjakoloru = this.resources["edycjakoloru"] as EdycjaKoloru; Ustawienia.Zapisz( new Model.Kolor(edycjaKoloru.R, edycjakoloru.g, edycjakoloru.b)); Widoczna na listingu 4.3 wersja metody Window_Close jest odpowiednia, jeżeli instancję modelu widoku przechowujemy w zasobach okna. Jeżeli jej instancja przechowywana jest bezpośrednio we własności DataContext okna, pierwsze polecenie metody należy zastąpić przez: EdycjaKoloru edycjakoloru = this.datacontext as EdycjaKoloru; Warto zwrócić uwagę, że także w pierwszym przypadku, tzn. gdy model widoku zdefiniowany jest jako element zasobów, a własność DataContext siatki jest do niego tylko dowiązana, próba odczytania DataContext siatki również da spodziewane rezultaty. Z poziomu kodu C# nie ma znaczenia w jaki sposób ustawiamy wartość własności w kodzie XAML. Wykorzystajmy to, że musieliśmy zajrzeć do klasy MainWindow, aby zrobić tu trochę porządków. Możemy usunąć zawartość konstruktora tej klasy, a także metodę zdarzeniową sliderr_valuechanged i niepotrzebną już własność KolorProstokąta. To sprawi, że code-behind zostanie znacznie zredukowany. Implementacja interfejsu INotifyPropertyChanged Teraz pozycja suwaków powinna być zapamiętywana. Niestety kolor prostokąta nadal zmieniany jest tylko raz, po uruchomieniu aplikacji. Później pozostaje niewrażliwy na to, co robimy z suwakami. Wszystko przez to, że

27 widok nie jest powiadamiany o zmianie stanu modelu widoku. Jak takie powiadamianie uruchomić? Mechanizm wiązań XAML wykorzystuje do tego interfejs INotifyPropertyChanged. Klasa modelu widoku powinna go implementować. Zawsze! A zatem, aby model widoku powiadamiał widok o zmianach swojego stanu należy: 1. Do definicji klasy EdycjaKoloru dodać deklarację implementacji interfejsu INotifyPropertyChanged: public class EdycjaKoloru : INotifyPropertyChanged 2. Przestrzeń nazw tej klasy nie jest widoczna, ale wystarczy ustawić kursor edycji na wpisanej nazwie interfejsu i z menu kontekstowego edytora wybrać polecenie Resolve, aby sekcja poleceń using została uzupełniona o polecenie dołączające przestrzeń System.ComponentModel. 3. Ponownie otwórzmy menu kontekstowe edytora na nazwie interfejsu i wybierzmy polecenie Implement Interface, Implement Interface. Wówczas do klasy dodane zostanie zdarzenie PropertyChanged: public event PropertyChangedEventHandler PropertyChanged; To z tego zdarzenia korzysta mechanizm wiązania XAML. Naszym zadaniem jest wywołanie zdarzenia zawsze wtedy, gdy zmieniany jest stan modelu widoku, wskazując w ten sposób własności, których wartość uległa zmianie. 4. Do klasy dodajmy metodę pomocniczą OnPropertyChanged (listing 4.4) podobną do tych, jakie zwykle towarzyszą zdarzeniom. Nasza nie będzie jednak do końca typowa. Jej argumentem będzie tablica nazw własności i zdarzenie będzie wywoływane tyle razy, ile nazw podamy. Dzięki temu, że użyjemy modyfikatora params nie będziemy musieli jawnie tworzyć tablicy wystarczy że będziemy podawać nazwy własności jako kolejne argumenty metody. To będzie wygodne rozwiązanie, nawet w tak prostej aplikacji, jak nasza, w której model widoku ma tylko cztery publiczne własności. Każdej zmianie stanu towarzyszy bowiem zmiana przynajmniej dwóch własności modelu widoku: odpowiedniej składowej koloru oraz własności Color. Listing 4.4. Metoda pomocnicza służąca do uruchamiania metod subskrybujących zdarzenie private void OnPropertyChanged(params string[] nazwywłasności) if (PropertyChanged!= null) foreach(string nazwawłasności in nazwywłasności) PropertyChanged(this, new PropertyChangedEventArgs(nazwaWłasności)); 5. Metodę OnPropertyChanged należy wywołać w sekcjach set zdarzeń R, G i B. Dla przykładu w zdarzeniu R powinniśmy wywołać tą metodę z argumentami R i Color. Pełen kod klasy modelu widoku z wszystkimi zmianami widoczny jest na listingu 4.5. Listing 4.5. Zmieniona klasa modelu widoku using System.Windows.Media; using System.ComponentModel; namespace KoloryWPF.ModelWidoku public class EdycjaKoloru : INotifyPropertyChanged Model.Kolor kolor = Ustawienia.Czytaj(); public byte R

28 get return kolor.r; set kolor.r = value; OnPropertyChanged("R", "Color"); public byte G get return kolor.g; set kolor.g = value; OnPropertyChanged("G", "Color"); public byte B get return kolor.b; set kolor.b = value; OnPropertyChanged("B", "Color"); public Color Color get return kolor.tocolor(); public void Zapisz()

29 Ustawienia.Zapisz(kolor); public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(params string[] nazwywłasności) if (PropertyChanged!= null) foreach(string nazwawłasności in nazwywłasności) PropertyChanged(this, new PropertyChangedEventArgs(nazwaWłasności));... Teraz, gdy uruchomimy aplikację, wreszcie będzie działać. Zmiana pozycji suwaków znowu będzie powodować zmianę koloru prostokąta. Co więcej pozycja suwaków będzie prawidłowo odwzorowana w instancji modelu i, na razie z użyciem zdarzeń, zapisywana w ustawieniach aplikacji. Powiadomienia w alternatywnych modelach widoku W poprzednim rozdziale wspomniałem dwie inne możliwości zbudowania modelu widoku. Sprawdźmy teraz krótko jak sobie one poradzą przy wiązaniu danych. Zacznijmy od implementacji interfejsu INotifyPropertyChanged w klasie EdycjaKoloru2. Niestety, aby móc wywołać metodę OnPropertyChanged, musimy zrezygnować z domyślnie implementowanych własności. A to oznacza, że klasa straci swój największy atut, czyli niewielką ilość kodu (listing 4.6). Ponieważ publiczne metody i własności klasy EdycjaKoloru2 jest taka sama, jak klasy EdycjaKoloru, aby użyć jej w widoku wystarczy zmienić tylko nazwę klasy w kodzie XAML i w code-behind dodając 2 na końcu. Listing 4.6. Implementacja interfejsu INotifyPropertyChanged w klasie EdycjaKoloru2 public class EdycjaKoloru2 : INotifyPropertyChanged public EdycjaKoloru2() Model.Kolor kolor = Ustawienia.Czytaj(); R = kolor.r; G = kolor.g; B = kolor.b; private byte r, g, b; public byte R

30 get return r; set r = value; OnPropertyChanged("R", "Color"); public byte G get return g; set g = value; OnPropertyChanged("G", "Color"); public byte B get return b; set b = value; OnPropertyChanged("B", "Color"); public Color Color get return Color.FromRgb(R, G, B);

31 public void Zapisz() Model.Kolor kolor = new Model.Kolor(R, G, B); Ustawienia.Zapisz(kolor); public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(params string[] nazwywłasności) if (PropertyChanged!= null) foreach (string nazwawłasności in nazwywłasności) PropertyChanged(this, new PropertyChangedEventArgs(nazwaWłasności)); Zupełnie inaczej sprawy się mają, jeżeli chodzi o trzeci wariant modelu widoku, czyli klasę EdycjaKoloru3. W tej klasie nie definiujemy własności pozwalających na dostęp do poszczególnych własności modelu, a po prostu udostępniamy jego instancje w całości (listing 3.5). Dodatkowo definiujemy tylko własność tylko do odczytu Color konwertującą kolor na typ System.Windows.Media.Color. Niestety takie podejście, w którym w modelu widoku są tylko dwie własności tylko do odczytu, uniemożliwia nam użycie zdarzenia PropertyChanged w taki sposób, jak w powyższych przykładach. Widok będzie bowiem wiązany z elementami składowymi samego modelu i w ten sposób będzie modyfikował pomijając pośrednictwo modelu widoku. Kod własności Kolor nie będzie wywoływany przy każdej zmianie pozycji suwaków, a jedynie raz w momencie wiązania. A to oznacza, że to nie tylko model widoku (ze względu na własność Color), ale również model musi implementować interfejs INotifyPropertyChanged. Listing 4.7 pokazuje modyfikacje modelu, listing 4.8 modelu widoku, listing 4.9 kodu XAML widoku, a 4.10 metody Window_Closed w codebehind. Listing 4.7. Model implementujący interfejs INotifyPropertyChanged using System.ComponentModel; namespace KoloryWPF.Model public class Kolor : INotifyPropertyChanged private byte r, g, b; public byte R get return r; set r = value; OnPropertyChanged("R");

32 public byte G get return g; set g = value; OnPropertyChanged("G"); public byte B get return b; set b = value; OnPropertyChanged("B"); public Kolor(byte r, byte g, byte b) this.r = r; this.g = g; this.b = b; public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(params string[] nazwywłasności) if (PropertyChanged!= null) foreach (string nazwawłasności in nazwywłasności) PropertyChanged(this, new PropertyChangedEventArgs(nazwaWłasności));

33 Listing 4.8. Klasa modelu widoku korzystająca ze zdarzenia PropertyChanged modelu public class EdycjaKoloru3 : INotifyPropertyChanged private Model.Kolor kolor = Ustawienia.Czytaj(); public Model.Kolor Kolor get return kolor; public Color Color get return Kolor.ToColor(); public void Zapisz() Ustawienia.Zapisz(Kolor); public EdycjaKoloru3() Kolor.PropertyChanged += (object sender, PropertyChangedEventArgs e) => OnPropertyChanged("Color"); ; public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(params string[] nazwywłasności) if (PropertyChanged!= null) foreach (string nazwawłasności in nazwywłasności) PropertyChanged(this, new PropertyChangedEventArgs(nazwaWłasności));

34 Listing 4.9. Zmiany w kodzie XAML widoku <Window x:class="kolorywpf.mainwindow" xmlns=" xmlns:x=" xmlns:local="clr-namespace:kolorywpf" xmlns:vm="clr-namespace:kolorywpf.modelwidoku" Title="Kolory WPF" Height="480" Width="640" Closed="Window_Closed"> <Window.Resources> <vm:edycjakoloru3 x:key="edycjakoloru" /> </Window.Resources> <Grid DataContext="StaticResource edycjakoloru"> <Rectangle x:name="rectangle" Margin="10,10,10,79" Stroke="Black"> <Rectangle.Fill> <SolidColorBrush Color="Binding Color, Mode=OneWay" /> </Rectangle.Fill> </Rectangle> <Slider x:name="sliderr" Margin="10,0,10,56" Height="18" VerticalAlignment="Bottom" Maximum="255" Value="Binding Kolor.R, Mode=TwoWay" /> <Slider x:name="sliderg" Margin="10,0,10,33" Height="18" VerticalAlignment="Bottom" Maximum="255" Value="Binding Kolor.G, Mode=TwoWay" /> <Slider x:name="sliderb" Margin="10,0,10,10" Height="18" VerticalAlignment="Bottom" </Grid> </Window> Maximum="255" Value="Binding Kolor.B, Mode=TwoWay" /> Listing Metoda zdarzeniowa uruchamiana przed zamknięciem aplikacji private void Window_Closed(object sender, EventArgs e) EdycjaKoloru3 edycjakoloru = this.resources["edycjakoloru"] as EdycjaKoloru3; Ustawienia.Zapisz(edycjaKoloru.Kolor); Widok jest związany zarówno z własnościami modelu, jak i z własnością modelu widoku (listing 4.9). Suwaki odnoszą się bezpośrednio do modelu, do jego własności R, G i B, a prostokąt do własności Color zdefiniowanej w modelu widoku. Dlatego obie klasy Model.Kolor i ModelWidoku.EdycjaKoloru3 muszą implementować interfejs INotifyPropertyChanged. Ponieważ tylko model jest zmieniany z poziomu widoku, model widoku o zmianach koloru może się dowiedzieć jedynie subskrybując zdarzenie PropertyChanged modelu. To właśnie robi w konstruktorze (listing 4.8). W efekcie, gdy nastąpi zmiana którejkolwiek składowej koloru w modelu, model widoku zgłasza automatycznie zmianę własności Color. Jak widać z powyższych listingów takie podejście powoduje, że pomimo początkowej prostoty klas, teraz wszystkie musieliśmy rozbudować o dodatkowy kod związany z mechanizmem powiadamiania o zmianach. Tylko metoda zdarzeniowa Window_Closed z klasy MainWindow uprościła się nieco dzięki temu, że mamy bezpośredni dostęp do instancji klasy Kolor i nie musimy tworzyć tymczasowego jej egzemplarza.

35 Rozdział 5. Konwersja danych w wiązaniu Prosta konwersja typów W wiązaniu pozycji suwaków z własnościami modelu widoku kryje się pewne zagrożenie, które w przypadku WPF się nie ujawnia, ale w Windows Phone 8.1 spowodowałoby, że aplikacja nie chciałaby działać. Własności R, G i B modelu widoku są typu byte, co ogranicza ich wartości do liczb całkowitych z zakresu od 0 do 255. Własności Value suwaków są jednak typu double. Odczyt składowych koloru z modelu widoku i przypisywanie ich do pozycji suwaków, co ma miejsce tylko przy uruchamianiu aplikacji, jest całkowicie bezpieczny. Konwersja z byte do double jest dopuszczana implcite, typ double ma bowiem zarówno większy zakres, jak i większą precyzję. Jednak w momencie, w którym poruszymy suwakiem, wiązanie danych wymusza konwersję z double (wartość własności Value suwaka) do byte (własności R, G i B modelu widoku i pośrednio odpowiadające im własności modelu). W aplikacji Windows Phone 8.1 taka operacja skończyłaby się błędem, choć aplikacja w żaden sposób by go nie zasygnalizowała. Jedynym śladem byłby zapis w oknie Output Visual Studio. Oraz oczywiście nasze gorączkowe próby zrozumienia dlaczego nie zmienia się kolor prostokąta. W tej sytuacji pomoże nam konwerter między typami byte i double. Użyjemy go w wiązaniu pozycji suwaków. Konwerter jest klasą implementującą interfejs IValueConverter z przestrzeni nazw System.Windows.Data (w Windows Phone z przestrzeni Windows.UI.Xaml.Data), który wymusza obecność dwóch metod: Convert i ConvertBack (listing 5.1). Obie metody mają takie same sygnatury. Wartość źródła wiązania (własności zdefiniowanej w modelu widoku) przesyłana jest do metody Convert w parametrze value typu object, a skonwertowana wartość powinna być zwrócona przez wartość metody 2. Pożądany typ, na jaki wartość ma być skonwertowana jest przekazywana w drugim parametrze. My jednak wiemy w jakim kontekście będzie używany konwerter, więc możemy zrobić zwykłe rzutowanie na typ double. Musi ono być jednak poprzedzone rzutowaniem na typ byte, który jest prawdziwym typem argumentu value. W metodzie ConvertBack wszystko jest odwrotnie. Przez parametr value przekazywana jest wartość z widoku, a targettype informuje o typie własności z modelu widoku. Umieśćmy konwerter w osobnym pliku Konwertery.cs. Klasę umieściłem w przestrzeni KoloryWPF, odpowiadającej warstwie widoku. Listing 5.1. Klasa konwertera using System; using System.Windows.Data; namespace KoloryWPF public class ByteToDoubleConverter : IValueConverter public object Convert(object value, Type targettype, object parameter, System.Globalization.CultureInfo culture) return (double)(byte)value; public object ConvertBack(object value, Type targettype, object parameter, 2 Niestety nie ma parametrycznej wersji interfejsu IValueConverter, co wydawałoby się bardzo naturalne.

36 System.Globalization.CultureInfo culture) return (byte)(double)value; Aby użyć konwertera musimy wpierw utworzyć jego instancję w kodzie XAML. Doskonale do jej przechowania nadają się zasoby okna: <Window x:class="kolorywpf.mainwindow" xmlns=" xmlns:x=" xmlns:local="clr-namespace:kolorywpf" xmlns:vm="clr-namespace:kolorywpf.modelwidoku" Title="Kolory WPF" Height="480" Width="640" Closed="Window_Closed"> <Window.Resources> <vm:edycjakoloru x:key="edycjakoloru" /> <local:bytetodoubleconverter x:key="konwersjabytedouble" /> </Window.Resources> Instancję konwertera możemy następnie użyć w wiązaniach pozycji suwaków wskazując go w atrybucie Converter wiązania: <Slider x:name="sliderr" Margin="10,0,10,56" Height="18" VerticalAlignment="Bottom" /> Maximum="255" Value="Binding R, Mode=TwoWay, Converter=StaticResource konwersjabytedouble" <Slider x:name="sliderg" Margin="10,0,10,33" Height="18" VerticalAlignment="Bottom" /> Maximum="255" Value="Binding G, Mode=TwoWay, Converter=StaticResource konwersjabytedouble" <Slider x:name="sliderb" Margin="10,0,10,10" Height="18" VerticalAlignment="Bottom" /> Maximum="255" Value="Binding B, Mode=TwoWay, Converter=StaticResource konwersjabytedouble" Zamiast używać konwertera, moglibyśmy dopasować typy własności zdefiniowane w klasie modeli widoku z typami własności kontrolek, do których mają być wiązanie. W podobnym celu zdefiniowaliśmy własność Color. To nie jest jednak dobry sposób. Model widoku powinien być abstrakcyjny nie należy go dostosowywać do wymagań konkretnego widoku. Do dopasowanie jednego z drugim służą właśnie konwertery. Poza tym typ byte własności R, G i B jest świetnym strażnikiem poprawności danych, którego szkoda byłoby wymienić na wygodniejsze wiązanie. Konwersja klas Color i SolidColorBrush Do pliku Konwertery.cs dodajmy jeszcze jedną klasę konwertującą kolor (typ System.Windows.Media.Color) do pędzla (klasa SolidBrushColor z tej samej przestrzeni nazw) (listing 5.2). Tym razem wystarczy nam konwersja w jedną stronę, więc metodę ConvertBack pozostawimy niezaimplementowaną. Listing 5.2. Konwerter koloru do pędzla public class ColorToSolidColorBrushConverter : IValueConverter

37 public object Convert(object value, Type targettype, object parameter, System.Globalization.CultureInfo culture) Color kolor = (Color)value; return new SolidColorBrush(kolor); public object ConvertBack(object value, Type targettype, object parameter, throw new NotImplementedException(); System.Globalization.CultureInfo culture) Instancję konwertera, podobnie jak poprzedniego, dodajmy do zasobów okna: <Window.Resources> <vm:edycjakoloru x:key="edycjakoloru" /> <local:bytetodoubleconverter x:key="konwersjabytedouble" /> <local:colortosolidcolorbrushconverter x:key="konwersjacolorbrush" /> </Window.Resources> Użyjmy go, aby uprościć element Rectangle: <Rectangle x:name="rectangle" Margin="10,10,10,79" Stroke="Black" Fill="Binding Color, Converter=StaticResource konwersjacolorbrush" /> I w tym przypadku moglibyśmy w modelu widoku udostępnić własność typu SolidColorBrush, ale, jak wspomniałem wyżej, straciłby on w ten sposób swój abstrakcyjny minimalistyczny charakter. Multibinding Bardzo interesująca jest dostępna w WPF możliwość konwersji kilku własności modelu widoku do jednej własności kontrolki umieszczonej w widoku. Dzięki niej moglibyśmy się pozbyć z klasy EdycjaKoloru własności Color, która nieco zaburza czystość tej klasy. Aby stworzyć konwerter tego typu należy utworzyć klasę implementującą interfejs IMultiValueConverter. Dodajmy taką klasę o nazwie SkładoweRGBToSolidColorBrushConverter do pliku Konwertery.cs (listing 5.3). Zwróćmy uwagę na zmianę w sygnaturze metod Convert i ConvertBack: Teraz pierwsza z nich przyjmuje tablicę wartości, a druga zwraca taką tablicę. Metody ConvertBack nie będziemy wprawdzie używać, ale jej implementację podaję dla przykładu. Listing 5.3. Konwersja wiele-do-jednego używana w wielowiązaniu danych public class SkładoweRGBToSolidColorBrushConverter : IMultiValueConverter public object Convert(object[] values, Type targettype, object parameter, byte r = (byte)values[0]; byte g = (byte)values[1]; byte b = (byte)values[2]; System.Globalization.CultureInfo culture) return new SolidColorBrush(Color.FromRgb(r, g, b));

38 public object[] ConvertBack(object value, Type[] targettypes, object parameter, System.Globalization.CultureInfo culture) SolidColorBrush pędzel = value as SolidColorBrush; Color kolor = pędzel.color; return new object[3] kolor.r, kolor.g, kolor.b ; Tworzenie instancji konwertera realizuje się tak samo, jak w przypadku poprzednich konwerterów: <local:składowergbtosolidcolorbrushconverter x:key="konwersjargbbrush" /> ale inaczej się go używa: <Rectangle x:name="rectangle" Margin="10,10,10,79" Stroke="Black"> <Rectangle.Fill> <MultiBinding Mode="OneWay" Converter="StaticResource konwersjargbbrush"> <Binding Path="R" /> <Binding Path="G" /> <Binding Path="B" /> </MultiBinding> </Rectangle.Fill> </Rectangle> Kolejność elementów Binding wyznacza kolejność elementów w tablicy values w metodzie Convert konwertera. Tak, jak zapowiedziałem, po podłączeniu koloru prostokąta bezpośrednio do składowych koloru udostępnianych przez własności R, G i B modelu widoku, własność Color stanie się zbyteczna i można ją usunąć. Multibinding i używane w nim konwertery implementujące interfejs IMultiValueConverter nie są dostępne w aplikacjach dla systemu Windows Phone. Wiązanie między kontrolkami Wiązanie kontrolek nie musi dotyczyć tylko elementów udostępnianych przez model widoku. Możemy związać własności kontrolek z własnościami innych kontrolek. Prostym przykładem takiego wiązania niech będą etykiety TextBlock pokazujące wartości wyznaczone przez pozycje poszczególnych suwaków (listing 5.4, rysunek 5.1). Listing 5.4. Kod XAML zawierający wiązania między suwakami i etykietami <Slider x:name="sliderr" Margin="10,0,40,56" Height="18" VerticalAlignment="Bottom" Maximum="255" Value="Binding R, Mode=TwoWay, Converter=StaticResource konwersjabytedouble" /> <TextBlock Height="18" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="10,0,10,56" Text="Binding ElementName=sliderR, Path=Value" /> <Slider x:name="sliderg" Margin="10,0,40,33" Height="18" VerticalAlignment="Bottom" Maximum="255" Value="Binding G, Mode=TwoWay, Converter=StaticResource konwersjabytedouble" /> <TextBlock Height="18" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="10,0,10,33" Text="Binding ElementName=sliderG, Path=Value" />

39 <Slider x:name="sliderb" Margin="10,0,40,10" Height="18" VerticalAlignment="Bottom" Maximum="255" Value="Binding B, Mode=TwoWay, Converter=StaticResource konwersjabytedouble" /> <TextBlock Height="18" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="10,0,10,10" Text="Binding ElementName=sliderB, Path=Value" /> Rysunek 5.1. Etykiety pokazujące wartości suwaków Rozważmy również inny przykład: zamiast wiązać kolor prostokąta z modelem widoku, możemy związać go bezpośrednio z pozycjami suwaków. Zaletą takiego rozwiązania jest mniejsza liczba powiązań między warstwami widoku i modelu widoku. Do tego musimy jednak przygotować konwerter, który przekształcałby trzy składowe koloru zapisane w liczbach typu double do typu SolidColorBrush (listing 5.5). Zmieniony kod XAML widoczny jest na listingu 5.6. Listing 5.5. Na listingu zaznaczone są różnice metody Convert względem tej z konwertera z listingu 5.3 public class SkładoweRGBDoubleToSolidColorBrushConverter : IMultiValueConverter public object Convert(object[] values, Type targettype, object parameter, byte r = (byte)(double)values[0]; byte g = (byte)(double)values[1]; byte b = (byte)(double)values[2]; System.Globalization.CultureInfo culture) return new SolidColorBrush(Color.FromRgb(r, g, b)); public object[] ConvertBack(object value, Type[] targettypes, object parameter, System.Globalization.CultureInfo culture) throw new NotImplementedException(); Listing 5.6. Zmieniony kod XAML <Window x:class="kolorywpf.mainwindow"

40 xmlns=" xmlns:x=" xmlns:local="clr-namespace:kolorywpf" xmlns:vm="clr-namespace:kolorywpf.modelwidoku" Title="Kolory WPF" Height="480" Width="640" Closed="Window_Closed"> <Window.Resources> <vm:edycjakoloru x:key="edycjakoloru" /> <local:bytetodoubleconverter x:key="konwersjabytedouble" /> <local:colortosolidcolorbrushconverter x:key="konwersjacolorbrush" /> <local:składowergbtosolidcolorbrushconverter x:key="konwersjargbbrush" /> <local:składowergbdoubletosolidcolorbrushconverter x:key="konwersjargbdoublebrush" /> </Window.Resources> <Grid DataContext="StaticResource edycjakoloru"> <Rectangle x:name="rectangle" Margin="10,10,10,79" Stroke="Black"> <Rectangle.Fill> <MultiBinding Mode="OneWay" Converter="StaticResource konwersjargbdoublebrush"> <Binding ElementName="sliderR" Path="Value" /> <Binding ElementName="sliderG" Path="Value" /> <Binding ElementName="sliderB" Path="Value" /> </MultiBinding> </Rectangle.Fill> </Rectangle>... </Grid> </Window> Zwróćmy uwagę, że gdyby nie chęć przechowywania koloru między uruchomieniami aplikacji, a więc gdyby jedynym celem aplikacji było ustawianie koloru prostokąta za pomocą trzech suwaków, konwerter byłby jedynym kodem, jaki potrzebowałby ten projekt. Dzięki bezpośredniemu wiązaniu między kontrolkami model widoku i sam model stają się całkowicie niepotrzebne! Zadanie 1. Zaimplementuj i przetestuj wszystkie nie zaimplementowany metody ConvertBack z tego rozdziału.

41 Rozdział 6. Polecenia (commands) Interfejs ICommand. W klasie MainWindow tworzącej tak zwany code-behind w warstwie widoku pozostały już tylko dwie metody. Pierwsza obsługuje naciśnięcie klawisza Escape, zamykając okno. Druga wymusza zapisanie składowych koloru w ustawieniach aplikacji w momencie zamykania okna (zdarzenie Window.Closed). W tym rozdziale postaramy się pozbyć ich obu. Zacznijmy od drugiej. W tym celu zdefiniujemy w modelu widoku tzw. polecenie (ang. command), które umożliwi nam wymuszenie zapisania stanu aplikacji. Polecenie to klasa implementująca interfejs ICommand. Interfejs ten wymusza obecność dwóch metod oraz zdarzenia. Metody to Execute wykonująca zasadnicze działanie polecenia oraz CanExecute sprawdzająca, czy wykonanie polecenia jest możliwe. Natomiast zdarzenie CanExecuteChanged powiadamia o zmianie możliwości wykonania polecenia. Chyba najprostsza klasa polecenia widoczna jest na listingu 6.1. Zapiszmy ją w osobnym pliku o nazwie ModelWidoku.Polecenia.cs. Nietrywialnie zdefiniowana jest w niej tylko metoda Execute, która przywraca początkowy stanu aplikacji przesuwając suwaki na pozycje zerowe. Do metody tej przekazywany jest parametr, o którym zakładamy, że zawiera referencję do instancji modelu widoku. Metoda CanExecute zawsze zwraca wartość true, a zdarzenie nie jest używane. Listing 6.1. Klasa implementująca interfejs ICommand using System; using System.Windows.Input; using System.Diagnostics; namespace KoloryWPF.ModelWidoku class ResetujCommand : ICommand public event EventHandler CanExecuteChanged; public bool CanExecute(object parameter) return true; public void Execute(object parameter) EdycjaKoloru modelwidoku = parameter as EdycjaKoloru; if (modelwidoku!= null) modelwidoku.r = 0; modelwidoku.g = 0; modelwidoku.b = 0;

42 Aby móc użyć polecenia w widoku, należy utworzyć jego instancję w modelu widoku i ją udostępnić. Najlepiej zrobić to za pomocą publicznej własności tylko do odczytu typu ICommand. Aby udostępnić polecenie ResetujCommand w klasie EdycjaKoloru należy do niej dodać kod widoczny na listingu 6.2. W kodzie widoczne jest zabezpieczenie przed powielaniem egzemplarzy klasy polecenia. Listing 6.2. Własność zdefiniowana w klasie EdycjaKoloru udostępniająca polecenie private ICommand resetujcommand; public ICommand Resetuj get if (resetujcommand == null) resetujcommand = new ResetujCommand(); return resetujcommand; Użycie polecenia w przycisku Do okna dodajmy przycisk. Przesuńmy suwaki nieco w górę i umieśćmy przycisk pod nimi (rysunek 6.1). Aby związać polecenie z kliknięciem przycisku w kodzie XAML, należy użyć atrybutu Command elementu Button. Dodatkowo, jako parametr powinniśmy przesłać instancję klasy EdycjaKoloru, czyli model widoku. Jeżeli jest ona zdefiniowana w zasobach aplikacji, możemy użyć kodu widocznego na listingu 6.3. Rysunek 6.1. Interfejs aplikacji wzbogaciliśmy o przycisk Listing 6.3. Przycisk, którego atrybut Command związany jest z poleceniem udostępnionym przez model widoku <Button Content="Resetuj" Height="25" Width="75" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="10,0,0,10" Command="Binding Resetuj" CommandParameter="StaticResource edycjakoloru" /> Jeżeli instancja modelu widoku nie jest umieszczona w zasobach, zawsze możemy wykorzystać to, że jest kontekstem danych kontrolek i użyć wiązania postaci:

43 <Button Content="Resetuj" Height="25" Width="75" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="10,0,0,10" Command="Binding Resetuj" CommandParameter="Binding Path=DataContext, RelativeSource=RelativeSource Self" /> Jednak zamiast kłopotać się z przekazywaniem instancji modeli widoku do klasy polecenia jako parametru metody Execute, znacznie wygodniejsze jest przekazanie go przez konstruktor polecenia z poziomu kodu C# modelu widoku. Zmieńmy klasę polecenia zgodnie ze wzorem z listingu 6.4. Listing 6.4. Polecenie przechowujące referencję do modelu widoku class ResetujCommand : ICommand private EdycjaKoloru modelwidoku; public ResetujCommand(EdycjaKoloru modelwidoku) this.modelwidoku = modelwidoku; public event EventHandler CanExecuteChanged; public bool CanExecute(object parameter) return true; public void Execute(object parameter) EdycjaKoloru modelwidoku = parameter as EdycjaKoloru; if (modelwidoku!= null) modelwidoku.r = 0; modelwidoku.g = 0; modelwidoku.b = 0; W konsekwencji musimy zmienić kod własności Resetuj w klasie EdycjaKoloru (listing 6.5). Listing 6.5. Kod własności należy uzupełnić o przesyłanie referencji do instancji modelu widoku private ICommand resetujcommand; public ICommand Resetuj get if (resetujcommand == null) resetujcommand = new ResetujCommand(this); return resetujcommand;

44 Natomiast kod XAML można uprościć. Przesyłanie parametru do polecenia nie jest już bowiem potrzebne i tak parametr zostanie zignorowany: <Button Content="Resetuj" Height="25" Width="75" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="10,0,0,10" Command="Binding Resetuj" CommandParameter="Binding Path=DataContext, RelativeSource=RelativeSource Self" /> Sprawdzanie możliwości wykonania polecenia Pójdźmy o krok dalej i wykorzystajmy metodę CanExecute polecenia, aby sprawdzić, czy wykonanie polecenia jest możliwe i potrzebne. W naszym przypadku warunkiem niech będzie to, że suwaki są w innym położeniu niż wyjściowe, co przekłada się na warunek że składowe koloru przechowywane w modelu widoku nie są wszystkie równe zeru. Zmieńmy wobec tego metodę CanExecute w taki sposób, aby sprawdzała ten warunek (listing 6.6). Dodatkowo zmodyfikujmy zdarzenie CanExecuteChanged polecenia tak, aby przy dodaniu kolejnej metody do zbioru metod subskrybujących to zdarzenie, metoda ta była zapisywana także w menedżerze poleceń zaimplementowanym w klasie System.Windows.Input.CommandManager, który za nas zadba o zgłaszanie tego zdarzenia w razie zmiany warunku zaimplementowanego w metodzie CanExecute. Przejmie tym samym odpowiedzialność za powiadamianie wykorzystujących to polecenie kontrolek. Rozbudowana definicja zdarzenia widoczna jest na listingu 6.6. Efekt tych zmian będzie bardzo ciekawy, choć nie rzucający się w pierwszej chwili w oczy. Przycisk stanie się nieaktywny, jeżeli suwaki będą w zerowej pozycji. Stanie się znów aktywny, gdy którykolwiek z nich przesuniemy. Listing 6.6. Polecenie z nietrywialną metodą CanExecute class ResetujCommand : ICommand private EdycjaKoloru modelwidoku; public ResetujCommand(EdycjaKoloru modelwidoku) this.modelwidoku = modelwidoku; public event EventHandler CanExecuteChanged add CommandManager.RequerySuggested += value; remove CommandManager.RequerySuggested -= value; public bool CanExecute(object parameter)

45 return (modelwidoku.r!= 0) (modelwidoku.g!= 0) (modelwidoku.b!= 0); public void Execute(object parameter) if (modelwidoku!= null) modelwidoku.r = 0; modelwidoku.g = 0; modelwidoku.b = 0; Resetowanie stanu po naciśnięciu klawisza Nie tylko przyciski mogą korzystać z poleceń. Z poleceniem możemy również związać choćby naciśnięcie klawisza. Można to zrobić na poziomie każdej kontrolki, siatki lub całego okna. To ostatnie rozwiązanie jest najlepsze naciśnięcie klawisza będzie wówczas wykrywane bez względu na to, czy któraś z kontrolek jest aktywna (brzydko mówiąc: ma focus ). To jednak oznacza, że element okno musi mieć przypisany kontekst wiązania (por. listingi 4.1 i 4.2 z rozdziału 4). Kod pokazujący wiązanie naciśnięcia klaswisza R z poleceniem Resetuj modelu widoku widoczny jest na listingu 6.7. Listing 6.7. Wiązanie naciśnięcia klawisza R z poleceniem Resetuj udostępnianym przez model widoku <Window... > <Window.DataContext> <vm:edycjakoloru /> </Window.DataContext> <Window.InputBindings> <KeyBinding Key="R" Command="Binding Resetuj" /> </Window.InputBindings> Jeżeli zamiast prostego naciśnięcia klawisza R, chcemy aby resetowanie aplikacji następowało po naciśnięciu kombinacji Ctrl+R, należy dodać atrybut Modifiers: <KeyBinding Key="R" Modifiers="Control" Command="Binding Resetuj" /> W podobny sposób możemy związać polecenie z czynnościami wykonywanymi myszą. Wystarczy, że do elementu Window.InputBindings dodamy element: <MouseBinding Gesture="MiddleClick" Command="Binding Resetuj" /> Spowoduje on, że polecenie będzie wykonywane, gdy kliknięty zostanie środkowy klawisz myszy. Jeżeli chcemy, żeby dodatkowym warunkiem było jednoczesne przytrzymywanie klawisza Alt, należy element MouseBinding następująco zmodyfikować: <MouseBinding Gesture="Alt+MiddleClick" Command="Binding Resetuj" /> Klasa RelayCommand Klasa polecenia może być uogólniona tak, żeby mogła przechowywać dowolną czynność i dowolny warunek weryfikujący potrzebę lub możliwość wykonania tej czynności. Zamiast korzystać z metod zdefiniowanych w klasie polecenia, wystarczy przecież aby klasa ta przechowała referencje do tych dwóch metody lub wyrażeń Lambda. Referencje te będą przekazane jako argumenty konstruktora. Wówczas metody te mogą odwoływać się

46 do pól modelu widoku bez przekazywania jego referencji do obiektu polecenia. Często używaną implementacją takiej ogólnej klasy polecenia jest klasa RelayCommand (co można chyba przetłumaczyć jako polecenie przekaźnikowe ) opisana np. w dostępnym on-line artykule pt. WPF Apps With The Model-View-ViewModel Design Pattern z MSDN Magazine ( (por. też klasę MvvmCommand z książki Budowanie aplikacji biznesowych za pomocą Windows Presentation Foundation i wzorca Model View ViewModel autorstwa Garofalo Raffaele). Klasa ta widoczna jest na listingu 6.8. Listing 6.8. Uogólniona klasa polecenia public class RelayCommand : ICommand #region Fields readonly Action<object> _execute; readonly Predicate<object> _canexecute; #endregion // Fields #region Constructors public RelayCommand(Action<object> execute) : this(execute, null) public RelayCommand(Action<object> execute, Predicate<object> canexecute) if (execute == null) throw new ArgumentNullException("execute"); _execute = execute; _canexecute = canexecute; #endregion // Constructors #region ICommand Members [DebuggerStepThrough] public bool CanExecute(object parameter) return _canexecute == null? true : _canexecute(parameter); public event EventHandler CanExecuteChanged add if (_canexecute!= null) CommandManager.RequerySuggested += value; remove if (_canexecute!= null) CommandManager.RequerySuggested -= value;

47 public void Execute(object parameter) _execute(parameter); #endregion // ICommand Members Użycie tej klasy w modelu widoku (listing 6.9) przenosi miejsce, w którym zdefiniowany jest kod polecenia i towarzyszący mu warunek jego wykonania z osobnej klasy polecenia do klasy modelu widoku. To bardzo wygodne rozwiązanie, szczególnie że z punktu widzenia widoku i kodu XAML, użycie ogólnej wersji klasy polecenia nic nie zmienia. Listing 6.9. Prywatne pole i publiczna własność udostępniająca polecenie w klasie modelu widoku private ICommand resetujcommand; public ICommand Resetuj get if (resetujcommand == null) resetujcommand = new RelayCommand( argument => R = 0; G = 0; B = 0;, argument => (R!= 0) (G!= 0) (B!= 0) ); return resetujcommand; Warto wspomnieć o predefiniowanych poleceniach, które są gotowe do użycia w czterech statycznych klasach: ApplicationCommands, NavigationCommands, MediaCommands i EditingCommands. Zdarzenia w MVVM a polecenia Przycisk, jak również elementy menu, czy pole opcji (checkbox) posiada możliwość związania polecenia. Każda kontrolka ma również możliwość związania poleceń z klawiszami lub gestami wykonywanymi myszą (służy do tego omówiony wyżej pod-element InputBindings). Mimo to sytuacji, w których uaktywnione zostaną polecenia jest znacznie mniej niż sytuacji, w których zgłaszane są zdarzenia kontrolek. Bardzo łatwo jest dla przykładu związać metodę zdarzeniową ze zdarzeniem zamknięcia ekranu. Jak jednak zrobić to używając polecenia, dzięki czemu pozostalibyśmy w zgodzie ze wzorcem MVVM? Okazuje się, że jest pewien trik, który został utworzony na potrzeby współpracy Visual Studio z Expression Blend, a którego można użyć, aby zdarzenie przekształcić w polecenie. Kluczowa jest tu klasa EventTrigger, która wykonuje wskazane

48 polecenie w momencie wystąpienia zdarzenia. Klasa ta zwykle jest używana podczas definiowania styli. Trik, który chcę teraz przedstawić polega właśnie na tym, że użyjemy jej do przekształcenia zdarzenia w polecenie. 1. Zacznijmy od modyfikacji w modelu widoku. Zastąpmy metodę Zapisz przez polecenie (listing 6.10). Listing Zmiany w klasie EdycjaKoloru public void Zapisz() Ustawienia.Zapisz(kolor); private ICommand zapiszcommand; public ICommand Zapisz get if (zapiszcommand == null) zapiszcommand = new RelayCommand(argument => Ustawienia.Zapisz(kolor); ); return zapiszcommand; 2. Kolejnym krokiem będzie dodanie do projektu dwóch bibliotek: System.Windows.Interactivity.dll i Microsoft.Expression.Interaction.dll (obie w najwyższych wersjach). W tym celu z menu Project wybieramy Add Reference... Pojawi się okno Reference Manager, z którego lewej strony wybieramy Assemblies, Framework i w okienku edycyjnym przeszukiwania (prawy górny róg okna) wpisujemy Interact. 3. Po pojawianiu się wyników wyszukiwania, zaznaczamy obie biblioteki (należy zaznaczyć każdą z nich osobno uważając na wersje) i klikamy przycisk OK. 4. Z pliku MainWindow.xaml.cs usuwamy metodę zdarzeniową Window_Closed. W tej chwili została w nim już tylko metoda zdarzeniowa Windows_KeyDown zamykająca okno po naciśnięciu klawisza Esc. 5. Następnie przechodzimy do edycji kodu XAML w pliku MainWindows.xaml. Zakładam, że instancja model widoku jest kontekstem wiązania okna, a więc w kodzie XAML jest obecne przypisanie podobne do: <Window.DataContext> <vm:edycjakoloru /> </Window.DataContext> 6. Do znacznika Windows dodajemy przestrzeń nazw której nadajemy alias i, co oznacza, że umieszczamy w nim atrybut: xmlns:i=" 7. Ze znacznika Window należy usunąć atrybut Closed, który wskazywał na usuniętą przed chwilą z code-behind metodę zdarzeniową. 8. Następnie do elementu Window wstawiamy element Interaction.Triggers, gdzie Interaction to przestrzeń nazw, a Triggers to kolekcja, do której dodajemy instancję klasy EventTrigger. Atrybut EventName tego elementu wskazuje nazwę zdarzenia macierzystego elementu (czyli Window). W naszym przypadku będzie to Closed. Zawartością elementu EventTrigger powinien być natomiast element InvokeCommandAction, który wskazuje polecenie wykonywane w razie wystąpienia zdarzenia. Cały kod XAML, wraz z opisanymi wyżej zmianami widoczny jest na listingu Listing Zamknięcie okna spowoduje wykonanie polecenia Zapisz zdefiniowanego w modelu widoku

49 <Window x:class="kolorywpf.mainwindow" /> xmlns=" xmlns:x=" xmlns:local="clr-namespace:kolorywpf" xmlns:vm="clr-namespace:kolorywpf.modelwidoku" xmlns:i=" Title="Kolory WPF" Height="480" Width="640" KeyDown="Window_KeyDown" Closed="Window_Closed"> <Window.Resources>... <vm:składowergbdoubletosolidcolorbrushconverter x:key="konwersjargbdoublebrush" </Window.Resources> <Window.DataContext> <vm:edycjakoloru /> </Window.DataContext> <Window.InputBindings> <KeyBinding Key="R" Modifiers="Control" Command="Binding Resetuj" /> <MouseBinding Gesture="MiddleClick" Command="Binding Resetuj" /> </Window.InputBindings> <i:interaction.triggers> <i:eventtrigger EventName="Closed"> <i:invokecommandaction Command="Binding Zapisz" /> </i:eventtrigger> </i:interaction.triggers> <Grid> <Rectangle x:name="rectangle" Margin="10,10,10,109" Stroke="Black"> <Rectangle.Fill> <MultiBinding Mode="TwoWay" Converter="StaticResource konwersjargbdoublebrush"> <Binding ElementName="sliderR" Path="Value" /> <Binding ElementName="sliderG" Path="Value" /> <Binding ElementName="sliderB" Path="Value" /> </MultiBinding> </Rectangle.Fill> </Rectangle>... </Grid> </Window> <Button Content="Resetuj" Height="25" Width="75" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="10,0,0,10" Command="Binding Resetuj" /> 9. Warto oczywiście uruchomić aplikację i sprawdzić czy zapisywanie zrealizowane w nowy sposób działa prawidłowo.

50 Zamykanie okna W code-behind została już tylko jedna metoda, która związana jest ze zdarzeniem KeyDown okna. Możemy się jej pozbyć definiując w modelu widoku następujące polecenie: public ICommand ZamknijOkno get return new RelayCommand(argument => App.Current.MainWindow.Close(); ); i podłączając je do naciśniętego klawisza Esc w sposób, którego już używaliśmy: <Window.InputBindings> <KeyBinding Key="R" Modifiers="Control" Command="Binding Resetuj" /> <MouseBinding Gesture="MiddleClick" Command="Binding Resetuj" /> <KeyBinding Key="Escape" Command="Binding ZamknijOkno" /> </Window.InputBindings> Należy oczywiście usunąć atrybut Key_Down ze znacznika Window i samą metodę zdarzeniową z pliku MainWindow.xaml.cs. Po uruchomieniu aplikacji przekonamy się, że to rozwiązanie działa. Problem jednak w tym, że niezbyt mi się ono podoba. Model widoku nie powinien znać szczegółów samego widoku. Zwróćmy uwagę, że konsekwencją złamania tej zasady będą trudności, z jakim nam przyjdzie sie zmierzyć, jeżeli zechcemy przygotować testy jednostkowe dla polecenia ZamknijOkno. Powyższe polecenie i korzystające z niego wiązanie można nieco poprawić przesyłając referencję okna przez parametr: public ICommand ZamknijOkno get return new RelayCommand( argument => (argument as System.Windows.Window).Close(); ); Wówczas wiązanie powinno wyglądać następująco: <KeyBinding Key="Escape" Command="Binding ZamknijOkno" CommandParameter="Binding RelativeSource=RelativeSource AncestorType=x:Type Window" /> To jednak nie zmienia zasadniczego mankamentu tego rozwiązania. Już lepsze od tego jest moim zdaniem pozostawienie kodu odpowiedzialnego za zamknięcie okna w metodzie zdarzeniowej z code-behind. Innym, bardziej eleganckim obejściem problemu jest kolejny trik, który nazywa się zachowaniem dołączonym do kontrolki okna. Omówię go w następnym rozdziale. Zadanie 1. Napisz samodzielnie komendę kopiującą do schowka kod koloru (np. #FFFF0000 dla czerwieni). Użyj do tego metody System.Windows.Clipboard.SetText. Stwórz korzystający z tego polecenia przycisk. Zwiąż je także z naciśnięciem kombinacji klawiszy Ctrl+C.

51 Rozdział 7. Zachowania, własności zależności i własności doczepione Zachowania (behaviors) Zachowania, podobnie jak mechanizm przekształcania zdarzeń w polecenia, zostały wprowadzony do XAML na potrzeby współpracy Visual Studio z Expression Blend. Dzięki nim, projektant korzystający z Blend może dodawać do kontrolek dodatkowe umiejętności bez potrzeby pisania kodu wystarczy że będzie dysponował wcześniej uzgodnionymi z programistami zachowaniami (ang. behaviors) rozszerzającymi możliwości kontrolek. Aby móc definiować zachowania, musimy do projektu dodać biblioteki: System.Windows.Interactivity.dll i Microsoft.Expression.Interaction.dll. To są te same biblioteki, których wymaga mechanizm przekształcania zdarzeń w polecenia omówiony w poprzednim rozdziale. Z punktu widzenia programisty, zachowania to nic innego jak klasa, która rozszerza wskazany typ kontrolki o pewne nowe możliwości. W pierwszych przykładach rozszerzać będziemy klasę okna Window. Bardzo prosty przykład pokazujący w jaki sposób można zdefiniować zachowanie dotyczące okna widoczny jest na listingu 7.1. Zachowanie to pozwala na wskazanie klawisza, którego naciśnięcie spowoduje zamknięcie okna. Będzie do tego służyła publiczna własność Klawisz typu Key. Oprócz tego w klasie zdefiniowana jest metoda zdarzeniowa window_previewkeydown, która zakłada, że nadawcą zdarzenia (parametr sender) jest obiekt reprezentujący okno. Wykorzystuje przesłaną referencję obiektu okna, aby je zamknąć. Metoda ta jest wiązana ze zdarzeniem PreviewKeyDown okna w metodzie OnAttached uruchamianej w momencie podłączania zachowania do rozszerzanego obiektu. Ten sposób definiowania zachowań nadaje się do przypadków, w których zachowanie dotyczy wyłącznie obiektu, do którego zostanie dodane w naszym przypadku okna. Referencja do tego obiektu, tzw. obiektu powiązanego, dostępna jest poprzez własność AssociatedObject zachowania, a jej typ określa parametr klasy bazowej Behavior. Listing 7.1. Definiowanie prostego zachowania public class ZamknięcieOknaPoNaciśnieciuKlawisza : Behavior<Window> public Key Klawisz get; set; protected override void OnAttached() Window window = this.associatedobject; if (window!= null) window.previewkeydown += window_previewkeydown; private void window_previewkeydown(object sender, KeyEventArgs e) Window window = (Window)sender; if (e.key == Klawisz) window.close();

52 Aby użyć zachowania, przejdźmy do kodu XAML widoku. Należy w nim zadeklarować przestrzeń nazw w której znajdują się klasy potrzebne do dodania zachowania. W naszym projekcie przestrzeń ta jest już obecna pod aliasem i ze względu na użyty wcześniej mechanizm przekształcania zdarzeń na polecenia. Pozostaje wobec tego stworzyć kolekcję zachowań i:interaction.behaviors i dodać do niej zachowanie ZamknięcieOknaPoNaciśnieciuKlawisza określając jednocześnie jakiego klawisza chcemy używać do zamykania aplikacji (listing 7.2). Listing 7.2. Dodawanie zachowania do kodu XAML <Window x:class="kolorywpf.mainwindow"... xmlns=" xmlns:x=" xmlns:local="clr-namespace:kolorywpf" xmlns:vm="clr-namespace:kolorywpf.modelwidoku" xmlns:i=" Title="Kolory WPF" Height="480" Width="640" > <i:interaction.triggers> <i:eventtrigger EventName="Closed"> <i:invokecommandaction Command="Binding Zapisz" /> </i:eventtrigger> </i:interaction.triggers> <i:interaction.behaviors> <local:zamknięcieoknaponaciśnieciuklawisza Klawisz="Escape" /> </i:interaction.behaviors> Własność zależności (dependency property) Wydaje mi się, że powyższy przykład jest dość łatwy do zrozumienia. Drugi będzie bardziej skomplikowany, bo wykorzystamy w nim nowy mechanizm zaprojektowany dla WPF o nazwie własność zależności (dependency property). Własności tego typu są powszechnie stosowane w WPF, w szczególności w klasach kontrolek dostępnych w XAML jako elementy. Atrybuty kontrolek są właśnie tego rodzaju własnościami. Używa się ich tak samo, jak zwykłych zależności zdefiniowanych w klasach kontrolek. Różnią się jednak sposobem przechowywania wartości. Podczas gdy zwykłe własności z reguły przechowują swoją wartość w prywatnych polach, własności zależności robią to w specjalnym słowniku zdefiniowanym w klasie DependencyObject. Właśnie po tej klasie dziedziczy klasa Behaviour klasa bazowa definiowanych przez nas zachowań. To jednak nie cała prawda, bo tak naprawdę w tym słowniku przechowywane są tylko te wartości własności zależności, których wartość została zmieniona. W ten sposób zmniejsza się ilość miejsca używanego przez własności kontrolek, co ma duże znaczenie w aplikacjach WPF zważywszy na to, że zwykle używamy tylko niewielkiej części spośród wszystkich własności kontrolek (czyli atrybutów elementów dodanych do kodu XAML) większość pozostaje przy swoich domyślnych wartościach. Ten mechanizm pozwala również na dziedziczenie wartości własności. Kontrolki WPF (elementy w kodzie XAML) dziedziczą wartości własności po elementach, w których zostały umieszczone. Dotyczy to na przykład ich formatowania (kolor tła, cechy czcionki itp.), ale również wszystkich innych, choćby kontekstu wiązania danych. Mechanizm własności zależności widzi relacje zawierania elementów XAML. Jeżeli w danym elemencie został użyty atrybut, to wykorzystana będzie oczywiście wskazana w nim wartość. Jednak jeżeli w elemencie nie ma przypisania wartości atrybutu, mechanizm własności zależności potrafi odnaleźć ją w nadrzędnych elementach XAML, a jeżeli takiej nie znajdzie - skorzystać z wartości domyślnej. Ponadto własności zależności używają mechanizmu powiadamiania o zmianach wartości, co jest kluczowe w kontekście wiązania danych. Sprawdźmy jak wygląda definiowanie tego typu własności na przykładzie drugiego zachowania (listing 7.2). Zachowanie to zakłada, że w oknie znajduje się przycisk 3. Należy go wskazać we własności Przycisk tego 3 To zachowanie jest wzorowane na przykładzie, który znalazłem na stronie

53 zachowania. Ta własność będzie właśnie własnością zależności. Wartość domyślna tej własności będzie równa null, co oznacza, że zachowanie będzie w istocie nieaktywne. Jeżeli jednak przypiszemy własności Przycisk jakąś wartość, a konkretnie referencję istniejącego przycisku, to wykonana zostanie metoda PrzyciskZmieniony, która zwiąże ze zdarzeniem Click tego przycisku metodę, która zamyka okno, do którego dodamy projektowane zachowanie. Jednocześnie usuwane jest wiązanie zdarzenia Click z przycisku będącego poprzednią wartością własności Przycisk. To może się zdarzyć, gdy własność tą zmienimy np. w code-behind. Listing 7.2. Definiowanie zachowania opartego na własności zalezności public class PrzyciskZamykającyOkno : Behavior<Window> public static readonly DependencyProperty PrzyciskProperty = DependencyProperty.Register( ); "Przycisk", typeof(button), typeof(przyciskzamykającyokno), new PropertyMetadata(null, PrzyciskZmieniony) public Button Przycisk get return (Button)GetValue(PrzyciskProperty); set SetValue(PrzyciskProperty, value); private static void PrzyciskZmieniony(DependencyObject d, DependencyPropertyChangedEventArgs e) Window window = (d as PrzyciskZamykającyOkno).AssociatedObject; RoutedEventHandler button_click = (object sender, RoutedEventArgs _e) => window.close(); ; if (e.oldvalue!= null) ((Button)e.OldValue).Click -= button_click; if (e.newvalue!= null) ((Button)e.NewValue).Click += button_click; Na listingu 7.2 widoczna jest zwykła własność Przycisk, która będzie widoczna jako atrybut zachowania. Jej wartość jest odczytywana i zmieniana za pomocą metod SetValue i GetValue zdefiniowanych w klasie DependencyObject. Tej samej nazwy tj. Przycisk używamy rejestrując własność zależności metodą DependencyProperty.Register. Wartość, jaką uzyskamy zapisujemy w statycznym polu PrzyciskProperty. To pole musi być statyczne, bo odnosi się do statycznych elementów klasy DependencyObject, m.in. do zdefiniowanego w nim słownika przechowującego wartość własności zależności. Argumentami metody Register są: nazwa własności, jej typ, typ właściciela (w naszym przypadku zachowania) oraz dodatkowe dane obiekt typu PropertyMetadata. Ten ostatni daje nam możliwość określenia wartości domyślnej własności (w naszym przypadku jest ona równa null) oraz metody, która będzie wykonywana, gdy wartość własności zostanie zmieniona. My użyliśmy metody PrzyciskZmieniony, której działanie opisałem wyżej. Wróćmy do kodu XAML. Aby użyć nowego zachowania, należy do siatki, w której umieszczone są wszystkie kontrolki dodać jeszcze jeden przycisk: <Button x:name="przyciskzamknij" Content="Zamknij" Height="25" Width="75" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="100,0,0,10" />

54 Ważne jest, żeby został nazwany. Ja użyłem nazwy przyciskzamknij. Następnie do zbioru zachowań, który mamy już zdefiniowany, dodajemy nowe zachowanie typu PrzyciskZamykającyOkno: <i:interaction.behaviors> <local:zamknięcieoknaponaciśnieciuklawisza Klawisz="Escape" /> <local:przyciskzamykającyokno x:name="przyciskzamykającyokno" </i:interaction.behaviors> Przycisk="Binding ElementName=przyciskZamknij" /> W zdarzeniu tym wiążemy z atrybutem Przycisk dodany wcześniej przycisk o nazwie przyciskzamknij. To powoduje, że wykonana zostanie metoda PrzyciskZamykającyOkno.PrzyciskZmieniony, która ze zdarzeniem Click przycisku wiąże metodę zamykającą bieżące okno. Możemy się o tym łatwo przekonać uruchamiając aplikację i klikając ten przycisk. Idąc za ciosem zdefiniujmy w tym zachowaniu jeszcze dwie własności: Polecenie i ParametrPolecenia. Pierwsze pozwoli na ustalenie polecenia wykonywanego przed zamknięciem okna (ale tylko w przypadku, gdy okno zamykamy za pomocą przycisku) oraz argumentu przesyłanego do tego polecenia. Klasa zachowania z dodanymi elementami widoczna jest na listingu 7.3. Teraz oprócz własności Przycisk w kodzie XAML możemy przypisać także atrybuty Polecenie i ParametrPolecenie. Do przetestowania nowych możliwości możemy użyć np. polecenia Resetuj zdefiniowanego w poprzednim rozdziale. Listing 7.3. Dodatkowe własności zależności zdefiniowane w zachowaniu public class PrzyciskZamykającyOkno : Behavior<Window> public static readonly DependencyProperty PrzyciskProperty = DependencyProperty.Register( ); "Przycisk", typeof(button), typeof(przyciskzamykającyokno), new PropertyMetadata(null, PrzyciskZmieniony) public Button Przycisk get return (Button)GetValue(PrzyciskProperty); set SetValue(PrzyciskProperty, value); public static readonly DependencyProperty PolecenieProperty = DependencyProperty.Register( "Polecenie", typeof(icommand), typeof(przyciskzamykającyoknozpoleceniem)); public ICommand Polecenie get return (ICommand)GetValue(PolecenieProperty); set SetValue(PolecenieProperty, value); public static readonly DependencyProperty ParametrPoleceniaProperty = DependencyProperty.Register(

55 "ParametrPolecenia", typeof(object), typeof(przyciskzamykającyoknozpoleceniem)); public object ParametrPolecenia get return (object)getvalue(parametrpoleceniaproperty); set SetValue(ParametrPoleceniaProperty, value); private static void PrzyciskZmieniony(DependencyObject d, DependencyPropertyChangedEventArgs e) Window window = (d as PrzyciskZamykającyOknoZPoleceniem).AssociatedObject; RoutedEventHandler button_click = (object sender, RoutedEventArgs _e) => ICommand polecenie = (d as PrzyciskZamykającyOknoZPoleceniem).Polecenie; object parametrpolecenia = (d as PrzyciskZamykającyOknoZPoleceniem).ParametrPolecenia; if (polecenie!= null) polecenie.execute(parametrpolecenia); window.close(); ; if (e.oldvalue!= null) ((Button)e.OldValue).Click -= button_click; if (e.newvalue!= null) ((Button)e.NewValue).Click += button_click; Własność doczepiona (attached property) i zachowanie doczepione (attached behavior) Znając własności zależności, możemy przyjrzeć się także kolejnej koncepcji XAML i WPF, a mianowicie własnościom doczepianym (ang. attached property). Pozwalają one na przekazywanie własności zdefiniowanych w elemencie nadrzędnym do elementów-dzieci. Dobrym przykładem jest np. własność Dock z pojemnika DockPanel 4 lub Grid.Row i Grid.Column z pojemnika Grid. Siatka (element Grid) sprawdza, czy umieszczone w niej elementy mają ustawione własności Grid.Row lub Grid.Column i umieszcza je w podanych w tych własnościach komórkach. Jeżeli ich nie ma, używa domyślnych wartości, czyli zer. Tworzenie własności doczepianych zazwyczaj związane jest z ułożeniem kontrolek i realizowane jest przede wszystkim na rzecz kontrolek-pojemników. Aby zdefiniować własną własność doczepianą, należy zdefiniować statyczne pole typu DependencyProperty, które przechowuje wartość zwrócona przez metodę DependencyProperty.RegisterAttached. Metoda ta rejestruje własność zależności (analogicznie jak własność Przycisk z listingu 7.2). Oprócz tego należy zdefiniować dwie statyczne metody: SetNazwaWłasności i GetNazwaWłasności. Jeżeli te elementy zamkniemy w osobnej klasie statycznej, uzyskamy zachowanie doczepiane (ang. attached behavior). Przykład takiego kompletu widoczny jest na listingu 7.4. W przykładzie tym dodajemy do kontrolek własność Klawisz. Przypisując ją w kodzie XAML, wiążemy ze zdarzeniem PreviewKeyDown tej kontrolki metodę zdarzeniową 4 Por. omówienie na stronie

56 zdefiniowaną w wyrażeniu Lambda. Jeżeli tą kontrolką jest całe okno, to przypisujemy mu metodę zamykającą to okno. W pozostałych przypadkach przełączamy własność IsEnabled kontrolek na false. Listing 7.4. Definiowanie zachowania doczepionego public static class KlawiszWyłączBehavior public static Key GetKlawisz(DependencyObject d) return (Key)d.GetValue(KlawiszProperty); public static void SetKlawisz(DependencyObject d, Key value) d.setvalue(klawiszproperty, value); public static readonly DependencyProperty KlawiszProperty = DependencyProperty.RegisterAttached( "Klawisz", typeof(key), typeof(klawiszwyłączbehavior), new PropertyMetadata(Key.None, KlawiszZmieniony)); private static void KlawiszZmieniony(DependencyObject d, DependencyPropertyChangedEventArgs e) Key klawisz = (Key)e.NewValue; if(d is Window) else (d as Window).PreviewKeyDown += (object sender, KeyEventArgs _e) => ; if (_e.key == klawisz) (sender as Window).Close(); (d as UIElement).PreviewKeyDown += (object sender, KeyEventArgs _e) => ; if (_e.key == klawisz) (sender as UIElement).IsEnabled = false;

57 Tego typu zachowania nie trzeba dołączać do kolekcji zachowań, której używaliśmy do tej pory. Zachowania te dodają do kontrolek dodatkowy atrybut. Przykłady ich użycia pokazuje listing 7.5. Proszę zwrócić uwagę, że aby po uruchomieniu aplikacji móc nacisnąć przycisk na rzecz suwaka, trzeba go wpierw kliknąć, aby uzyskał focus. Podobnie jest w przypadku siatki. Listing 7.5. Kod XAML z zaznaczonymi przykładami użycia zachowań doczepionych <Window x:class="kolorywpf.mainwindow" xmlns:local="clr-namespace:kolorywpf"... local:klawiszwyłączbehavior.klawisz="q" > <Grid local:klawiszwyłączbehavior.klawisz="w">... <Slider x:name="sliderr" Margin="10,0,40,86"... </Grid> </Window> Height="18" VerticalAlignment="Bottom" Maximum="255" Value="Binding R, Mode=TwoWay, Converter=StaticResource konwersjabytedouble" local:klawiszwyłączbehavior.klawisz="e" /> Zadanie Zmodyfikuj zachowanie ZamknięcieOknaPoNaciśnieciuKlawisza w taki sposób, żeby własność Klawisz była własnością zależności i żeby metoda OnAttached przestała być używana. Pominięty rozdział 7 pt. Tworzenie testów jednostkowych dla modeli i modeli widoków

58 Rozdział 8. Powtórzenie W ramach powtórzenia podstawowych wiadomości o MVVM przygotujemy aplikację, której zadaniem będzie sumowanie cen towarów wrzucanych do koszyka w supermarkecie. Na razie przygotujemy ją w WPF, co oczywiście nie ma większego sensu, ale w kolejnej części przeniesiemy ją na Windows Phone i Windows RT. Widok aplikacji będzie składał się z kontrolki TextBlock, która będzie wyświetlała bieżącą wartość sumy, kontrolki TextBox, która będzie pozwalała na wpisanie ceny dokładanego produktu i przycisku, który będzie ową cenę dodawał do sumy. W pierwszej wersji aplikacja będzie miała stały limit wydatków równy zł, którego nie będzie można przekroczyć. Aby zadanie uprościć, aplikacja w wersji WPF nie będzie przechowywać swojego stanu. Model Zacznijmy od modelu. Przypominam, że powinien on być łatwy do testowania, co oznacza m.in. brak zależności (oczywiście jeżeli to jest możliwe). Musimy zdecydować czy ma jedynie opisywać elementy odwzorowywanego świata, jak było w aplikacji KoloryWPF, czy też zawierać jakąś logikę. To oznacza m.in. decyzję, czy model ponosi odpowiedzialność za poprawność i zapisywanie danych, czy też wyłączamy te czynności do innych klas. Tym razem proponuję wyposażyć klasę modelu w mechanizmy walidacji danych. Model będzie znowu wyjątkowo prosty, ponieważ i tym razem składa się tylko z jednej klasy SumowanieKwot (listing 9.1). Klasa ta udostępnia dwie publiczne własności o nazwach Suma i Limit, ale możliwość ich modyfikacji ograniczona jest do zakresu prywatnego. Ich wartości inicjowane są w konstruktorze klasy. Poza tym zdefiniowane są w niej dwie publiczne metody: Dodaj i CzyKwotaJestPoprawna. Argumentem obu jest kwota. Pierwsza zwiększa wartość sumy, a druga pozwala na sprawdzenie czy dodanie wskazanej kwoty jest jeszcze możliwe, tzn. czy po dodaniu suma nie przekroczy ustalonego w konstruktorze limitu. Do przechowywania sumy, limitu i przekazywania kwot używamy typu decimal, który właśnie do operacji na walutach został stworzony. Jedyną przestrzenią nazw, jakiej wymaga kod tej klasy jest System. W niej zdefiniowany jest wyjątek ArgumentOutOfRangeException, który zgłaszamy w wypadku gdy dodanie nowej kwoty spowodowałoby, że suma przekroczy limit. Listing 9.1. Klasa modelu using System; namespace AsystentSklepowyWPF.Model public class SumowanieKwot public decimal Limit get; private set; public decimal Suma get; private set; public SumowanieKwot(decimal limit, decimal suma = 0) this.limit = limit; this.suma = suma; public void Dodaj(decimal kwota)

59 if (!CzyKwotaJestPoprawna(kwota)) throw new ArgumentOutOfRangeException("Kwota zbyt duża lub ujemna"); Suma += kwota; public bool CzyKwotaJestPoprawna(decimal kwota) bool czydodatnia = kwota > 0; bool czyprzekroczylimit = Suma + kwota > Limit; return czydodatnia &&!czyprzekroczylimit; Prototyp widoku Następnie zdefiniujmy widok. To, czy pierwszy projektowany jest model czy widok, zależy w zasadzie od preferencji programistów i ścieżki, jaką podąży rozmowa z osobą zamawiającą projekt. Ja lubię zaczynać od widoku, bo wówczas łatwo jest omawiać przypadku użycia, które ułatwiają ustalenie, czego klient tak naprawdę potrzebuje. TextBlock TextBox, tbkwota Button, tbdodaj Rysunek 9.1. Interfejs aplikacji Trzy kontrolki widoku będą ułożone w trzech wierszach siatki (rysunek 9.1). W pierwszym będzie TextBlock, w którym chcemy wyświetlać sumę obliczoną w modelu. Druga kontrolka to pole edycyjne TextBox pozwalająca na wpisanie kwoty. Natomiast trzecia to przycisk, którym będziemy uruchamiali dodawanie wpisanej kwoty do sumy. Cały kod widoczny jest na listingu 9.2. Proszę zwrócić uwagę, że ograniczyłem interfejs do minimum. W szczególności pole tekstowe nie ma nawet etykiety podpowiadającej jego przeznaczenie. Listing 9.2. Pierwsza wersja widoku <Window x:class="asystentsklepowywpf.mainwindow" <Grid> xmlns=" xmlns:x=" Title="Asystent sklepowy" Height="200" Width="200"> <Grid.RowDefinitions>

60 <RowDefinition Height="1*" /> <RowDefinition Height="1*" /> <RowDefinition Height="1*" /> </Grid.RowDefinitions> <TextBlock HorizontalAlignment="Left" Grid.Row="0" FontSize="25" Foreground="Navy" Margin="10,10,10,10"> Suma: <Run Foreground="Black" Text="0" FontFamily="Courier New" /> </TextBlock> <TextBox x:name="tbkwota" FontSize="25" FontFamily="Courier New" TextAlignment="Right" Margin="10,10,10,10" Grid.Row="1" Text="0" /> <Button x:name="btndodaj" Content="Dodaj" FontSize="25" Margin="10,10,10,10" Grid.Row="2" /> </Grid> </Window> Model widoku Model widoku również nie będzie zbyt skomplikowany. Będzie udostępniał jedną własność Suma i jedno polecenie DodajKwotę, a także metodę sprawdzającą czy łańcuch zawiera poprawną kwotę. Poza tym oczywiście implementuje interfejs INotifyPropertyChanged. Nietypowy, i nie do końca kanoniczny, jest ukłon modelu widoku w stronę widoku polegający na tym, że zamiast typu decimal użytego w modelu, używamy łańcuchów. Własność Suma jest typu string, a polecenie DodajKwotę akceptuje argument będący łańcuchem. Dzięki temu unikniemy jednak definiowania jednego konwertera, ale co ważniejsze, możemy przeprowadzić weryfikację kwoty w metodzie CanExecute polecenia. Argument polecenia, zarówno w przypadku metody Execute, jak i CanExecute, będzie używany, co oznacza, że będziemy musieli go uwzględnić w wiązaniu przycisku. Pełen kod modelu widoku widoczny jest na listingu 9.3. Do projektu należy dodać plik z klasą RelayCommand (por. listing 6.8), której używamy przy definiowaniu polecenia. Listing 9.3. Klasa modelu widoku using System; using System.Windows.Input; namespace AsystentSklepowyWPF.ModelWidoku using Model; using System.ComponentModel; using System.Windows; class ModelWidoku : INotifyPropertyChanged private SumowanieKwot model = new SumowanieKwot(1000); public string Suma get return model.suma.tostring();

61 public event PropertyChangedEventHandler PropertyChanged; private void OnpropertyChanged(string nazwawłasnosci) if (PropertyChanged!= null) PropertyChanged(this, new PropertyChangedEventArgs(nazwaWłasnosci)); public bool CzyŁańcuchKwotyJestPoprawny(string s) if (string.isnullorwhitespace(s)) return false; decimal kwota; if (!decimal.tryparse(s, out kwota)) return false; else return model.czykwotajestpoprawna(kwota); private ICommand dodajkwotęcommand; public ICommand DodajKwotę get if (dodajkwotęcommand == null) dodajkwotęcommand = new RelayCommand( (object argument) => try decimal kwota = decimal.parse((string)argument); model.dodaj(kwota); OnpropertyChanged("Suma"); catch (Exception exc) MessageBox.Show(exc.Message, "Asystent sklepowy", MessageBoxButton.OK, MessageBoxImage.Error); );, (object argument) => return CzyŁańcuchKwotyJestPoprawny((string)argument);

62 return dodajkwotęcommand; Wiązanie Najtrudniejszym zadaniem w aplikacji opartej na wzorcu MVVM jest ustalenie jak powinien wyglądać model widoku. A najprzyjemniejszym wiązanie do niego widoku, jednak tylko pod warunkiem, że model widoku został dobrze zaprojektowany. Zacznijmy od utworzenia instancji modelu widoku i przypisania jej do własności DataContext okna. Następnie własność Suma zwiążmy z przebiegiem (pod-elemenent Run) elementu TextBlock. Przycisk zwiążmy z poleceniem DodajKwotę. Parametrem tego wiązania będzie własność Text pola edycyjnego (wiązanie między kontrolkami). Zmiany w widoku pokazuje listing 9.4. Alternatywnym rozwiązaniem byłoby dodanie do modelu widoku własności udostępniającej kwotę, związanie z nią pole edycyjnego i odwoływanie do niego w metodach Execute i CanExecute polecenia. Listing 9.4. Wiązania dodane w widoku <Window x:class="asystentsklepowywpf.mainwindow" xmlns=" xmlns:x=" xmlns:l="clr-namespace:asystentsklepowywpf" xmlns:mw="clr-namespace:asystentsklepowywpf.modelwidoku" Title="Asystent sklepowy" Height="200" Width="200"> <Window.DataContext> <mw:modelwidoku /> </Window.DataContext> <Grid> </Grid> </Window> <Grid.RowDefinitions> <RowDefinition Height="1*" /> <RowDefinition Height="1*" /> <RowDefinition Height="1*" /> </Grid.RowDefinitions> <TextBlock HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="25" Suma: Foreground="Navy" Grid.Row="0" Margin="10,10,10,10"> <Run Foreground="Black" FontFamily="Courier New" </TextBlock> Text="Binding Path=Suma, Mode=OneWay" /> <TextBox x:name="tbkwota" FontSize="30" FontFamily="Courier New" TextAlignment="Right" Margin="10,10,10,10" Grid.Row="1" Text="0" /> <Button x:name="btndodaj" Content="Dodaj" FontSize="20" Margin="10,10,10,10" Grid.Row="2" Command="Binding DodajKwotę" CommandParameter="Binding ElementName=tbKwota, Path=Text" />

63 Konwerter Dzięki użyciu własności CanExecute i menedżera poleceń w definicji użytej przez nas klasy RelayCommand, przycisk Dodaj staje się nieaktywny, gdy wpisana w polu edycyjnym łańcuch nie jest poprawną liczbą, lub oznacza ujemną kwotę lub taką, która spowodowałaby przekroczenie limitu. Nieaktywność przycisku uniemożliwia próby przekroczenia limitu, nie wskazuje jednak na źródło błędu użytkownik może być zdezorientowany. Chciałbym wobec tego dodatkowo podkreślić niepoprawność łańcucha zmianą koloru czcionki w polu tekstowym. W tym celu nie musimy wcale dodawać kodu do modelu widoku. Wystarczy, jeżeli ten kolor zwiążemy z kontrolowaną przez menedżera poleceń własnością IsEnabled przycisku. Do tego potrzebujemy jednak konwertera wielkości typu bool do Brush (listing 9.5). Listing 9.5. Klasa konwertera using System; using System.Windows.Data; using System.Windows.Media; namespace AsystentSklepowyWPF class BoolToBrushConverter : IValueConverter public object Convert(object value, Type targettype, object parameter, System.Globalization.CultureInfo culture) bool b = (bool)value; return b? Brushes.Black : Brushes.Red; public object ConvertBack(object value, Type targettype, object parameter, System.Globalization.CultureInfo culture) throw new NotImplementedException(); Dzięki niemu, możemy do elementu TextBox dodać wiązanie z przyciskiem: <TextBox x:name="tbkwota" FontSize="30" FontFamily="Courier New" TextAlignment="Right" Margin="10,10,10,10" Grid.Row="1" Text="0" Foreground="Binding ElementName=btnDodaj, Path=IsEnabled, Mode=OneWay, Converter=StaticResource booltobrush" /> To powoduje, że aplikacja swoim wyglądem wyraźnie sygnalizuje niepoprawną kwotę wpisaną w polu edycyjnym (rysunek 9.2).

64 Rysunek 9.2. Interfejs aplikacji z poprawną i niepoprawną kwotą Wzorzec MVVM Wielką zaletą wzorca MVVM jest wyraźne rozdzielenie warstw: model kontaktuje się tylko z modelem widoku, z kolei model widoku dowiązany jest do widoku. Taka modułowość wspaniale ułatwia pisanie aplikacji, a także jej przebudowywanie. Zmiany związane z poprawianiem kodu są bowiem zazwyczaj ograniczone do jednej z warstw i nie powinny wymagać modyfikacji pozostałych. Proponuję wrócić teraz na chwilę do rozdziału 2 i jeszcze raz go przeczytać, w szczególności akapit dotyczący modelu widoku. Warto przy tym mieć na uwadze rysunek 9.1 (por. rysunek 2.1). W rozdziałach 5 i 7 poznaliśmy dwie konstrukcje, które rozszerzają strukturę aplikacji MVVM przedstawioną w rozdziale 2. Mam na myśli konwertery i zachowania. Konwertery służą do uzgadniania interfejsu modelu widoku z kontrolkami XAML, co jest czasem niezbędne do ich związania. Zachowania zastępują metody zdarzeniowe, pozwalając na dowolne wzbogacanie możliwości kontrolek. Klasy definiujące oba te mechanizmy są częścią warstwy widoku, można w nich bowiem używać klas kontrolek i innych typów właściwych dla widoku. Co za tym idzie, ich testowanie nie jest tak łatwe, jak klas modelu lub modelu widoku. Rysunek 9.1. Struktura aplikacji MVVM Zadania 1. Przygotuj konwerter między typami string i decimal. W modelu widoku zmień typy własności Suma i argumentu polecenie DodajKwotę na decimal. Użyj konwertera do wiązania tej własności i polecenia w widoku. 2. Do widoku dodaj etykietę TextBlock, której użyj do informowania o błędach aplikacji. W modelu widoku zdefiniuj własność InformacjaOBłędzie typu string. Zwiąż z nią nową etykietę. 3. Przygotuj zestaw testów jednostkowych dla modelu i modelu widoku.

MVVM i XAML w Visual Studio 2015 / Jacek Matulewski. Gliwice, cop Spis treści

MVVM i XAML w Visual Studio 2015 / Jacek Matulewski. Gliwice, cop Spis treści MVVM i XAML w Visual Studio 2015 / Jacek Matulewski. Gliwice, cop. 2016 Spis treści Część I Wzorzec MVVM. Podstawy XAML 7 Rozdział 1. Szybkie wprowadzenie do XAML 9 Wzorzec widoku autonomicznego 9 Tworzenie

Bardziej szczegółowo

Rozdział 3. Zapisywanie stanu aplikacji w ustawieniach lokalnych

Rozdział 3. Zapisywanie stanu aplikacji w ustawieniach lokalnych Rozdział 3. Zapisywanie stanu aplikacji w ustawieniach lokalnych Jacek Matulewski Materiały dla Podyplomowego Studium Programowania i Zastosowania Komputerów, sekcja Projektowanie i tworzenie aplikacji

Bardziej szczegółowo

Utworzenie aplikacji mobilnej Po uruchomieniu Visual Studio pokazuje się ekran powitalny. Po lewej stronie odnośniki do otworzenia lub stworzenia

Utworzenie aplikacji mobilnej Po uruchomieniu Visual Studio pokazuje się ekran powitalny. Po lewej stronie odnośniki do otworzenia lub stworzenia Utworzenie aplikacji mobilnej Po uruchomieniu Visual Studio pokazuje się ekran powitalny. Po lewej stronie odnośniki do otworzenia lub stworzenia nowego projektu (poniżej są utworzone projekty) Po kliknięciu

Bardziej szczegółowo

WYKONANIE APLIKACJI OKIENKOWEJ OBLICZAJĄCEJ SUMĘ DWÓCH LICZB W ŚRODOWISKU PROGRAMISTYCZNYM. NetBeans. Wykonał: Jacek Ventzke informatyka sem.

WYKONANIE APLIKACJI OKIENKOWEJ OBLICZAJĄCEJ SUMĘ DWÓCH LICZB W ŚRODOWISKU PROGRAMISTYCZNYM. NetBeans. Wykonał: Jacek Ventzke informatyka sem. WYKONANIE APLIKACJI OKIENKOWEJ OBLICZAJĄCEJ SUMĘ DWÓCH LICZB W ŚRODOWISKU PROGRAMISTYCZNYM NetBeans Wykonał: Jacek Ventzke informatyka sem. VI 1. Uruchamiamy program NetBeans (tu wersja 6.8 ) 2. Tworzymy

Bardziej szczegółowo

Programowanie obiektowe

Programowanie obiektowe Laboratorium z przedmiotu Programowanie obiektowe - zestaw 07 Cel zajęć. Celem zajęć jest zapoznanie z praktycznymi aspektami tworzenia aplikacji okienkowych w C#. Wprowadzenie teoretyczne. Rozważana w

Bardziej szczegółowo

- Narzędzie Windows Forms. - Przykładowe aplikacje. Wyższa Metody Szkoła programowania Techniczno Ekonomiczna 1 w Świdnicy

- Narzędzie Windows Forms. - Przykładowe aplikacje. Wyższa Metody Szkoła programowania Techniczno Ekonomiczna 1 w Świdnicy Wyższa Metody Szkoła programowania Techniczno Ekonomiczna 1 w Świdnicy - Narzędzie Windows Forms - Przykładowe aplikacje 1 Narzędzia Windows Form Windows Form jest narzędziem do tworzenia aplikacji dla

Bardziej szczegółowo

Programowanie obiektowe i zdarzeniowe wykład 4 Kompozycja, kolekcje, wiązanie danych

Programowanie obiektowe i zdarzeniowe wykład 4 Kompozycja, kolekcje, wiązanie danych Programowanie obiektowe i zdarzeniowe wykład 4 Kompozycja, kolekcje, wiązanie danych Obiekty reprezentują pewne pojęcia, przedmioty, elementy rzeczywistości. Obiekty udostępniają swoje usługi: metody operacje,

Bardziej szczegółowo

Tworzenie prezentacji w MS PowerPoint

Tworzenie prezentacji w MS PowerPoint Tworzenie prezentacji w MS PowerPoint Program PowerPoint dostarczany jest w pakiecie Office i daje nam możliwość stworzenia prezentacji oraz uatrakcyjnienia materiału, który chcemy przedstawić. Prezentacje

Bardziej szczegółowo

e-podręcznik dla seniora... i nie tylko.

e-podręcznik dla seniora... i nie tylko. Pliki i foldery Czym są pliki? Plik to w komputerowej terminologii pewien zbiór danych. W zależności od TYPU pliku może to być: obraz (np. zdjęcie z imienin, rysunek) tekst (np. opowiadanie) dźwięk (np.

Bardziej szczegółowo

5.4. Tworzymy formularze

5.4. Tworzymy formularze 5.4. Tworzymy formularze Zastosowanie formularzy Formularz to obiekt bazy danych, który daje możliwość tworzenia i modyfikacji danych w tabeli lub kwerendzie. Jego wielką zaletą jest umiejętność zautomatyzowania

Bardziej szczegółowo

Adobe InDesign lab.1 Jacek Wiślicki, Paweł Kośla. Spis treści: 1 Podstawy pracy z aplikacją Układ strony... 2.

Adobe InDesign lab.1 Jacek Wiślicki, Paweł Kośla. Spis treści: 1 Podstawy pracy z aplikacją Układ strony... 2. Spis treści: 1 Podstawy pracy z aplikacją... 2 1.1 Układ strony... 2 strona 1 z 7 1 Podstawy pracy z aplikacją InDesign jest następcą starzejącego się PageMakera. Pod wieloma względami jest do niego bardzo

Bardziej szczegółowo

Jak przygotować pokaz album w Logomocji

Jak przygotować pokaz album w Logomocji Logomocja zawiera szereg ułatwień pozwalających na dość proste przygotowanie albumu multimedialnego. Najpierw należy zgromadzić potrzebne materiały, najlepiej w jednym folderze. Ustalamy wygląd strony

Bardziej szczegółowo

using System;... using System.Threading;

using System;... using System.Threading; Kontekst synchronizacji Wątek w platformie.net może posiadać kontekst synchronizacji reprezentowany przez instancję klasy SynchronizationContext lub jej klasy potomnej. Jeżeli wątek posiada taki kontekst

Bardziej szczegółowo

Informatyka II. Laboratorium Aplikacja okienkowa

Informatyka II. Laboratorium Aplikacja okienkowa Informatyka II Laboratorium Aplikacja okienkowa Założenia Program będzie obliczał obwód oraz pole trójkąta na podstawie podanych zmiennych. Użytkownik będzie poproszony o podanie długości boków trójkąta.

Bardziej szczegółowo

xmlns:prism=http://www.codeplex.com/prism c. <ContentControl prism:regionmanager.regionname="mainregion" />

xmlns:prism=http://www.codeplex.com/prism c. <ContentControl prism:regionmanager.regionname=mainregion /> 1 Tworzenie Shella a. W pierwszej kolejności tworzymy nowy projekt: WPF Application. Name: Shell SolutionName: PrismApp b. Dodajemy bibliotekę PRISM za pomocą NuGet Managera (dla.net Framework 4.5 Prism

Bardziej szczegółowo

Obsługa programu Paint. mgr Katarzyna Paliwoda

Obsługa programu Paint. mgr Katarzyna Paliwoda Obsługa programu Paint. mgr Katarzyna Paliwoda Podstawowo program mieści się w Systemie a dojście do niego odbywa się przez polecenia: Start- Wszystkie programy - Akcesoria - Paint. Program otwiera się

Bardziej szczegółowo

Instrukcja laboratoryjna cz.3

Instrukcja laboratoryjna cz.3 Języki programowania na platformie.net cz.2 2015/16 Instrukcja laboratoryjna cz.3 Język C++/CLI Prowadzący: Tomasz Goluch Wersja: 2.0 I. Utworzenie projektu C++/CLI z interfejsem graficznym WPF 1 Cel:

Bardziej szczegółowo

Kolory elementów. Kolory elementów

Kolory elementów. Kolory elementów Wszystkie elementy na schematach i planach szaf są wyświetlane w kolorach. Kolory te są zawarte w samych elementach, ale w razie potrzeby można je zmienić za pomocą opcji opisanych poniżej, przy czym dotyczy

Bardziej szczegółowo

SYSTEMY OPERACYJNE I SIECI KOMPUTEROWE

SYSTEMY OPERACYJNE I SIECI KOMPUTEROWE SYSTEMY OPERACYJNE I SIECI KOMPUTEROWE WINDOWS 1 SO i SK/WIN 006 Wydajność systemu 2 SO i SK/WIN Najprostszym sposobem na poprawienie wydajności systemu, jeżeli dysponujemy zbyt małą ilością pamięci RAM

Bardziej szczegółowo

Modelowanie obiektowe - Ćw. 1.

Modelowanie obiektowe - Ćw. 1. 1 Modelowanie obiektowe - Ćw. 1. Treść zajęć: Zapoznanie z podstawowymi funkcjami programu Enterprise Architect (tworzenie nowego projektu, korzystanie z podstawowych narzędzi programu itp.). Enterprise

Bardziej szczegółowo

MVVM Light Toolkit. Julita Borkowska

MVVM Light Toolkit. Julita Borkowska MVVM Light Toolkit Julita Borkowska Czym jest MVVM Light Toolkit? MVVM Light Toolkit został stworzony w 2009 roku przez Laurenta Bugnion. Jest to biblioteka dostarczająca zestaw komponentów pomocnych podczas

Bardziej szczegółowo

Fragment tekstu zakończony twardym enterem, traktowany przez edytor tekstu jako jedna nierozerwalna całość.

Fragment tekstu zakończony twardym enterem, traktowany przez edytor tekstu jako jedna nierozerwalna całość. Formatowanie akapitu Fragment tekstu zakończony twardym enterem, traktowany przez edytor tekstu jako jedna nierozerwalna całość. Przy formatowaniu znaków obowiązywała zasada, że zawsze przez rozpoczęciem

Bardziej szczegółowo

Laboratorium Systemów Operacyjnych

Laboratorium Systemów Operacyjnych Laboratorium Systemów Operacyjnych Użytkownicy, Grupy, Prawa Tworzenie kont użytkowników Lokalne konto pozwala użytkownikowi na uzyskanie dostępu do zasobów lokalnego komputera. Konto domenowe pozwala

Bardziej szczegółowo

Jarosław Kuchta Podstawy Programowania Obiektowego. Podstawy grafiki obiektowej

Jarosław Kuchta Podstawy Programowania Obiektowego. Podstawy grafiki obiektowej Jarosław Kuchta Podstawy Programowania Obiektowego Podstawy grafiki obiektowej Zagadnienia Grafika proceduralna grafika obiektowa Grafika WPF dualizm XAML C# Właściwości obiektów graficznych edycja właściwości

Bardziej szczegółowo

Formularze w programie Word

Formularze w programie Word Formularze w programie Word Formularz to dokument o określonej strukturze, zawierający puste pola do wypełnienia, czyli pola formularza, w których wprowadza się informacje. Uzyskane informacje można następnie

Bardziej szczegółowo

Papyrus. Papyrus. Katedra Cybernetyki i Robotyki Politechnika Wrocławska

Papyrus. Papyrus. Katedra Cybernetyki i Robotyki Politechnika Wrocławska Katedra Cybernetyki i Robotyki Politechnika Wrocławska Kurs: Zaawansowane metody programowania Copyright c 2014 Bogdan Kreczmer Niniejszy dokument zawiera materiały do wykładu dotyczącego programowania

Bardziej szczegółowo

Programowanie obiektowe i zdarzeniowe wykład 1 Wprowadzenie do programowania zdarzeniowego

Programowanie obiektowe i zdarzeniowe wykład 1 Wprowadzenie do programowania zdarzeniowego Programowanie obiektowe i zdarzeniowe wykład 1 Wprowadzenie do programowania zdarzeniowego 1/34 Wymagania wstępne: Znajomość podstaw programowania. Efekty kształcenia: Umiejętność tworzenia prostych aplikacji

Bardziej szczegółowo

Programowanie obiektowe

Programowanie obiektowe Programowanie obiektowe Wykład 7 Marcin Młotkowski 8 kwietnia 2015 Plan wykładu Z życia programisty, część 1 1 Z życia programisty, część 1 2 3 Z życia programisty, część 2 Model View Controller MVC w

Bardziej szczegółowo

PROBLEMY TECHNICZNE. Co zrobić, gdy natrafię na problemy związane z użytkowaniem programu DYSONANS

PROBLEMY TECHNICZNE. Co zrobić, gdy natrafię na problemy związane z użytkowaniem programu DYSONANS PROBLEMY TECHNICZNE Co zrobić, gdy natrafię na problemy związane z użytkowaniem programu DYSONANS Jeżeli stwierdziłeś występowanie błędów lub problemów podczas pracy z programem DYSONANS możesz skorzystać

Bardziej szczegółowo

Celem ćwiczenia jest zapoznanie się z podstawowymi funkcjami i pojęciami związanymi ze środowiskiem AutoCAD 2012 w polskiej wersji językowej.

Celem ćwiczenia jest zapoznanie się z podstawowymi funkcjami i pojęciami związanymi ze środowiskiem AutoCAD 2012 w polskiej wersji językowej. W przygotowaniu ćwiczeń wykorzystano m.in. następujące materiały: 1. Program AutoCAD 2012. 2. Graf J.: AutoCAD 14PL Ćwiczenia. Mikom 1998. 3. Kłosowski P., Grabowska A.: Obsługa programu AutoCAD 14 i 2000.

Bardziej szczegółowo

1 LINQ. Zaawansowane programowanie internetowe Instrukcja nr 1

1 LINQ. Zaawansowane programowanie internetowe Instrukcja nr 1 1 LINQ 1 1. Cel zajęć Celem zajęć jest zapoznanie się z technologią LINQ oraz tworzeniem trójwarstwowej aplikacji internetowej. 2. Zadanie Proszę przygotować aplikację WWW, która: będzie pozwalała na generowanie

Bardziej szczegółowo

MS Access formularze

MS Access formularze MS Access formularze Formularze to obiekty służące do wprowadzania i edycji danych znajdujących się w tabelach. O ile wprowadzanie danych bezpośrednio do tabel odbywa się zawsze w takiej samej formie (arkusz

Bardziej szczegółowo

Komputery I (2) Panel sterowania:

Komputery I (2) Panel sterowania: Komputery I (2) Paweł Jamer Panel sterowania: Podstawowym miejscem z którego zarządzamy ustawieniami systemu Windows jest panel sterowania. Znaleźć tam możemy wszelkiego rodzaju narzędzia umożliwiające

Bardziej szczegółowo

Podczas dziedziczenia obiekt klasy pochodnej może być wskazywany przez wskaźnik typu klasy bazowej.

Podczas dziedziczenia obiekt klasy pochodnej może być wskazywany przez wskaźnik typu klasy bazowej. Polimorfizm jest filarem programowania obiektowego, nie tylko jeżeli chodzi o język C++. Daje on programiście dużą elastyczność podczas pisania programu. Polimorfizm jest ściśle związany z metodami wirtualnymi.

Bardziej szczegółowo

Nawigacja po długim dokumencie może być męcząca, dlatego warto poznać następujące skróty klawiszowe

Nawigacja po długim dokumencie może być męcząca, dlatego warto poznać następujące skróty klawiszowe Zestawienie wydatków rok 2015 1 Wstaw numerację stron. Aby to zrobić przejdź na zakładkę Wstawianie i w grupie Nagłówek i stopka wybierz Numer strony. Następnie określ pozycję numeru na stronie (na przykład

Bardziej szczegółowo

Jak zrobić klasyczny button na stronę www? (tutorial) w programie GIMP

Jak zrobić klasyczny button na stronę www? (tutorial) w programie GIMP Jak zrobić klasyczny button na stronę www? (tutorial) w programie GIMP Niniejszy tutorial jest wyłączną własnością Doroty Ciesielskiej Zapraszam na moją stronę http://www.direktorek03.wm studio.pl oraz

Bardziej szczegółowo

Instrukcja laboratoryjna nr.4

Instrukcja laboratoryjna nr.4 Języki programowania na platformie.net cz.2 2016/17 Instrukcja laboratoryjna nr.4 Język Visual Basic for.net Prowadzący: Tomasz Goluch Wersja: 3.1 I. Współpraca Visual Basic z C# Cel: Wykorzystanie w kodzie

Bardziej szczegółowo

1. Umieść kursor w miejscu, w którym ma być wprowadzony ozdobny napis. 2. Na karcie Wstawianie w grupie Tekst kliknij przycisk WordArt.

1. Umieść kursor w miejscu, w którym ma być wprowadzony ozdobny napis. 2. Na karcie Wstawianie w grupie Tekst kliknij przycisk WordArt. Grafika w dokumencie Wprowadzanie ozdobnych napisów WordArt Do tworzenia efektownych, ozdobnych napisów służy obiekt WordArt. Aby wstawić do dokumentu obiekt WordArt: 1. Umieść kursor w miejscu, w którym

Bardziej szczegółowo

Aktywności są związane z ekranem i definiują jego wygląd. Dzieje się to poprzez podpięcie do aktywności odpowiedniego widoku.

Aktywności są związane z ekranem i definiują jego wygląd. Dzieje się to poprzez podpięcie do aktywności odpowiedniego widoku. Aktywności to podstawowe elementy związane z platformą Android. Dzięki poznaniu aktywności będziesz w stanie napisać pierwszą aplikację przeznaczoną na urządzenie mobilne. Po dodaniu kontrolek możesz w

Bardziej szczegółowo

Spis treści 1. Wstęp Logowanie Główny interfejs aplikacji Ogólny opis interfejsu Poruszanie się po mapie...

Spis treści 1. Wstęp Logowanie Główny interfejs aplikacji Ogólny opis interfejsu Poruszanie się po mapie... Spis treści 1. Wstęp... 2 2. Logowanie... 2 3. Główny interfejs aplikacji... 2 3.1. Ogólny opis interfejsu... 2 3.2. Poruszanie się po mapie... 3 3.3. Przełączanie widocznych warstw... 3 4. Urządzenia...

Bardziej szczegółowo

Ćwiczenia nr 4. Arkusz kalkulacyjny i programy do obliczeń statystycznych

Ćwiczenia nr 4. Arkusz kalkulacyjny i programy do obliczeń statystycznych Ćwiczenia nr 4 Arkusz kalkulacyjny i programy do obliczeń statystycznych Arkusz kalkulacyjny składa się z komórek powstałych z przecięcia wierszy, oznaczających zwykle przypadki, z kolumnami, oznaczającymi

Bardziej szczegółowo

Przewodnik... Tworzenie Landing Page

Przewodnik... Tworzenie Landing Page Przewodnik... Tworzenie Landing Page Spis treści Kreator strony landing page Stwórz stronę Zarządzaj stronami 2 Kreator strony landing page Kreator pozwala stworzyć własną stronę internetową z unikalnym

Bardziej szczegółowo

Ćwiczenie 1 Automatyczna animacja ruchu

Ćwiczenie 1 Automatyczna animacja ruchu Automatyczna animacja ruchu Celem ćwiczenia jest poznanie procesu tworzenia automatycznej animacji ruchu, która jest podstawą większości projektów we Flashu. Ze względu na swoją wszechstronność omawiana

Bardziej szczegółowo

UONET+ moduł Dziennik

UONET+ moduł Dziennik UONET+ moduł Dziennik Sporządzanie ocen opisowych i diagnostycznych uczniów z wykorzystaniem schematów oceniania Przewodnik System UONET+ umożliwia sporządzanie ocen opisowych uczniów w oparciu o przygotowany

Bardziej szczegółowo

Cel: Przypisujemy przyciskom określone funkcje panel górny (Panel1)

Cel: Przypisujemy przyciskom określone funkcje panel górny (Panel1) W odcinku III tworzyliśmy paski narzędzi. Umieszczaliśmy na panelach ikony, reprezentujące czynności (charakterystyczne dla edytorów tekstu). Musimy teraz przypisać każdemu przyciskowi jego czynność (wycinanie,

Bardziej szczegółowo

5.2. Pierwsze kroki z bazami danych

5.2. Pierwsze kroki z bazami danych 5.2. Pierwsze kroki z bazami danych Uruchamianie programu Podobnie jak inne programy, OO Base uruchamiamy z Menu Start, poprzez zakładkę Wszystkie programy, gdzie znajduje się folder OpenOffice.org 2.2,

Bardziej szczegółowo

Wprowadzenie do projektu QualitySpy

Wprowadzenie do projektu QualitySpy Wprowadzenie do projektu QualitySpy Na podstawie instrukcji implementacji prostej funkcjonalności. 1. Wstęp Celem tego poradnika jest wprowadzić programistę do projektu QualitySpy. Będziemy implementować

Bardziej szczegółowo

Rys.1. Technika zestawiania części za pomocą polecenia WSTAWIAJĄCE (insert)

Rys.1. Technika zestawiania części za pomocą polecenia WSTAWIAJĄCE (insert) Procesy i techniki produkcyjne Wydział Mechaniczny Ćwiczenie 3 (2) CAD/CAM Zasady budowy bibliotek parametrycznych Cel ćwiczenia: Celem tego zestawu ćwiczeń 3.1, 3.2 jest opanowanie techniki budowy i wykorzystania

Bardziej szczegółowo

Programowanie Obiektowe GUI

Programowanie Obiektowe GUI Programowanie Obiektowe GUI Swing Celem ćwiczenia jest ilustracja wizualnego tworzenia graficznego interfejsu użytkownika opartego o bibliotekę Swing w środowisku NetBeans. Ponadto, ćwiczenie ma na celu

Bardziej szczegółowo

BOC INFORMATION TECHNOLOGIES CONSULTING. Zadania. Przykład bankowy

BOC INFORMATION TECHNOLOGIES CONSULTING. Zadania. Przykład bankowy ADONIS - Szkolenie Zadania Przykład bankowy BOC Information Technologies Consulting Sp. z o.o. Al. Jerozolimskie 109/26 02-011 Warszawa Tel: +48-22-628 00 15 Fax: +48-22-621 66 88 e-mail: boc@boc-pl.com

Bardziej szczegółowo

I. Program II. Opis głównych funkcji programu... 19

I. Program II. Opis głównych funkcji programu... 19 07-12-18 Spis treści I. Program... 1 1 Panel główny... 1 2 Edycja szablonu filtrów... 3 A) Zakładka Ogólne... 4 B) Zakładka Grupy filtrów... 5 C) Zakładka Kolumny... 17 D) Zakładka Sortowanie... 18 II.

Bardziej szczegółowo

Zadanie Wstaw wykres i dokonaj jego edycji dla poniższych danych. 8a 3,54 8b 5,25 8c 4,21 8d 4,85

Zadanie Wstaw wykres i dokonaj jego edycji dla poniższych danych. 8a 3,54 8b 5,25 8c 4,21 8d 4,85 Zadanie Wstaw wykres i dokonaj jego edycji dla poniższych danych Klasa Średnia 8a 3,54 8b 5,25 8c 4,21 8d 4,85 Do wstawienia wykresu w edytorze tekstu nie potrzebujemy mieć wykonanej tabeli jest ona tylko

Bardziej szczegółowo

DOKUMENTY I GRAFIKI. Zarządzanie zawartością Tworzenie folderu Dodawanie dokumentu / grafiki Wersje plików... 7

DOKUMENTY I GRAFIKI. Zarządzanie zawartością Tworzenie folderu Dodawanie dokumentu / grafiki Wersje plików... 7 DOKUMENTY I GRAFIKI SPIS TREŚCI Zarządzanie zawartością... 2 Tworzenie folderu... 3 Dodawanie dokumentu / grafiki... 4 Wersje plików... 7 Zmiana uprawnień w plikach... 9 Link do dokumentów i dodawanie

Bardziej szczegółowo

Temat: Organizacja skoroszytów i arkuszy

Temat: Organizacja skoroszytów i arkuszy Temat: Organizacja skoroszytów i arkuszy Podstawowe informacje o skoroszycie Excel jest najczęściej wykorzystywany do tworzenia skoroszytów. Skoroszyt jest zbiorem informacji, które są przechowywane w

Bardziej szczegółowo

Baltie - programowanie

Baltie - programowanie Baltie - programowanie Chcemy wybudować na scenie domek, ale nie chcemy sami umieszczać przedmiotów jak w trybie Budowanie, ani wydawać poleceń czarodziejowi jak w trybie Czarowanie. Jak utworzyć własny

Bardziej szczegółowo

Delphi podstawy programowania. Środowisko Delphi

Delphi podstawy programowania. Środowisko Delphi Delphi podstawy programowania Środowisko Delphi Olsztyn 2004 Delphi Programowanie obiektowe - (object-oriented programming) jest to metodologia tworzeniu programów komputerowych definiująca je jako zbiór

Bardziej szczegółowo

mfaktura Instrukcja instalacji programu Ogólne informacje o programie www.matsol.pl biuro@matsol.pl

mfaktura Instrukcja instalacji programu Ogólne informacje o programie www.matsol.pl biuro@matsol.pl mfaktura Instrukcja instalacji programu Ogólne informacje o programie www.matsol.pl biuro@matsol.pl Instalacja programu 1. Po włożeniu płytki cd do napędu program instalacyjny powinien się uruchomić automatyczne.

Bardziej szczegółowo

Windows Workflow Foundation (wprowadzenie - prosty przykład Sequential Workflow):

Windows Workflow Foundation (wprowadzenie - prosty przykład Sequential Workflow): Windows Workflow Foundation (wprowadzenie - prosty przykład Sequential Workflow): 1. Utworzenie projektu - Sequential Workflow. File - New Project - Visual C# -Workflow- Sequential Workflow Console Application

Bardziej szczegółowo

Architektura interfejsu użytkownika

Architektura interfejsu użytkownika Uniwersytet Jagielloński Interfejsy graficzne Wykład 3 Architektura interfejsu użytkownika Barbara Strug 2011 Hall of shame Hall of Shame Hall of Fame O czym dzisiaj Model Widok- Kontroler Hierarchia widoków

Bardziej szczegółowo

Laboratorium 8 ( Android -pierwsza aplikacja)

Laboratorium 8 ( Android -pierwsza aplikacja) Dr Mirosław Łątka Informatyka dla medycyny Jesień 2012 Laboratorium 8 ( Android -pierwsza aplikacja) Naszym celem jest stworzenie aplikacji, która wyświetla zdjęcie Alberta Einsteina. Jeden z przycisków

Bardziej szczegółowo

Szkolenie dla nauczycieli SP10 w DG Operacje na plikach i folderach, obsługa edytora tekstu ABC. komputera dla nauczyciela. Materiały pomocnicze

Szkolenie dla nauczycieli SP10 w DG Operacje na plikach i folderach, obsługa edytora tekstu ABC. komputera dla nauczyciela. Materiały pomocnicze ABC komputera dla nauczyciela Materiały pomocnicze 1. Czego się nauczysz? Uruchamianie i zamykanie systemu: jak zalogować się do systemu po uruchomieniu komputera, jak tymczasowo zablokować komputer w

Bardziej szczegółowo

Informacje ogólne. Karol Trybulec p-programowanie.pl 1. 2 // cialo klasy. class osoba { string imie; string nazwisko; int wiek; int wzrost;

Informacje ogólne. Karol Trybulec p-programowanie.pl 1. 2 // cialo klasy. class osoba { string imie; string nazwisko; int wiek; int wzrost; Klasy w C++ są bardzo ważnym narzędziem w rękach programisty. Klasy są fundamentem programowania obiektowego. Z pomocą klas będziesz mógł tworzyć lepszy kod, a co najważniejsze będzie on bardzo dobrze

Bardziej szczegółowo

Zaawansowane aplikacje internetowe - laboratorium

Zaawansowane aplikacje internetowe - laboratorium Zaawansowane aplikacje internetowe - laboratorium Web Services (część 3). Do wykonania ćwiczeń potrzebne jest zintegrowane środowisko programistyczne Microsoft Visual Studio 2005. Ponadto wymagany jest

Bardziej szczegółowo

Zadanie 10. Stosowanie dokumentu głównego do organizowania dużych projektów

Zadanie 10. Stosowanie dokumentu głównego do organizowania dużych projektów Zadanie 10. Stosowanie dokumentu głównego do organizowania dużych projektów Za pomocą edytora Word można pracować zespołowo nad jednym dużym projektem (dokumentem). Tworzy się wówczas dokument główny,

Bardziej szczegółowo

Wybieramy File->New->Project Wybieramy aplikację MFC->MFC Application jak na rysunku poniżej:

Wybieramy File->New->Project Wybieramy aplikację MFC->MFC Application jak na rysunku poniżej: Interfejs w MFC 1. Tworzenie nowego projektu Wybieramy File->New->Project Wybieramy aplikację MFC->MFC Application jak na rysunku poniżej: Następnie wybieramy opcje jak na rysunku: Następnie wybieramy

Bardziej szczegółowo

Lokalizacja jest to położenie geograficzne zajmowane przez aparat. Miejsce, w którym zainstalowane jest to urządzenie.

Lokalizacja jest to położenie geograficzne zajmowane przez aparat. Miejsce, w którym zainstalowane jest to urządzenie. Lokalizacja Informacje ogólne Lokalizacja jest to położenie geograficzne zajmowane przez aparat. Miejsce, w którym zainstalowane jest to urządzenie. To pojęcie jest używane przez schematy szaf w celu tworzenia

Bardziej szczegółowo

WIZUALIZER 3D APLIKACJA DOBORU KOSTKI BRUKOWEJ. Instrukcja obsługi aplikacji

WIZUALIZER 3D APLIKACJA DOBORU KOSTKI BRUKOWEJ. Instrukcja obsługi aplikacji /30 WIZUALIZER 3D APLIKACJA DOBORU KOSTKI BRUKOWEJ Instrukcja obsługi aplikacji Aby rozpocząć pracę z aplikacją, należy zarejestrować się w celu założenia konta. Wystarczy wpisać imię, nazwisko, adres

Bardziej szczegółowo

2. W oknie dialogowym Choose Toolbox Items w zakładce.net Framework Components naciskamy przycisk Browse...

2. W oknie dialogowym Choose Toolbox Items w zakładce.net Framework Components naciskamy przycisk Browse... KORZYSTANIE Z KONTROLKI.NET LENDEVICERS232 DODAWANIE KONTROLKI DO ZBIORU KOMPONENTÓW DOSTĘPNYCH W PALECIE TOOLBOX (ŚRODOWISKA PROGRAMISTYCZNE FIRMY MICROSOFT) W środowisku programistycznym (Visual C++,

Bardziej szczegółowo

I Tworzenie prezentacji za pomocą szablonu w programie Power-Point. 1. Wybieramy z górnego menu polecenie Nowy a następnie Utwórz z szablonu

I Tworzenie prezentacji za pomocą szablonu w programie Power-Point. 1. Wybieramy z górnego menu polecenie Nowy a następnie Utwórz z szablonu I Tworzenie prezentacji za pomocą szablonu w programie Power-Point 1. Wybieramy z górnego menu polecenie Nowy a następnie Utwórz z szablonu 2. Po wybraniu szablonu ukaŝe się nam ekran jak poniŝej 3. Następnie

Bardziej szczegółowo

Serwis jest dostępny w internecie pod adresem www.solidnyserwis.pl. Rysunek 1: Strona startowa solidnego serwisu

Serwis jest dostępny w internecie pod adresem www.solidnyserwis.pl. Rysunek 1: Strona startowa solidnego serwisu Spis treści 1. Zgłoszenia serwisowe wstęp... 2 2. Obsługa konta w solidnym serwisie... 2 Rejestracja w serwisie...3 Logowanie się do serwisu...4 Zmiana danych...5 3. Zakładanie i podgląd zgłoszenia...

Bardziej szczegółowo

Nieskonfigurowana, pusta konsola MMC

Nieskonfigurowana, pusta konsola MMC Konsola MMC Aby maksymalnie, jak to tylko możliwe, ułatwić administrowanie systemem operacyjnym oraz aplikacjami i usługami w systemie Windows XP, wszystkie niezbędne czynności administracyjne można wykonać

Bardziej szczegółowo

Sposoby tworzenia projektu zawierającego aplet w środowisku NetBeans. Metody zabezpieczenia komputera użytkownika przed działaniem apletu.

Sposoby tworzenia projektu zawierającego aplet w środowisku NetBeans. Metody zabezpieczenia komputera użytkownika przed działaniem apletu. Sposoby tworzenia projektu zawierającego aplet w środowisku NetBeans. Metody zabezpieczenia komputera użytkownika przed działaniem apletu. Dr inż. Zofia Kruczkiewicz Dwa sposoby tworzenia apletów Dwa sposoby

Bardziej szczegółowo

Inżynieria Programowania Laboratorium 3 Projektowanie i implementacja bazy danych. Paweł Paduch paduch@tu.kielce.pl

Inżynieria Programowania Laboratorium 3 Projektowanie i implementacja bazy danych. Paweł Paduch paduch@tu.kielce.pl Inżynieria Programowania Laboratorium 3 Projektowanie i implementacja bazy danych Paweł Paduch paduch@tu.kielce.pl 06-04-2013 Rozdział 1 Wstęp Na dzisiejszych zajęciach zajmiemy się projektem bazy danych.

Bardziej szczegółowo

Dodanie nowej formy do projektu polega na:

Dodanie nowej formy do projektu polega na: 7 Tworzenie formy Forma jest podstawowym elementem dla tworzenia interfejsu użytkownika aplikacji systemu Windows. Umożliwia uruchomienie aplikacji, oraz komunikację z użytkownikiem aplikacji. W trakcie

Bardziej szczegółowo

Edytor tekstu OpenOffice Writer Podstawy

Edytor tekstu OpenOffice Writer Podstawy Edytor tekstu OpenOffice Writer Podstawy OpenOffice to darmowy zaawansowany pakiet biurowy, w skład którego wchodzą następujące programy: edytor tekstu Writer, arkusz kalkulacyjny Calc, program do tworzenia

Bardziej szczegółowo

Instrukcja użytkownika

Instrukcja użytkownika SoftwareStudio Studio 60-349 Poznań, ul. Ostroroga 5 Tel. 061 66 90 641 061 66 90 642 061 66 90 643 061 66 90 644 fax 061 86 71 151 mail: poznan@softwarestudio.com.pl Herkules WMS.net Instrukcja użytkownika

Bardziej szczegółowo

Visual Studio instalacja

Visual Studio instalacja Visual Studio 2017 - instalacja Do tej pory napisaliśmy wiele programów, z czego niemal wszystkie były aplikacjami konsolowymi. Najwyższy więc czas zająć się tworzeniem aplikacji z graficznym interfejsem

Bardziej szczegółowo

Sekretariat Optivum. Jak przygotować listę uczniów zawierającą tylko wybrane dane, np. adresy e-mail ucznia i jego opiekunów? Projektowanie listy

Sekretariat Optivum. Jak przygotować listę uczniów zawierającą tylko wybrane dane, np. adresy e-mail ucznia i jego opiekunów? Projektowanie listy Sekretariat Optivum Jak przygotować listę uczniów zawierającą tylko wybrane dane, np. adresy e-mail ucznia i jego opiekunów? Program Sekretariat Optivum ma wbudowane różne edytory, które umożliwiają przygotowywanie

Bardziej szczegółowo

narzędzie Linia. 2. W polu koloru kliknij kolor, którego chcesz użyć. 3. Aby coś narysować, przeciągnij wskaźnikiem w obszarze rysowania.

narzędzie Linia. 2. W polu koloru kliknij kolor, którego chcesz użyć. 3. Aby coś narysować, przeciągnij wskaźnikiem w obszarze rysowania. Elementy programu Paint Aby otworzyć program Paint, należy kliknąć przycisk Start i Paint., Wszystkie programy, Akcesoria Po uruchomieniu programu Paint jest wyświetlane okno, które jest w większej części

Bardziej szczegółowo

1. Wstęp Niniejszy dokument jest instrukcją użytkownika dla aplikacji internetowej DM TrackMan.

1. Wstęp Niniejszy dokument jest instrukcją użytkownika dla aplikacji internetowej DM TrackMan. Instrukcja korzystania z aplikacji TrackMan wersja WEB 1. Wstęp... 1 2. Logowanie... 1 3. Główny interfejs aplikacji... 2 3.1. Ogólny opis interfejsu... 2 3.2. Poruszanie się po mapie... 2 3.3. Przełączanie

Bardziej szczegółowo

Kopiowanie, przenoszenie plików i folderów

Kopiowanie, przenoszenie plików i folderów Kopiowanie, przenoszenie plików i folderów Pliki i foldery znajdujące się na dysku można kopiować lub przenosić zarówno w ramach jednego dysku jak i między różnymi nośnikami (np. pendrive, karta pamięci,

Bardziej szczegółowo

Tworzenie szablonów użytkownika

Tworzenie szablonów użytkownika Poradnik Inżyniera Nr 40 Aktualizacja: 12/2018 Tworzenie szablonów użytkownika Program: Plik powiązany: Stratygrafia 3D - karty otworów Demo_manual_40.gsg Głównym celem niniejszego Przewodnika Inżyniera

Bardziej szczegółowo

Operacje na gotowych projektach.

Operacje na gotowych projektach. 1 Operacje na gotowych projektach. I. Informacje wstępne. -Wiele firm udostępnia swoje produkty w postaci katalogów wykonanych w środowisku projektowania AutoCad. Podstawowym rozszerzeniem projektów stworzonych

Bardziej szczegółowo

WIZUALIZER 3D APLIKACJA DOBORU KOSTKI BRUKOWEJ. Instrukcja obsługi aplikacji

WIZUALIZER 3D APLIKACJA DOBORU KOSTKI BRUKOWEJ. Instrukcja obsługi aplikacji /30 WIZUALIZER 3D APLIKACJA DOBORU KOSTKI BRUKOWEJ Instrukcja obsługi aplikacji Aby rozpocząć pracę z aplikacją, należy zarejestrować się w celu założenia konta. Wystarczy wpisać imię, nazwisko, adres

Bardziej szczegółowo

Ćw. I Projektowanie opakowań transportowych cz. 1 Ćwiczenia z Corel DRAW

Ćw. I Projektowanie opakowań transportowych cz. 1 Ćwiczenia z Corel DRAW Ćw. I Projektowanie opakowań transportowych cz. 1 Ćwiczenia z Corel DRAW Celem ćwiczenia jest wstępne przygotowanie do wykonania projektu opakowania transportowego poprzez zapoznanie się z programem Corel

Bardziej szczegółowo

Kompleksowe tworzenie aplikacji klasy Desktop z wykorzystaniem SWT i

Kompleksowe tworzenie aplikacji klasy Desktop z wykorzystaniem SWT i Program szkolenia: Kompleksowe tworzenie aplikacji klasy Desktop z wykorzystaniem SWT i JFace Informacje ogólne Nazwa: Kod: Kategoria: Grupa docelowa: Czas trwania: Forma: Kompleksowe tworzenie aplikacji

Bardziej szczegółowo

Politechnika Gdańska Katedra Optoelektroniki i Systemów Elektronicznych

Politechnika Gdańska Katedra Optoelektroniki i Systemów Elektronicznych Laboratorium OiOSE. Programowanie w środowisku MS Visual C++ 1 Politechnika Gdańska Katedra Optoelektroniki i Systemów Elektronicznych Organizacja i Oprogramowanie Systemów Elektronicznych Michał Kowalewski

Bardziej szczegółowo

Przykładowa dostępna aplikacja w Visual Studio - krok po kroku

Przykładowa dostępna aplikacja w Visual Studio - krok po kroku Przykładowa dostępna aplikacja w Visual Studio - krok po kroku Zadaniem poniższego opisu jest pokazanie, jak stworzyć aplikację z dostępnym interfejsem. Sama aplikacja nie ma konkretnego zastosowania i

Bardziej szczegółowo

Zaznaczanie komórek. Zaznaczenie pojedynczej komórki polega na kliknięciu na niej LPM

Zaznaczanie komórek. Zaznaczenie pojedynczej komórki polega na kliknięciu na niej LPM Zaznaczanie komórek Zaznaczenie pojedynczej komórki polega na kliknięciu na niej LPM Aby zaznaczyć blok komórek które leżą obok siebie należy trzymając wciśnięty LPM przesunąć kursor rozpoczynając od komórki

Bardziej szczegółowo

Obiekt klasy jest definiowany poprzez jej składniki. Składnikami są różne zmienne oraz funkcje. Składniki opisują rzeczywisty stan obiektu.

Obiekt klasy jest definiowany poprzez jej składniki. Składnikami są różne zmienne oraz funkcje. Składniki opisują rzeczywisty stan obiektu. Zrozumienie funkcji danych statycznych jest podstawą programowania obiektowego. W niniejszym artykule opiszę zasadę tworzenia klas statycznych w C#. Oprócz tego dowiesz się czym są statyczne pola i metody

Bardziej szczegółowo

NAGŁÓWKI, STOPKI, PODZIAŁY WIERSZA I STRONY, WCIĘCIA

NAGŁÓWKI, STOPKI, PODZIAŁY WIERSZA I STRONY, WCIĘCIA NAGŁÓWKI, STOPKI, PODZIAŁY WIERSZA I STRONY, WCIĘCIA Ćwiczenie 1: Ściągnij plik z tekstem ze strony www. Zaznacz tekst i ustaw go w stylu Bez odstępów. Sformatuj tekst: wyjustowany czcionka Times New Roman

Bardziej szczegółowo

Dodawanie i modyfikacja atrybutów zbioru

Dodawanie i modyfikacja atrybutów zbioru Dodawanie i modyfikacja atrybutów zbioru Program Moje kolekcje wyposażony został w narzędzia pozwalające na dodawanie, edycję oraz usuwanie atrybutów przypisanych do zbioru kolekcji. Dzięki takiemu rozwiązaniu

Bardziej szczegółowo

Access - Aplikacja. Tworzenie bazy danych w postaci aplikacji

Access - Aplikacja. Tworzenie bazy danych w postaci aplikacji Tworzenie bazy danych w postaci aplikacji Access - Aplikacja 1. Otwórz plik zawierający bazę danych Wypożyczalni kaset video o nazwie Wypożyczalnia.mdb. 2. Utworzy kwerendę, która wyświetli tytuły i opisy

Bardziej szczegółowo

System rezerwacji online

System rezerwacji online Spis treści 1. Część widoczna dla klientów dokonujących rezerwacji...1 1.a. Ogólne informacje...1 1.b. Etapy w rezerwacji...3 I. Etap 1 wybór dat początku i końca pobytu oraz wybór pokoi...3 II. Etap 2

Bardziej szczegółowo

1. Dockbar, CMS + wyszukiwarka aplikacji Dodawanie portletów Widok zawartości stron... 3

1. Dockbar, CMS + wyszukiwarka aplikacji Dodawanie portletów Widok zawartości stron... 3 DODAJEMY TREŚĆ DO STRONY 1. Dockbar, CMS + wyszukiwarka aplikacji... 2 2. Dodawanie portletów... 3 Widok zawartości stron... 3 Omówienie zawartości portletu (usunięcie ramki itd.)... 4 3. Ikonki wybierz

Bardziej szczegółowo

CMS- kontakty (mapa)

CMS- kontakty (mapa) CMS- kontakty (mapa) Rozpatrzy inny rodzaj kontaktu mapa sytuacyjna. W naszej kategorii kontaktów dodamy teraz multimedialną mapę dojazdową. W tym celu potrzebny nam będzie moduł HTML 1.0. Będziemy mogli

Bardziej szczegółowo

1. Opis okna podstawowego programu TPrezenter.

1. Opis okna podstawowego programu TPrezenter. OPIS PROGRAMU TPREZENTER. Program TPrezenter przeznaczony jest do pełnej graficznej prezentacji danych bieżących lub archiwalnych dla systemów serii AL154. Umożliwia wygodną i dokładną analizę na monitorze

Bardziej szczegółowo

Jarosław Kuchta. Podstawy Programowania Obiektowego. ćwiczenie 10. Podstawy grafiki w WPF

Jarosław Kuchta. Podstawy Programowania Obiektowego. ćwiczenie 10. Podstawy grafiki w WPF Jarosław Kuchta Podstawy Programowania Obiektowego ćwiczenie 10 Podstawy grafiki w WPF Wprowadzenie W tym ćwiczeniu rozpoczniemy tworzenie gry Wall Demolition. Jest to odmiana gry Blockout. W grze występuje

Bardziej szczegółowo

Podstawy programowania. Ćwiczenie. Pojęcia bazowe. Języki programowania. Środowisko programowania Visual Studio

Podstawy programowania. Ćwiczenie. Pojęcia bazowe. Języki programowania. Środowisko programowania Visual Studio Podstawy programowania Ćwiczenie Pojęcia bazowe. Języki programowania. Środowisko programowania Visual Studio Tematy ćwiczenia algorytm, opis języka programowania praca ze środowiskiem, formularz, obiekty

Bardziej szczegółowo

UNIWERSYTET RZESZOWSKI KATEDRA INFORMATYKI

UNIWERSYTET RZESZOWSKI KATEDRA INFORMATYKI UNIWERSYTET RZESZOWSKI KATEDRA INFORMATYKI LABORATORIUM TECHNOLOGIA SYSTEMÓW INFORMATYCZNYCH W BIOTECHNOLOGII Aplikacja bazodanowa: Cz. II Rzeszów, 2010 Strona 1 z 11 APLIKACJA BAZODANOWA MICROSOFT ACCESS

Bardziej szczegółowo