Trigery – Triggers

Trigery jest to mechmizm WPF-a służący reakcji UI na jakieś zdarzenie. Wyróżniamy cztery rodzaje triggerów

  • Property triggers – używane do zareagowanie na zmiany dependency property w danej kontrolce,
  • Data triggers – używane do zareagowanie na zmiany w bindowanych obiektach- można podłączyć się do właściwości z DataContextu jak i z danej kontrolki,
  • MultiDataTriggers – używane do reagowania na zmiany kilku właściwości,
  • MultiTrigger – używane do reagowania na zmiany kilku dependecy property w danej kontrolce,
  • Event triggers – używane do reagowania na jakieś zdarzenie – służą do odpalania animacji

1. Property triggers

Oto przykład wykorzystania Property Triggera. W ResourceDictionary tworzymy styl, który zostanie przypisany do przycisku.

Widzimy, że dany trigger jest powiązany z właściwością IsMouseOver.W przypadku gdy wartość tej właściwości zostanie ustawiona na true, tło przycisku zostanie zmienione na czarne. Natomiast gdy IsMouseOver powróci do stanu false, tło przycisku wróci do wartości sprzed zmiany.

2. Data triggers

Oto przykład wykorzystania Data Triggera. W ResourceDictionary tworzymy styl, który zostanie przypisany do przycisku.

W tym przypadku nasz trigger reaguje na zmiany właściwości Text. Jako, że nie ustawiliśmy źródła bindowania, właściwość Text będzie wyszukiwana w DataContext danej kontrolki. W przypadku gdy DataContext nie zostanie ustawiony, właściwość ta będzie wyszukiwana w kontrolce będącej wyżej w hierarchii.W przypadku gdy wartość właściwości Text będzie wynosiła “Kolor” tło kontrolki zostanie zmienione na kolor beżowy.

3. MultiData triggers

Oto przykład wykorzystania MultiDataTriggera. W ResourceDictionary tworzymy styl, który zostanie przypisany do przycisku.

Tym razem nasz trigger zareaguje, tylko i wyłącznie gdy wartość właściwości “Text”(znajdującej się w DataContext) będzie równe “Kolor”, oraz wartość właściwości “Title” będzie równe “Spider-Man”. W chwili gdy warunek ten zostanie spełniony, tło kontrolki zmieni się na kolor czarny.

4. MultiTrigger

Zasada działania MultiTriggera jest analogiczna z zasadą działania MultiDataTriggera. Różnica między ni mi jest taka, że w MultiTriggerze możemy jedynie odnosić się do propertisów danej kontrolki (kontrolki dla której styl tworzymy,oraz kontrolek wchodzących w skład naszej kontrolki). Przykładowy kod MultiTriggera może wyglądać w następujący sposób:

Tym razem nasz trigger zareaguje, tylko i wyłącznie gdy tło naszej kontrolki będzie czarne, oraz kursor myszy będzie znajdował się nad przyciskiem. W chwili gdy warunki ten zostaną spełnione, napis na przycisku zmieni się na “MultiTrigger

5. Event triggers

Event triggery wykraczają poza zakres tej części tutoriala, gdyż są one ściśle związane z animacjami.

Trigery – Triggers

Style

Style jest to mechanizm wprowadzony w WPF-ie, a następnie w Silverlighcie, dzięki któremu w łatwy sposób można ujednolicić wygląd elementów naszej aplikacji.
Style z reguły definiowane są w zasobach. Mogą to być zarówno zasoby całej aplikacji, danego okna lub nawet wybranej kontrolki.
Przykładowy styl może wyglądać w następujący sposób:

Każdy styl jest rozpoznawany dzięki unikalnemu kluczowi – parametr x:Key. Możliwe jest pominięcie klucza, w przypadku gdy ustawiona zostanie właściwść TargetType. Jednakże zdefiniowanie właściwości TargetType spowoduje, że dany styl będzie automatycznie zastosowany do wszystkich kontrolek danego typu. W przypadku gdybyśmy jednak chcieli wyłączyć dany styl dla pojedynczej kontrolki możemy zawsze przypisać mu wartość x:Null
Jak widać styl posiada kolekcję obiektów typu Setter. Najważniejszymi propertisami każdego Settera są:

  • Property – określa nazwę właściwości, którą dany styl będzie modyfikować,
  • Value – określa wartość właściwości określonej w elemencie Property

