- Honors the Model-View-ViewModel pattern.
- Keeps the view as simple as possible, ideally as a template and no code-behind.
- Uses the power of the Command pattern in WPF (Routed Commands), keeping the View agnostic about the logic encapsulated in commands.
After some attempts, the result are 2 classes that make this all possible in a very elegant way, I think. The first class is the ViewModelBase (fragment):
public abstract class ViewModelBase
{
// Something like the "Null object pattern"
protected static ViewModelBase EmptyViewModel = new EmptyViewModel();
public IEnumerable CommandBindings { get; }
public IEnumerable InputBindings { get; }
protected abstract void PrepareBindings();
}
The ViewModelBase class, as its name implies, is supposed to be the base class of all the ViewModels, and it is here where you define what to do when a RoutedCommand is executed. It is the glue between the RoutedCommands in the View and the command execution logic in the ViewModel.
Then, there is the ViewModelControl class (full text):
public class ViewModelControl : ContentControl
{
protected override void OnContentChanged(object oldContent, object newContent)
{
CommandBindings.Clear();
ViewModelBase viewModel = newContent as ViewModelBase;
if (viewModel != null)
{
foreach (var binding in viewModel.CommandBindings)
{
CommandBindings.Add(binding);
}
InputBindings.Clear();
foreach (var binding in viewModel.InputBindings)
{
InputBindings.Add(binding);
}
}
base.OnContentChanged(oldContent, newContent);
}
}
This control takes care of associating the CommandBindings to the visual elements so that the RoutedCommands mechanism works.
And that's it. There are some implementation details missing, but with these classes in place you just have to:
1- Create your ViewModel classes inheriting from ViewModelBase.
2- Provide the implementation of their PrepareBindings methods.
3- Create the templates for your ViewModels.
4- Bind your controls to the commands.
Using this implementation you'll get these results:
1- You'll have your RoutedCommands.
2- Logic for these commands will be in your ViewModel classes, where you can test it.
3- No event handlers, no code-behinds required.
Mix this with INotifyPropertyChanged in your ViewModels and DataModels and you'll have a cool architecture for your View. Here's an example:
1. Define a routed commands:
public class ViewCommands
{
public static RoutedCommand MyRoutedCommand;
static ViewCommands()
{
MyRoutedCommand = new RoutedCommand("MyRouted", typeof(ViewCommands));
}
}
2. Define a ViewModel:
public class MyViewModel : ViewModelBase
{
protected override void PrepareBindings()
{
this.AddCommandBindings(new CommandBinding(
ViewCommands. MyRoutedCommand,
(x, y) => DoSomethingIfMyRoutedIsExecutedOnThisViewModel(),
(x, y) => y.CanExecute = CanMyRoutedCommandBeExecuted));
}
}
3. Finally define a view with a ViewModelControl and some templates for your ViewModels and DataModels:
<Window
xmlns:baseViews="clr-namespace:Namespace.Of.ViewModelControl"
xmlns:models="clr-namespace:Namespace.Of.ViewModels"
...>
<Window.Resources>
<DataTemplate DataType="{x:Type models:MyViewModel}">
<Button Content="Execute MyRoutedCommand" Command="model:ViewCommands.MyRoutedCommand" />
</DataTemplate>
</Window.Resources>
<Grid>
<baseViews:ViewModelControl Content="{Binding}" />
</Grid>
</Window>
That's it. It works very nicely, try it.
I think I'll be posting a more complete article about this soon. In the mean time:
- What do you think?
- Want to see some real code?
No comments:
Post a Comment