Skip to content
Dustin Leavins edited this page Mar 7, 2014 · 14 revisions

KSMVVM.WPF - Kinda Small MVVM Toolkit

KSMVVM.WPF is still a work in progress, so this wiki is also a work in progress.

Example Unit Test for KVVM.WPF

Commands

KSMVVM has two base classes to use when implementing the ICommand interface for commands.

BasicCommand

BasicCommand is the easier of the two to use, but it's also the least flexible of the two. It's intended for use in existing projects using code-behind.

ICommand.Execute(param) invokes an Action delegate (note the lack of a parameter type)

ICommand.CanExecute(param) invokes a Func<bool> delegate

ICommand.CanExecuteChanged is implemented using a hack that just forwards everything to CommandManager.RequerySuggested. This event fires regularly, so CanExecute() gets called very regularly. There will be performance consequences if the handler is resource-intensive.

CustomCommand

CustomCommand is the ICommand implementation that you will likely want to use in new projects. It's very similar to BasicCommand, but with a few key differences.

ICommand.Execute(param) still invokes a delegate, but for CustomCommand, it's of type Action<object>.

ICommand.CanExecute(param) invokes a Func<object, bool> delegate.

ICommand.CanExecuteChanged is implemented as a 'real' event. CustomCommand comes with a built-in public method (TriggerCanExecuteChanged()) that allows you to manually trigger the event.

Which One to Use?

New code should use CustomCommand. It's not dependent on a shortcut that can cause major performance problems.

If you're having trouble with using CustomCommand, or you're porting existing code-behind to MVVM, you might want to consider using BasicCommand because it's the easier of the two to use. BasicCommand does not require you to call TriggerCanExecuteChanged() (and doesn't even have that method) and the value of CanExecute should always be up-to-date.

ProxyCommand

KSMVVM.WPF has a third ICommand implementation: ProxyCommand. An instance of ProxyCommand is a wrapper around a backing ICommand instance (which can be null).

ProxyCommand can be helpful if you want to change a command's implementation at runtime without changing bindings that use it. A good use case for ProxyCommand is a window-level "save" command. Not every page needs a save command. You can set BackingCommand when a user navigates to a page needing save functionality. Set BackingCommand to null to disable the ProxyCommand instance.

Navigation

In most MVVM implementations for WPF, navigation occurs through messaging. You send a message from ViewModel to View saying "you should navigate to this page", and the View actually handles navigation.

You can still use this method in KSMVVM (using BasicMessenger or another messaging class), but KSMVVM has a IAppNavigationService interface for classes implementing navigation control.

Here's what you do to navigate to HomePage:

  1. Pass/inject an IAppNavigationService instance (Nav) to your ViewModel
  2. From the ViewModel, call Nav.Navigate(() => new HomePage()).

Why pass a delegate to Nav.Navigate()? It's designed that way to enable unit tests to check for navigation requests without actually instantiating the Page instance.

KSMVVM.WPF has two different implementations of IAppNavigationService. PageNavigationService wraps around Page.NavigationService(), and MockAppNavigationService (found in the Testing namespace) helps you check nav requests in your unit test code.

ViewModelBase

There are two (completely optional) base classes you can use for View Models. Both are in the ViewModel namespace.

ViewModelBase implements INotifyPropertyChanged. That's it.

ViewModelBase<T> extends ViewModelBase and provides a Model property of type T.

Messaging

KSMVVM has a very light messaging class called BasicMessenger. How light is it? It's basically a wrapper for a ConcurrentDictionary<string, Action>() instance. Here's how you use it for one-way ViewModel to View communication.

// In ViewModel
BasicMessenger.Default.Send("msg-id");

// Page Code-Behind; possibly in Loaded handler
BasicMessenger.Default.Register("msg-id", () => { MessageBox.Show("Hi world."); });

// Page Code-Behind; possibly in Unloaded handler
BasicMessenger.Default.Unregister("msg-id");

There are a few limitations of BasicMessenger.

  1. You cannot currently pass message data to a handler
  2. You can only register a single listener for each ID.