Dla przykłady – następujący styl:

ustawi rozmiar czcionki na buttonie na 22 pkt. Jako, że nie została ustawiona właściwość TargetType dla stylu, musimy podawać pełną nazwę właściwości(czyli nazwę klasy oraz nazwę właściwości).
Oczywiście możemy również ustawiać bardziej skomplikowane właściwości

W przypadku gdybyśmy pisali w Silverlighcie właściwość TargetType ustawia się troszeczkę inaczej. Mianowicie zamiast
TargetType{x:Type Button} piszemy TargetType{Button}

Właściwość TargetType określa nam dla jakiego typu obiektów(kontrolek) dany typ jest przeznaczony. Styl z pierwszego przykładu można zapisać następująco:

Dzięki użyciu właściwości TargetType, kompilator wie, że dany styl jest przeznaczony dla Buttona, dlatego też możemy w Setterze użyć skróconej nazwy właściwości(FontSize zamiast Button.FontSize). Ponadto dzięki użyciu TargetType, możemy pominąć definiowanie klucza po jakim nasz styl będzie rozpoznawany. Od tej chwili wszystkie buttony będą renderowane z użyciem powyższego stylu (oczywiście jeżeli styl ten będzie w resourcach danej kontrolki).

Dziedziczenie styli

Style mogą być dziedziczone. Oznacza to tyle, że styl pochodny będzie rozszerzał styl bazowy. W przypadku gdy styl bazowy ma zdefiniowany TargetType, styl dziedziczący nie może go zmienić. W celu zdefiniowania dziedziczenia używa się właściwości BasedOn. Przykładowe dziedziczenie styli pokazano poniżej

W stylu dziedziczącym można przesłaniać właściwości ze stylu bazowego. W przypadku gdy nie zostaną one przesłonięte, użyte zostaną propertisy ze stylu bazowego.
W celu zaaplikowania stylu do jakiejś kontrolki posługujemy się następującą składnią

myStyle – klucz stylu zdefiniowanego w zasobach.

Style

Commands – czyli interakcja widoku z ViewModelem cz.2

Tak jak wspomniałem w poprzednim wpisie, tym razem zajmiemy się tworzeniem własnej klasy implantującej interfejs ICommand .

1. Implementacja interfejsu ICommand – WPF

Klasa implementująca ICommand może wyglądać w następujący sposób

Klasa DelegateCommand przyjmuje w konstruktorze dwa parametry – delegaty do funkcji. Pierwszy z nich jest to delegat do funkcji, która ma się wykonać, gdy komenda może zostać wykonana.Drugi natomiast jest to delegat do funkcji, która sprawdza czy dana komenda może zostać wykonana.Ważnym elementem jest tutaj następujący kawałek kodu :

który podłącza naszą klasę do WPF-owego systemu komend.
Teraz wystarczy w ViewModelu stworzyć obiekt typu DelegateCommand przekazać do niego odpowiednie funkcje, a następnie zbindować go do widoku. Może to wyglądać w następujący sposób:

  • ViewModel
  • Widok (XAML)

2. Implementacja interfejsu ICommand Silverlight

Przedstawiony powyżej przykład skompiluje się jedynie w aplikacji WPF-owej. Silverlight niestety nie posiada CommandManagera, dlatego też gdy chcemy używać komend właśnie w tej technologii musimy zmodyfikować nasz kod na następujący:

Jak widać w funkcji

sami musimy zadbać o wywołanie zdarzenia

Ponadto dopisana została funkcja

funkcję tą musimy wywoływać zawsze gdy zmieni się jakaś właściwość, która ma mieć wpływ na stan przycisku zbindowanego do naszej komendy.

3. Przykład

Załóżmy,że na widoku mamy przycisk edytuj, który powinien być aktywny tylko i wyłącznie wtedy, gdy zaznaczono jakiś element na gridzie. Przycisk ten jest zbindowany do komendy EditCommand. Jak już wcześniej wspomniano, z powodu braku w Silverlighcie CommandManagera sami musimy wywołać zdarzenie informujące o potrzebie zmiany stanu przycisku. Dlatego też, w ViewModelu została stworzona właściwość SelectedItem

która reaguje na zmiany zaznaczenia na gridzie. W chwili, gdy zaznaczenie się zmienia, wywoływany jest seter z tej właściwości, w którym to wywołujemy funkcję EditCommand.RaiseCanExecute(). Dzięki czemu informujemy kontrolkę o ewentualnej potrzebie zmiany jej stanu.

