WPF – Caliburn.Micro – obsługa zdarzeń

Najwyższy szas na zapowiadaną obsługę zdarzeń w Caliburn.Micro.
Najpierw jednak krótkie przypomnienie. Zaczniemy od prostego zdarzenie Click na Buttonie

Bez MVVM

Każde zdarzenie z z View ma odwzorowanie w Code-Behind. Jeśli nazwy metod się nie zgadzają kod się nie kompiluje
View

<Button x:Name="SayHi" Content="Say Hi!" Click="SayHi_Click" />

Code-behind

private void SayHi_Click(object sender, RoutedEventArgs e)
{
    //some app logic
}

Z MVVM

Musimy pamiętać o konieczności wpowadzenia klasy implementującej ICommand (odsyłam do przykładów z wcześniejszych postów)
View

<Button x:Name="SayHi" Content="Say Hi!" Command="{Binding SayHi}" />

ViewModel

public ICommand SayHi { get { return new RelayCommand(SayHiExcute, CanSayHiExcute); } }

Caliburn.Micro

W celu obsłużenia zdarzenia Click wybranego Buttona wystarczy nadać mu nazwę.
View

<Button x:Name="SayHi" />

Następnie w ViewModelu wystarczy obsłużyć publiczną metodę o tej samej nazwie.
ViewModel

public void SayHi()
{
    //Do something
}

Proste i skuteczne. Tylko że zwykle mamy jeszcze inne zdarzenia do obsłużenia poza Click.

Zdarzenie nie mające parametrów

Najpierw prosta sprawa. Mamy zdarzenie MouseEnter. Występuje ono zawsze gdy wjedziemy myszką na wybraną kontrolkę.

Bez MVVM

<Button x:Name="SayHi" Content="Say Hi!" MouseEnter="SayHi_MouseEnter" />

Code-behind

private void SayHi_MouseEnter(object sender, MouseEventArgs e)
{
    //some app logic
}

Z MVVM

Tutaj sprawa się komplikuje po pierwsze w View w definicji naszego głównego elementu (Window, UserControl) należy dodać

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 

dodając wcześniej odpowiednią referencje do projektu ma się rozumieć
Następnie w kodzie Buttona

<Button x:Name="SayHi" Content="Say Hi!" >
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseEnter" >
            <i:InvokeCommandAction Command="{Binding SayHiMouseEnter}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Button>

ViewModel

public ICommand SayHiMouseEnter { get { return new RelayCommand(SayHiMouseEnterExcute, CanSayHiMouseEnterExcute); } }

Trochę bardziej skomplikowane jak widać.

Caliburn.Micro

W View dodajemy odwołanie do odpowiedniego namespaceu. I tutaj uwaga! Wszystko zależy z jaką wersją Caliburn.Micro współpracujemy.
W starych był to następujący wpis:

xmlns:cm="http://www.caliburnproject.org"

W trochę nowszych:

xmlns:cm="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"

A w najnowszym (Caliburn.Micro 2.0.1 stan na 04-09-2014)

xmlns:cm="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro.Platform"

Więc może to powodować trochę zamieszania.

Wracając do sedna:
View

<Button x:Name="SayHi" Content="Say Hi!" cm:Message.Attach="[Event MouseEnter] = [Action SayHiMouseEnter]" />

Gdybyśmy zamiast SayHiMouseEnter wpisali SayHiMouseEnter() też zadział
ViewModel

public void SayHiMouseEnter()
{
     //some app logic
}

I w zasadzie to by wystarczyło. Niestety powyższy kod zadziała wtedy i tylko wtedy gdy nie będziemy mieć w modelu metody SayHi (czyli metody podpiętej pod zdarzenie Click). Ale co gdybyśmy chcieli jednak mieś obie metody otóż należy zmodyfikować wtedy nieznacznie kod naszego View

<Button  Content="Say Hi!" cm:Message.Attach="[Event MouseEnter] = [Action SayHiMouseEnter]; [Event Click] = [Action SayHi]" />

Wprawne oko zauważy, że brak x:Name. Postąpiłem tak dlatego ponieważ nie chciało mi się modyfikować nazwy metody SayHi. Nie może istnieć metoda o takiej samej nazwie jak Button jeśli chcemy odpalać zdarzenie korzystając z Message.Attach.

Zdarzenia z parametrami

Tylko w Caliburn.Micro aby zbytnio nie zanudzać.

