WPF – Caliburn.Micro – Gdy standardowa konwencja to za mało

Czasami są takie dni w życiu człowieka (a nawet programisty), że wszystko idzie zgodnie z planem. Każde zadanie, które dostaje jest banalne albo (lepsza opcja) zrobione już wcześniej. Miałem kiedyś taki dzień. Jakby to powiedział Skipper dzień idealny. Niestety w moim przypadku to się nie sprawdziło. W moim przypadku sprawdza się raczej “jeśli wszystko idzie zgodnie z planem to znaczy, że to zasadzka”. No ale wracając do meritum dostałem kiedyś “banalne” zadanie musiałem wymienić tło w głównym oknie aplikacji (nie mój pomysł – klient płaci klient wymaga). No co za problem pomyślałem. Na szybkiego przypomniałem sobie jakie są opcje. A więc jednolity kolor czyli to co było wtedy:

<StackPanel x:Name="MainWindowPanel" Background="Plum">
....
<StackPanel>

Jeśli klientowi nie odpowiada jednostajne tło to zawsze można rzucić jakiś gradient (tylko niech lepiej on, nie ja określi kolory):

<StackPanel x:Name="MainWindowPanel">
    <StackPanel.Background>
        <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
            <GradientStop Color="Yellow" Offset="0.0" />
            <GradientStop Color="Red" Offset="0.5" />
            <GradientStop Color="BlanchedAlmond" Offset="1.0" />
        </LinearGradientBrush>
    </StackPanel.Background>
....
<StackPanel>

A w ostateczności tło z pliku:

<StackPanel x:Name="MainWindowPanel">
    <StackPanel.Background>
        <ImageBrush ImageSource="..\Assets\wallpaper.jpg" />
    </StackPanel.Background>
....
<StackPanel>

Problem rozpracowany czekam tylko na informacje, w który sposób mam to zrobić. Gdy wreszcie dowiedziałem się jak mam to zrobić zrozumiałem, że to była zasadzka. Klient zażyczył sobie aby w zależności od rozdzielczości załadował się inny obraz. Nie byłby to żaden problem gdyby nie to, że chciałem mieć w Caliburn.Micro czysty code-behind. To w ViewModelu miała znaleźć się metoda, która miała odpowiadać za wybór właściwego pliku jako tła. Muszę przyznać, że korciło mnie niemiłosiernie bo wystarczyło przecież wpisać w code-behind takie coś w sumie trywialnego (dla uproszczenia pominięty został fragment, który odczytuje rozdzielczość i na jej podstawie wyznacza obraz do wyświetlenia):

private void MainWindowPanel_Loaded(object sender, RoutedEventArgs e)
{
    Uri uriSource = new Uri("pack://application:,,,/Assets/wallpaper.jpg", UriKind.RelativeOrAbsolute);
    BitmapImage source = new BitmapImage(uriSource);
    MainWindowPanel.Background = new ImageBrush(source);
}

Powiem więcej przez pewien (bardzo krótki) czas tak ten kod wyglądał. Staram się rozwiązywać problemy jak najłatwiej (be pragmatic). Nie robię czegoś w określony sposób tylko dlatego, że tak wypada. Jeśli coś jest wygodne to mi to nie przeszkadza (nawet jeśli kuje w oczy). Dlaczego więc ten kod się nie ostał mimo tego, że działał i robił to co do niego należało? Zrozumiałem,że było to pójście na skróty (niebezpieczne skróty), dziś to, jutro to i zanim się obejrzę cały kod wyląduje w code-behind. Nie ze mną te numery szatanie. Jak więc sobie z tym poradzić? Jest kilka sposobów najprostszy to oczywiści binding:

<StackPanel x:Name="MainWindowPanel">
    <StackPanel.Background>
        <ImageBrush ImageSource="{Binding Wallpaper}" />
    </StackPanel.Background>
....
<StackPanel>

Teraz tylko w ViewModelu należy określić property Wallpaper typu string. I tyle by wystarczyło tylko że ja chciałem więcej. Chciałem aby mój View wyglądał w następujący sposób

<StackPanel x:Name="MainWindowPanel">
....
<StackPanel>

Chciałem prostego sposobu aby w ViewModelu ustawić property MainWindowPanel typu ImageBrush (na wypadek gdyby szalony klient chciał aby tło było dynamicznie wyliczane). Tylko, że nie ma takiej konwencji (“niedopracowana technologia” jak to mawia mój znajomy). Wiem, że można zrobić własną konwencję (czasem powstaje pytanie czy warto, ale to odrębna kwestia), ale nigdy tego nie robiłem a skoro tak to najwyższy czas i pora.

Otóż w Caliburn.Micro jest taka statyczna klasa ConventionManager. Naszym celem jest właśnie dopisanie do niej naszej nowej konwencji korzystając ze statycznej metody AddElementConvention czyli w tym konkretnym przypadku byłoby to:

ConventionManager.AddElementConvention<Panel>(Panel.BackgroundProperty, "Background", "Initialize");

Oczywiście dodanie nowej konwencji należy wykonać w Bootstrapperze naszej aplikacji.
Klasa ConventionManager potrafi znacznie więcej niż tutaj przedstawiłem (np. z jej pomocą możemy stworzyć konwencje która będzie ustawiała Visibility na zasadzie podobnej do IsEnabled). Wystarczy tylko podglądnąć jej metody w VS. Nie należy jednak przesadzać z ilością własnych konwencji bo tym sposobem tylko osłabiamy framework (nowa osoba w zespole musi nauczyć się naszych konwencji).

3 thoughts on “WPF – Caliburn.Micro – Gdy standardowa konwencja to za mało

  1. Pingback: dotnetomaniak.pl
  2. Przed-przed ostatni code snippet, w którym ustawiasz StackPanel.Background wydaje mi się najlepszym wyjściem. Podoba mi się w tym jawność takiej deklaracji – patrzę tu z perspektywy innego dewelopera. Natomiast ukrywanie tego w Konwencji to już trochę za dużo 🙂 Czemu chciałeś ukryć ten feature w Konwencji? (pytam z ciekawości :))

    1. Po prostu chciałem to zrobić inaczej i sprawdzić siebie (no bo nie robiłem tego wcześniej). Choć faktycznie im dłużej na to patrzę to ustawienie StackPanel.Background wydaje się najlepszym rozwiązaniem.

Comments are closed.