Tehdään WPF-sovellus - 25 - Navigoinnin toteutus
- 5 minsNavigointi tapahtuu ViewModelia vaihtamalla
Olemme aiemmin lisänneet päänäkymälle ResultView-näkymän omana elementtinään. Haluamme kuitenkin vaihtaa tätä näkymää aina, kun alareunan painiketta painetaan. Eli tarvitsemme tähän jonkin hieman dynaamisemman vaihtoehdon.
Laitetaan ResultViewin tilalle ContentControl, joka nimensä mukaisesti huokuu hyvää paikkaa esittää sisältöä. Lisätään myös MainViewModelille uusi property CurrentViewModel
, jonka tarkoitus on ylläpitää tietoa missä näkymässä käyttäjä milloinkin on.
ContentControlia
tulisi käyttää ViewModelien kanssa, mutta tässä yksi menettelevä. Bindataan ContentControlin Content
MainViewModelin CurrentViewModel
-propertyyn.
-<local:ResultView />
+<ContentControl Content="{Binding CurrentViewModel}" />
public class MainViewModel : ViewModel
{
private ViewModel _currentViewModel;
public ViewModel CurrentViewModel
{
get => _currentViewModel;
private set => SetProperty(ref _currentViewModel, value)
}
// ...
Voimme asettaa CurrentViewModelin
setterin private
, koska emme halua kuin Navigate
-komennon muokkaavan tätä arvoa.
Navigate-komento vaihtaa ViewModelia
Seuraavaksi tarvitsemme ViewModelit, joiden välillä haluamme Navigate
-komennon vaihtavan.
Lisätään MainViewModelille
talteen InputViewModel
ja ResultViewModel
omiin kenttiinsä, jotta voimme antaa komennon asettaa näistä toisen aina vuorollaan CurrentViewModelin
arvoksi. Alustetaan myös nämä uudet muuttujat rakentajassa.
public class MainViewModel : ViewModel
{
private readonly InputViewModel _inputViewModel;
private readonly ResultViewModel _resultViewModel;
// ...
public MainViewModel()
{
_inputViewModel = new InputViewModel();
_resultViewModel = new ResultViewModel();
}
Haluamme, että ensimmäinen näkymä, jonka käyttäjä näkee on painon ja pituuden syötekentät. Asetetaan siis CurrentViewModelin
arvoksi suoraan rakentajassa juuri luomamme _inputViewModel
:
public MainViewModel()
{
// ...
CurrentViewModel = _inputViewModel;
}
Käynnistetään sovelluksemme tässä vaiheessa ja ihmetellään näppäimistöllä pyyhältäneiden sormiemme tuotoksia:
.. no eihän se nyt aivan oikealta näytä, vai mitä?
Bindasimme aiemmin ContentControl.Content
-arvon suoraan ContentViewModel
-propertyyn, mutta emme ole kertoneet sovellukselle, että miten haluamme tuota tietoa näyttää.
ToString()
-kutsun tuottaman tekstin. (vapaaehtoinen vaihe) ToString() ylikirjoittaminen
Voit kokeilla miten ToString()
:n ylikirjoittaminen oikein käytännössä vaikuttaa näkymään näin:
1) Lisää InputViewModelille
ylikirjoitus haluamallasi tekstillä
public override string ToString()
{
return "Kissimirre";
}
2) Käännä ja suorita ohjelma
Näkymällä pitäisi näkyä ToString()
-funktion palauttama arvo. Hienoa! 😺
(vapaaehtoinen vaihe loppui)
DataTemplate määrittää miltä ViewModel näyttää
DataTemplaten avulla voimme kertoa WPF:lle, että miltä haluamme tarjotun tiedon näyttävän ruudulla.
Yksinkertaisuudessaan voimme lisätä kaksi uutta DataTemplate
-elementtiä App.xaml
-tiedostoon muiden tyylien joukkoon:
<DataTemplate DataType="{x:Type local:InputViewModel}">
<local:InputView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ResultViewModel}">
<local:ResultView />
</DataTemplate>
DataType
kertoo DataTemplatelle
, että minkä tyyppiselle tiedolle sen tulisi luoda näkymä. Meidän ei välttämättä olisi tarvinnut luoda Input- ja ResultViewejä ollenkaan, mutta tekemällä näin saimme jaettua näkymiä helposti hallittaviin pienempiin palasiin. Olisimme voineet määritellä DataTemplaten
myös näin avaamalla ResultViewin auki DataTemplaten sisälle:
<DataTemplate DataType="{x:Type local:ResultViewModel}">
<Border Margin="24"
Style="{StaticResource ItemBorder}">
<Grid VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="BMI" />
<TextBlock Grid.Row="1"
Text="{Binding Result}" />
</Grid>
</Border>
</DataTemplate>
Nyt kun suoritamme ohjelman, niin hommat alkavat näyttämään vähän toiveekkaammalta:
Joko sitä komentoa pääsee toteuttamaan?
Jos nyt sitten tämän kerran.
Loimme aiemmin osassa 23 DelegateCommand
-toteutuksen ICommand
-rajapinnalle. Nyt pääsemme vihdoinkin sitä käyttämään!
Alustetaan Navigate
-propertymme DelegateCommandiksi
ja nikkaroidaan sen sisälle painikkeen painamisesta tapahtuva logiikka.
Voimme määrittää DelegateCommandin
parametrit (execute
ja canExecute
) kätevästi käyttämällä lambdoja. Asetetaan execute
vaihtamaan CurrentViewModel
tulosten näyttämisen ViewModeliin. Sallitaan myös vielä tässä vaiheessa painikkeen painaminen aina tilanteesta riippumatta palauttamalla suoraan true
canExecute
-kyselylle:
Navigate = new DelegateCommand(
_ =>
{
CurrentViewModel = _resultViewModel;
},
_ => true);
_
-merkkiä voi käyttää perinteisemmän x
-merkin sijaan lambdassa, jos emme ole kiinnostuneita tästä parametrista (kts. discard). Esimerkiksi emme vielä tarvitse komennon execute
-parametrin arvoa, joten voimme merkata sen “hylätyksi” arvoksi käyttämällä x
:n sijaan _
-merkintää. Navigointi toimii nyt nätisti tulosnäkymään, kun painiketta painetaan. Käy vaikka kokeilemassa. Hienoa!
Vielä vähän ratkottavaa
Meillä on käsissämme vielä muutama ongelma ratkottavana:
1) Painikkeen teksti ei muutu sanaan “BACK” siirryttäessä tulosnäkymälle
2) Tulosnäkymältä ei pääse palaamaan takaisin
3) Painoindeksiä ei lasketa ollenkaan (hups.)
4) Navigointi tulosnäkymään onnistuu, vaikka emme olisikaan syöttäneet mitään arvoja
Tässä voisi olla hyvää tekemistä seuraavalle osalle!