Property z View

Czasem chcemy za parametr przekazać wartość, wybranej property z View. Jeśli jest ona zbindowana z ViewModelem to nie jest to problem po prostu w obsłudze zdarzenia powołujemy się na wartość z ViewModelu. Czasem jednak nie możemy, nie chcemy (niepotrzebne skreślić) przekazywać tej wartości do ViewModelu. Wystarczy nam tylko aby wartość była przekazana do metody.

<Button Content="Say Hi!" cm:Message.Attach=" [Event Click] = [Action SayHi(LastName.Text)]" />

Jak widać nic trudnego. Do metody SayHi przekazujemy property Text z TextBoxa LastName.
ViewModel

public void SayHi(string lastName)
{
    //some app logic
}

Musimy zwrócić uwagę na to co przekazujemy jako parametr jeśli np. zamiast string w przypływie weny twórczej wstawilibyśmy w sygnature metody SayHi inta to zostanie tam przekazany domyślny parametr dla danego typu lub null jeśli taki nie istnieje. Uważać trzeba na nazwy metod. Korzystanie z Message.Attach powoduje, że literówka w nazwie metody skutkuje exception.

EventArgs

Są jednak takie zdarzeniaenia, które wymagają aby przekazać do nich eventArgs (no bo jak inaczej sprawdzić, który przycisk na klawiaturze został naciśnięty).
View

<TextBox x:Name="Address" cm:Message.Attach="[Event KeyDown] = [Action AddressKeyDown($eventArgs)]" />

$eventArgs informuje że przekazujemy do określonej metody somyślne dla niej EventArgs.
Oczywiste jest, że metoda AddressKeyDown jako parametr musi przyjmować zmienną typu KeyEventArgs.

$this i $source

Innym razem chcemy przekazać informacje o kontrolce która odpaliła zdarzenie. Są na to dwie opcje

  • $source przekazuje informacje o konkretnym elemencie UI, który wywołał zdarzenie
  • $this przekazuje informacje o elemencie UI, do którego podpięte jest zdarzenie

Wraz z $this możemy używać kropki czyli np. $this.Tag zadziała czego nie można powiedzieć o $source.Tag
Wywołuje się je bardzo podobnie:

View

<Button  Content="TestTag!" cm:Message.Attach=" [Event Click] = [Action TestTag($this.Tag)]" Tag="Test" />

ViewModel

public void TestTag(string tag)
{
    //some app logic      
}

Data Context

Cżesto zdarza się sytuacja, że do metody chcemy wrzucić Data Context wtedy wystarczy w xamlu do wywołania metody dodać $dataContext

<Button  Content="Remove!" cm:Message.Attach=" [Event Click] = [Action Remove($dataContext)]" />

Pozostałe

Możemy oczywiście pójść na całośc i przekazać do metody View , z którego korzystamy wtedy trzeba użyć $view.
Albo zaszaleć na maxa i przekazać do metody absolutnie wszystko (m.in. View, ViewModel) korzystając z $executionContex.
Te dwa ostatnie przypadki są naprawdę rzadkie i w sumie mogą służyć za ciekawostkę (a może się komuś przydadzą).

Podsumowanie

Jeśli ktoś uważa, że obsługa zdarzeń bez Caliburna(bądź MVVM) jest prostsza i nie warto się poświęcać no cóż to jego święte prawo. Niech tylko mi powie jak zrobić testy interfejsu aplikacji. Za puente niech posłuży historia gdy mój znajomy poprosił mnie kiedyś o przetestowanie aplikacji. Przetestowałem aplikacje powiedziałem co mi się podoba (większość) a co nie. Był on jednak niezmiernie niepocieszony faktem, że nie przetestowałem mu przekazywania danych między (i do) widokami.

Chyba nie myślałeś, że będę sobie przeklikiwał widoczki i patrzył jakie dane przechodzą do bazy. Napisz to tak aby to dało się zautomatyzować.

3 thoughts on “WPF – Caliburn.Micro – obsługa zdarzeń

  1. Pingback: dotnetomaniak.pl
  2. Witaj,

    masz jakiś pomysł, w jaki sposób poradzić sobie z obsługą PasswordBoxa za pomocą Caliburna? Binding odpada “ze względów bezpieczeństwa”, a przekazywanie wartości Password za pomocą Message.Attach coś nie chce działać.

Comments are closed.