Tehdään WPF-sovellus - 23 - Komennon toteutus
- 3 minsKomennon toteuttaminen yhdellä luokalla
ICommand
-rajapinnan toteuttavan luokan property voidaan bindata Button
-elementin Command
-attribuuttiin. Meillä ei vain kylläkään ole yhtäkään toteutusta vielä ICommand
-rajapinnalle.. Tehdään tähän muutos!
RoutedCommand
- tai RoutedUICommand
-toteutuksiin. Näistä lisää täällä. Aloitetaan lisäämällä MVVM-kansioon uusi luokka DelegateCommand
ja määrätään se toteuttamaan ICommand
-rajapinta:
using System;
using System.Windows.Input;
namespace Kettunen.BMICalculator.WPFClient.MVVM
{
public class DelegateCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
throw new NotImplementedException();
}
public void Execute(object parameter)
{
throw new NotImplementedException();
}
}
}
Komento koostuu yksinkertaisimmillaan näistä kolmesta rajapinnan määrittelemästä komponentista.
CanExecuteChanged
-tapahtuma laukeaa, kun komennon suoritettavuus (CanExecute) muuttuu.CanExecute
-funktiolla tehdään tarkastus voiko komennon suorittaa nykyisessä ohjelman tilassa.Execute
-funktiota kutsutaan, josCanExecute
palauttaa true ja esim. painiketta on painettu näkymältä.
ICommand
on olemassa. Toteutetaan oma komentomme hyvin yleiskäyttöiseksi, jotta emme joutuisi tekemään aina uutta tyyppiä uudelle komennolle esim. CalculateCommand
, BackCommand
jne..
Rakentajan toteuttaminen
Aloitetaan rakentajasta. Komennon tarkoitus on siis suorittaa toiminto ja olla tietoinen siitä, että voiko toimintoa ylipäätään suorittaa kyseisessä ohjelman tilassa.
Näkymä antaa komennolle parametriksi null
tai object
-tyyppisen instanssin kutsuessaan komentoa. Määrittelemme näkymän puolella annettavan parametrin myöhemmin.
Näiden tietojen perusteella voimme nikkaroida rakentajan kasaan:
public class DelegateCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
// ...
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
}
CanExecute ja Execute
Näiden lisäysten jälkeen CanExecuten
ja Executen
toteuttaminen on hyvin suoraviivaista. Kutsutaan niissä vain juuri lisäämiämme _canExecute
- ja _execute
-toimintoja.
public bool CanExecute(object parameter) => _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
executen
ja canExecuten
. Tällöin meidän tulisi ottaa null
useammin huomioon, joten olen jättänyt tämän toteutuksen vain mahdollisimman yksinkertaiselle tasolle. CanExecuteChanged
CanExecuteChanged
-tapahtuman voimme toteuttaa käyttämällä CommandManageria
hyödyksi:
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
CommandManager.RequerySuggested
laukaistaan aina silloin, kun CommandManager
havaitsee, että jokin komennon suoritukseen vaikuttava tekijä on muuttunut. Esimerkiksi focuksen muuttuminen elementiltä toiselle. Täällä lisää aiheesta. Ja mitäs ihmettä. Sanoisin, että se oli siinä! 💪
Mietteitä tästä kaikesta
Sinänsä tuntuu erikoiselta, ettei WPF:n kirjastoissa ole mukana oletustoteutusta jo valmiiksi ICommand
- tai INotifyPropertyChanged
-rajapinnoille. Väittäisin, että kuitenkin suurin osa WPF-sovelluksista nojaa juurikin näiden rajapintojen onnistuneisiin toteutuksiin.
Toisaalta toimiviksi todettuja MVVM-paketteja on runsaasti mistä valita, joten ehkä oletustoteutusta ei ole katsottu tarpeelliseksi.
Tähän väliin voisi melkeinpä suositella Tim Coreyn läpikäyntiä eri MVVM-frameworkeista (video).
Seuraavassa osassa luodaan Input- ja Result-näkymille omat ViewModelinsa. Siinä puuhassa päästäänkin käyttämään ViewModel.SetProperty
-funktiota tarkoituksenmukaisella tavallaan!