Tehdään WPF-sovellus - 21 - Ystävämme MVVM

- 4 mins

Pääikkunan ViewModel vastaa navigoinnista

Tavoitteenamme on lisätä jokaisen näkymämme taustalle ViewModel hoitamaan asioita, jotta näkymän itse ei tarvitsisi tietää sovelluksemme taustatoiminnasta oikeastaan mitään.

Aloitetaan luomalla pääikkunallemme oma ViewModel lisäämällä uusi luokka MainViewModel omaan tiedostoonsa:

namespace Kettunen.BMICalculator.WPFClient
{
    public class MainViewModel
    {

    }
}
MainViewModel.cs

Tämä luokka tulee hoitamaan pääikkunamme toiminnallisuuden: navigoinnin syötekenttä- ja tulosnäkymän välillä. Ei unohdeta myöskään painikkeen tekstin vaihtamista näkymän muuttuessa.

Jäsennellään luokka ensin näiden tarpeiden perusteella ja lähdetään siitä sitten parantelemaan toteutusta tarpeidemme mukaisesti.

Painikkeeseen reagoidaan komennolla

WPF:ssä painikkeille on perinteistä, että ne bindataan ViewModelilla olevaan komentoon.

Komennot ovat ICommand-rajapinnasta periytettyjä luokkia. Niiden tehtävä on tarjota suoritettava toiminto ja tarkastelu, että voiko kyseistä toimintoa suorittaa nykyisessä tilassa.

Lisätään ICommand-tyyppinen “Navigate”-property MainViewModelille, joka voidaan bindata Viewin puolella seuraavasti:

+using System.Windows.Input;

namespace Kettunen.BMICalculator.WPFClient
{
    public class MainViewModel
    {
+        public ICommand Navigate { get; }
    }
}
MainViewModel.cs

Meidän tarvitsee kertoa näkymälle ensiksi, että minkä tyyppinen data sillä on taustallaan, eli DataContext-propertyssään. Tämä onnistuu käyttämällä d:DataContext-attribuuttia pääikkunan esittelyssä:

<Window ...
        d:DataContext="{d:DesignInstance Type={x:Type local:MainViewModel},
                                         IsDesignTimeCreatable=True}"
        ... >
MainWindow.xaml

d-nimiavaruudella kerromme, että emme ole oikeasti asettamassa pääikkunan DataContextia, vaan haluamme suunnittelun aikana käyttää kyseisen tyypin tietoja hyödyksemme. Tämä mahdollistaa sen, että VS:n IntelliSense poimii taustalla olevan ViewModelin propertyt ja meidän on helpompi luoda bindauksia näkymän puolella.

IsDesignTimeCreatable=true kertoo sen, että kun Visual Studion designer-näkymä lataa näkymän, niin se voi luoda kyseisestä ViewModelista taustalla uuden instanssin käyttämällä luokan oletusrakentajaa. Tässä tapauksessa designer kutsuu new MainViewModel() avautuessaan ja asettaa tämän instanssin DataContextikseen.

Nyt meidän on helpompi tehdä painikkeen bindaus komentoon:

<Button Grid.Row="1"
        Command="{Binding Navigate}">
    <TextBlock Style="{x:Null}"
                Text="CALCULATE" />
</Button>
MainWindow.xaml

Nyt jos klikkaat kursorin Navigate-tekstin päälle ja painat F12, niin editorin pitäisi viedä sinut suoraan ViewModelin puolelle. Jes, toimii!

Painikkeelle vaihtuva teksti bindaamalla

Haluamme, että painikkeen teksti vaihtuu sanaan “BACK”, kun tulosten näkymä näytetään. Bindataan siis painikkeen sisällä oleva tekstilaatikon teksti vastaavasti kuin bindasimme komennon painikkeelle:

<TextBlock Style="{x:Null}"
-           Text="CALCULATE" />
+           Text="{Binding NavigateText}" />
MainWindow.xaml

Mutta voi kurjaa. Bindaus ei onnnistukaan! Hoveroimalla näemme, että mikä mättää:

Bindaus ei onnistu - MainWindow.xaml.

Tämä oli kyllä ihan odotettuakin, sillä emme ole lisänneet vielä NavigateText-propertyä ViewModelin puolelle. Tekeminen on aikalailla Viewin ja ViewModelin välillä hyppimistä.

Annetaan nappulan tekstin olla vielä pelkkä “CALCULATE” tässä vaiheessa. Lisätään property ViewModelille:

public class MainViewModel
{
    public ICommand Navigate { get; }

    public string NavigateText => "CALCULATE";
}
MainViewModel.cs

Mutta voi kurjuuden kurjuus! Nyt kun käynnistämme sovelluksemme, niin vastassa onkin tyhjääkin tyhjempi painikkeen teksti:

Tyhjääkin tyhjempi painike.

Tämä johtuu siitä, että pääikkunamme DataContext on vielä täysin tyhjä. Korjatkaamme ongelma!

Koska olemme olleet nokkelia sovelluksemme käynnistyksen kanssa, niin voimme käydä vain luomassa uuden MainViewModelin instanssin OnStartup-funktiossamme ja asettamassa tämän ViewModelin pääikkunan DataContextiksi tähän tapaan:

var mainViewModel = new MainViewModel();
var mainWindow = new MainWindow
{
    DataContext = mainViewModel
};
mainWindow.Show();
App.xaml.cs

Noin! Nyt painikkeellakin näkyy teksti mukavasti.

Seuraavassa osassa luodaan ViewModeleillemme kantaluokka, joka helpottaa huomattavasti propertyjen ja näkymän päivittymisen kanssa.

Anssi Kettunen

Anssi Kettunen

Ohjelmistokehittäjä suorittamassa tehtävää 🦊

rss facebook twitter github gitlab youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora quora