4. Podsumowanie

Komendy w bardzo prosty i wygodny sposób pozwalają nam wywoływać metody bezpośrednio z ViewModelu. Niestety mają one również swoje ograniczenia:

  • reagują jedynie na zdarzenie Click,
  • można je wykorzystać jedynie na kontrolkach dziedziczących po klasie ButtonBase oraz kontrolkach typu MenuItem
Commands – czyli interakcja widoku z ViewModelem cz.2

Commands – czyli interakcja widoku z ViewModelem cz.1

1. Wstęp

Każdy programista, który napisał chociaż parę linijek kody w WinFormsach wie, że kontrolki używają eventów do powiadamiania o zmianach swojego stanu. W przypadku, gdy interesuje nas odpowiednia reakcja na zdarzenie, podpinamy się do niego odpowiednią funkcją i wykonujemy założone przez nas operację. W analogiczny sposób można postępować w WPF-ie oraz w Silverlighcie, jednakże podejście takie niejako mija się z modelem MVVM. Posiadanie event handlerów w kodzie, ściśle wiąże nam widok (XAML) z code behind lub nawet z samym ViewModelem danego widoku. Ponadto używanie eventów jest problematyczne (a właściwie niemożliwe), gdy widoki zostaną zdefiniowane w Resourcach. Dodatkową wadą używania event handlerów jest fakt, że aplikacje w których widok jest mocno związany z codebehind nie należą do aplikacji, które w łatwy sposób można poddać testom automatycznym.

2. Interfejs ICommand

W celu rozluźnienia połączenia między widokiem a ViewModelem wprowadzono interfejs

Funkcja

ma za zadanie sprawdzić, czy daną komendę można wykonać. Jeżeli nie,funkcja zwraca false i kontrolka zbindowana do danej komendy jest wygaszana (właściwość IsEnabled danej kontrolki ustawiana jest na false). Z kolei funkcja

jest to odpowiednik event handlera podłączonego do zdarzenia. Zdarzenie

informuje widok o tym czy dana komenda może się wykonać czy też nie – dzięki temu kontrolka wie kiedy się odświeżyć(zmienić wartość właściwości IsEnabled).

3. Sposób użycia

Załóżmy, że mamy okno na którym znajduje się przycisk. Po naciśnięciu przycisku chcielibyśmy aby wykonała się jakaś czynność. Każdy obiekt typu Window oraz Control posiada kolekcję CommandBindings, która przechowuje wszystkie komendy danego okna.
W celu dodania nowej komendy należy zrobić następującą rzecz. Po pierwsze musimy stworzyć nową komendę. Robimy to przy pomocy następującego kodu:

W celu dodania komendy do danego okna/kontrolki wykonujemy następującą operację

Funkcja Add z klasy CommandBindingCollection przyjmuje w parametrze obiekt typu CommandBinding. Konstruktor takiego obiektu składa się z trzech parametrów:

  • RoutedCommand – obiekt typu RoutedCommand
  • delegat typu ExecutedRoutedEventHandler – jest to delegat do funkcji, która ma się wykonać w przypadku gdy dana komenda spełnia warunki do “uruchomienia”
  • delegat typu CanExecuteRoutedEventHandler – jest to delegat do funkcji, która sprawdza czy daną komendę można wykonać. W celu zabronienia wykonania komendy należy ustawić argument ExecutedRoutedEventArgs CanExecute na false.

Na koniec należy jeszcze zbindować naszą komendę z przyciskiem. Przykładowy kod dodawania komendy może wyglądać w następujący sposób.

Bindowanie w przycisku do komendy może wyglądać w następujący sposób (XAML)

gdzie local jest aliasem na namespace, w którym znajduje się okno.

4. Podsumowanie

Użycie komend w pewnym stopniu oddzieliło nam widok od codebehind-a, jednakże wciąż nie jest to rozwiązanie idealne. Z codebehind-a wywołujemy metody ViewModelu, więc niejako ciągle jesteśmy bezpośrednio zależni od widoku. W części drugiej zaprezentuję w jaki sposób można stworzyć własny obiekt implementujący interfejs ICommand, dzięki czemu ograniczymy do minimum ilość kodu pisanego w codebehind.

Commands – czyli interakcja widoku z ViewModelem cz.1