Voordat ik laat zien hoe je in Silverlight een webcam capture programma maakt met MVVM, wil ik eerst dieper in gaan op de commands die ik vorige keer introduceerde.
De SayHelloCommand class die ik liet zien, werkt prima. Maar het is natuurlijk niet echt handig om een class te schrijven voor ieder stukje functionaliteit. Om dit wat generieker op te zetten, kwam Josh Smith met de RelayCommand. In deze post laat ik zien hoe dat werkt en hoe je het gebruikt.
Eerst nog even onze vorige implementatie:
public class SayHelloCommand : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
MessageBox.Show("Hello Silverlight");
}
}
Zoals je ziet is de logica vast geprogrammeerd in deze class. Laten we dit even generieker maken.
We beginnen met een base class voor onze ViewModels. Immers, alle ViewModels hebben dezelfde implementatie van INotifyPropertyChanged, dus die kunnen we mooi in een base class plaatsen:
public abstract class ObservableObject : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected virtual void RaisePropertyChanged(string propertyName)
{
var eventCopy = PropertyChanged;
if (eventCopy == null)
return;
eventCopy(this, new PropertyChangedEventArgs(propertyName));
}
}
En uiteraard passen we onze ViewModel even aan:
public class MyViewModel : ObservableObject
{
private SayHelloCommand _SayHelloCmd = new SayHelloCommand();
public ICommand SayHelloCmd
{
get
{
return _SayHelloCmd;
}
}
}
We hebben in onze ViewModel geen properties, dus we gebruiken de base class niet echt, maar het staat er maar vast voor later.
We maken nu een nieuwe class aan, met de naam RelayCommand. Deze leiden we af van ObservableObject. Ook implementeerd deze de ICommand interface:
public class RelayCommand : ObservableObject, ICommand
{
public bool CanExecute(object parameter)
{
throw new NotImplementedException();
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
throw new NotImplementedException();
}
}
Dit moet nu allemaal bekend zijn.
Voeg een paar private members toe:
#region Private members
readonly Action<object> _Execute;
readonly Predicate<object> _CanExecute;
#endregion
Deze gaan dadelijk de implementatie van de logica bevatten en worden aangeroepen in de Execute en de CanExecute. We moeten deze uiteraard meegegeven, dus we maken ook een constructor:
#region Constructors
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute", "execute is null.");
_Execute = execute;
_CanExecute = canExecute;
}
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
#endregion
We hebben twee constructors gemaakt. In de eerste geven we de execute en de canexecute mee, in de tweede alleen de execute waardoor de canExecute vanzelf null wordt.
De implementatie van de Execute en CanExecute zien er als volgt uit:
public bool CanExecute(object parameter)
{
return _CanExecute == null ? true : _CanExecute(parameter);
}
public void Execute(object parameter)
{
if (CanExecute(parameter))
_Execute(parameter);
}
In de CanExecute kijken we eerst of er een predicate aanwezig is om uit te voeren. Als deze er niet is, geven we standaard true terug. Anders geven we het resultaat van de predicate terug.
In de Execute roepen we eerst de CanExecute aan (gewoon, om aan de veilige kant te zitten) en daarna roepen we de _Execute action aan. Deze is nooit null, immers deze wordt gezet in de constructor.
Dit is alles wat we nodig hebben. Laten we de ViewModel even aanpassen. De SayHelloCommand class kunnen we weggooien, die hebben we niet meer nodig. In de ViewModel passen we de code aan. De _SayHelloCmd private member en de SayHelloCmd property veranderen we in de volgende code:
public class MyViewModel : ObservableObject
{
private RelayCommand _SayHelloCmd;
public ICommand SayHelloCmd
{
get
{
if (_SayHelloCmd == null)
_SayHelloCmd = new RelayCommand(SayHelloCmdExecuted);
return _SayHelloCmd;
}
}
private void SayHelloCmdExecuted(object paramNotUsed)
{
MessageBox.Show("Hello Silverlight and MVVM");
}
}
We maken pas een instance van de RelayCommand aan op het moment dat deze nodig is. Daarnaast hebben we een private method gemaakt die de logica bevat welke uitgevoerd moet worden bij het aanroepen van de Command. Dus: die logica zit niet meer in een aparte SayHelloCommand class maar gewoon in de ViewModel. En daar hoort hij ook!
De XAML blijft hetzelfde, de binding van de Command in de Button is nog steeds {Binding SayHelloCmd} dus dat is gelijk.
Uiteraard kun je ook de logica voor de CanExecute toevoegen, maar dat laat ik even aan jou over (hint: de signature is private bool SayHelloCmdCanExecute(object paramNotUsed).
Nog even over die paramNotUsed: bij de binding kun je ook een commandparameter opgeven (dus in je XAML), deze wordt meegegeven in de Execute en CanExecute. In dit voorbeeld heb ik het niet nodig, dus ik heb hem maar even paramNotUsed genoemd. Uiteraard hoort hij wel in de signature thuis, anders klopt de Action<object> niet.
Je kunt deze class natuurlijk uitbreiden: in plaats van RelayCommand die Action<object> heeft, kun je er een RelayCommand<T> van maken met een Action<T> waardoor je stronly typed parameters krijgt. Ook dat kun je nu zelf wel uitvogelen.
De volgende keer: de webcam snapshot applicatie in Silverlight 4 met MVVM met onze ObservableObject en RelayCommand implementaties!