Tehdään WPF-sovellus - 21 - Ystävämme MVVM
- 4 minsPää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
{
}
}
public
-sana luokan esittelyn eteen, jotta se näkyisi muillekin luokille avoimesti. 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.
ICommand
-rajapinta on yleensä toteutettu RelayCommand
-, DelegateCommand
- tai ReactiveCommand
-nimisillä toteutuksilla. Teemme oman toteutuksen oppimismielessä hetken päästä, kun se tulee oleelliseksi 👌 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; }
}
}
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}"
... >
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.
d
-symbolin alla aaltoviivoja, voit hoveroida sitä ja katsoa, että mikä on vialla. Tässä vaiheessa on hyvä kääntää sovellus, jotta MainViewModel osataan lukea designerin puolella. Nyt meidän on helpompi tehdä painikkeen bindaus komentoon:
<Button Grid.Row="1"
Command="{Binding Navigate}">
<TextBlock Style="{x:Null}"
Text="CALCULATE" />
</Button>
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}" />
Mutta voi kurjaa. Bindaus ei onnnistukaan! Hoveroimalla näemme, että mikä mättää:
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";
}
=>
-symbolia käytettäessä puhutaan lambdasta. Käytännössä sillä korvataan tässä turhan verboosi get
-avainsanan käyttö. Lisää aiheesta täällä. Mutta voi kurjuuden kurjuus! Nyt kun käynnistämme sovelluksemme, niin vastassa onkin tyhjääkin tyhjempi painikkeen teksti:
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();
Noin! Nyt painikkeellakin näkyy teksti mukavasti.
Seuraavassa osassa luodaan ViewModeleillemme kantaluokka, joka helpottaa huomattavasti propertyjen ja näkymän päivittymisen kanssa.