WPF – Caliburn.Micro – obsługa okien

W dotychczasowo opisanych częściach cyklu na temat MVVM (z wykorzystaniem Caliburn.Micro) wszystko było pokazane na przykładzie aplikacji mającej wyłącznie jedno okno. W rzeczywistym świecie takie aplikacje należą do mniejszości. Programy mają zwykle kilka okien i tym właśnie teraz się zajmiemy.

Podejście klasyczne

W klasycznie zbudowanej aplikacji jeśli chcemy wejść np. opcje aplikacji to otwiera się nam nowe okno. Spróbujmy zmodyfikować aplikację z ostatniego przykładu.
Modyfikacja View jest prosta po prostu dodajemy button

<Button x:Name="About" Content="About Window" />

Chcemy aby po kliknięciu w przycisk About otworzyło się okno About. Potrzeba do tego trzech kroków:

  1. Stworzenie odpowiedniego View. Czyli AboutView przy czym polecam aby przenieść go do folderu (namespace) Views w celu zachowania czytelności.
  2. Stworzenie odpowiedniego ViewModelu. Czyli AboutViewModel (niech na razie dziedziczy tak jak MainWindowViewModel z PropertyChangedBase). Jeśli zastosowana została sugestia z punktu powyżej to należy umieścić go w folderze (namespace) ViewModels.
  3. Obsługa przycisku. Czyli napisanie w MainWindowViewModel metody About.

Zatrzymajmy się na chwilę przy ostatnim podpunkcie. Jak ją prawidłowo zaimplementować? Gdybyśmy nie mieli Caliburna to w implementacja wyglądałaby mniej więcej taki:

public void About()
{
    AboutView about=new AboutView();
    about.ShowDialog();
}

Tylko, że z założenia kod ViewModelu nie powinien nic wiedzieć o View. Pomocna okaże się przy tym klasa WindowManager. Otóż po jej wcześniejszym zainicjowaniu (na razie niech to odbywa się w konstruktorze ViewModelu) wystarczy wywołać, którąś z jej metod (w zależności od przyjętego modelu zachowania aplikacji).

public void About()
{
    _windowManager.ShowDialog(new AboutViewModel());
}

Kod tego rozwiązania jest dostępny na githubie pod tagiem WindowManager.
Klasa PropertyChangedBase jest dość uboga jeśli chodzi o jej możliwości i nadaję się wyłącznie do najprostszych zastosowań jeśli chcemy czegoś więcej (np. zamykania okna po wystąpieniu określonego zdarzenia, zdarzeń na starcie okna) to potrzebujemy klasy Screen (oczywiście posiada ona w swojej strukturze dziedziczenia wspomnianą wcześniej klasę). Z ciekawszych rzeczy klasa ta posiada property DisplayName, której ustawienie zmienia nazwę okna (jeśli jej nie ustawimy to jako nazwę okna będziemy mieć nazwę klasy tj. this.ToString() chyba, że przykryjemy to wcześniej jakimś bindingiem). Wspomniane wcześniej zamykanie okna obsłużymy metodą TryClose. Ponadto znajdują się tam metody takie jak

  • OnActivate
  • OnInitialize

Jeśli ktoś jest przyzwyczajony do WindowsForms to może to być dla niego lekko mylące (w końcu w formsach było wyłącznie zdarzenie OnLoad). W każdym bądź razie przy określonym sposobie tworzenia aplikacji może okazać się, że OnInitialize wystąpi raz a OnActivate wiele razy dlatego konieczne jest to rozróżnienie. Kod aplikacji dostępny jest na githubie pod tagiem AboutViewModel.

Wszystko w jednym oknie

Poza standardowym sposobem (czyli każdy View to osobne okno) często spotyka się sytuację że program ma jedno okno a zmienia się jego zawartość. Takie zachowanie jest również dostępne w Caliburn.Micro (w aplikacjach mobilnych to wręcz podstawa). Jak więc to osiągnąć? Należy zmienić klasę bazową naszego ViewModelu ze Screen na Conductor. Nasza klasa Conductor dziedziczy po Screen. Kod dostępny jest na githubie pod tagiem SimpleConductor.
W pierwszym kroku tworzymy odpowiedni ViewModel

public class MultiViewModel : Conductor<object>
{
    public void SampleA()
    {
        ActivateItem(new SampleAViewModel());
    }

    public void SampleB()
    {
        ActivateItem(new SampleBViewModel());
    }
}

A teraz w powiązanym z nim View dodajemy coś takiego

<StackPanel>
   <StackPanel Orientation="Horizontal">
       <Button x:Name="SampleA" Content="Sample A" />
       <Button x:Name="SampleB" Content="Sample B" />
    </StackPanel>
    <ContentControl x:Name="ActiveItem" />
</StackPanel>

Kliknięcie któregoś z przycisków spowoduje, że w elemencie ActiveItem (znów konwencja) pojawi się odpowiedni widok. Tworząc widok należy tylko pamiętać, że nie tworzymy go już nie jako Window tylko jako UserControl.
Cofnijmy się na chwilę do kodu ViewModelu.
Dlaczego mamy:

public class MultiViewModel : Conductor<object>{...}

zamiast

public class MultiViewModel : Conductor<PropertyChangedBase>{...}

albo

public class MultiViewModel : Conductor<IScreen>{...}

Przyczyną jest fakt, że możemy(chcemy) zrobić ActivateItem na dowolnym obiekcie niekoniecznie na ViewModelu dziedziczącym po PropertyChangedBase (przy czym konwencja nazewnicza jest dalej potrzebna). Co jeszcze warto powiedzieć o Klasie Conductor? Może to, że Conductor można wywołać jeszcze na dwa sposoby:

Conductor<T>.Collection.OneActive
Conductor<T>.Collection.AllActive

Chcących dowiedzieć się czym się różnią te sposoby odsyłam na stronę projektu.
W następnej części opiszę przesyłanie danych pomiędzy elementami interfejsu.

Cykl o MVVM

One thought on “WPF – Caliburn.Micro – obsługa okien

  1. Pingback: dotnetomaniak.pl

Comments are closed.