Implementing MVVM in WPF without using System.Windows.Input.ICommand

I'm trying to implement a WPF application using MVVM (Model-View-ViewModel) pattern and I'd like to have the View part in a separate assembly (an EXE) from the Model and ViewModel parts (a DLL).

The twist here is to keep the Model/ViewModel assembly clear of any WPF dependency. The reason for this is I'd like to reuse it from executables with different (non-WPF) UI techs, for example WinForms or GTK# under Mono.

By default, this can't be done, because ViewModel exposes one or more ICommands. But the ICommand type is defined in the System.Windows.Input namespace, which belongs to the WPF!

So, is there a way to satisfy the WPF binding mechanism without using ICommand?

Thanks!


You should be able to define a single WPF custom routed command in your wpf layer and a single command handler class. All your WPF classes can bind to this one command with appropriate parameters.

The handler class can then translate the command to your own custom command interface that you define yourself in your ViewModel layer and is independent of WPF.

The simplest example would be a wrapper to a void delegate with an Execute method.

All you different GUI layers simply need to translate from their native command types to your custom command types in one location.


WinForms doesn't have the rich data binding and commands infrastructure needed to use a MVVM style view model.

Just like you can't reuse a web application MVC controllers in a client application (at least not without creating mountains of wrappers and adapters that in the end just make it harder to write and debug code without providing any value to the customer) you can't reuse a WPF MVVM in a WinForms application.

I haven't used GTK# on a real project so I have no idea what it can or can't do but I suspect MVVM isn't the optimal approach for GTK# anyway.

Try to move as much of the behavior of the application into the model, have a view model that only exposes data from the model and calls into the model based on commands with no logic in the view model.

Then for WinForms just remove the view model and call the model from the UI directly, or create another intermediate layer that is based on WinForms more limited data binding support.

Repeat for GTK# or write MVC controllers and views to give the model a web front-end.

Don't try to force one technology into a usage pattern that is optimized for another, don't write your own commands infrastructure from scratch (I've done it before, not my most productive choice), use the best tools for each technology.


I needed an example of this so I wrote one using various techniques.

I had a few design goals in mind

1 - keep it simple

2 - absolutely no code-behind in the view (Window class)

3 - demonstrate a dependency of only the System reference in the ViewModel class library.

4 - keep the business logic in the ViewModel and route directly to the appropriate methods without writing a bunch of "stub" methods.

Here's the code...

App.xaml (no StartupUri is the only thing worth noting)

<Application 
    x:Class="WpfApplicationCleanSeparation.App" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</Application>

App.xaml.cs (load up the main view)

using System.Windows;
using WpfApplicationCleanSeparation.ViewModels;

namespace WpfApplicationCleanSeparation
{
    public partial class App
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            var view = new MainView();
            var viewModel = new MainViewModel();

            view.InitializeComponent();
            view.DataContext = viewModel;
            CommandRouter.WireMainView(view, viewModel);
            view.Show();
        }
    }
}

CommandRouter.cs (the magic)

using System.Windows.Input;
using WpfApplicationCleanSeparation.ViewModels;

namespace WpfApplicationCleanSeparation
{
    public static class CommandRouter
    {
        static CommandRouter()
        {
            IncrementCounter = new RoutedCommand();
            DecrementCounter = new RoutedCommand();
        }

        public static RoutedCommand IncrementCounter { get; private set; }
        public static RoutedCommand DecrementCounter { get; private set; }

        public static void WireMainView(MainView view, MainViewModel viewModel)
        {
            if (view == null || viewModel == null) return;

            view.CommandBindings.Add(
                new CommandBinding(
                    IncrementCounter,
                    (λ1, λ2) => viewModel.IncrementCounter(),
                    (λ1, λ2) =>
                        {
                            λ2.CanExecute = true;
                            λ2.Handled = true;
                        }));
            view.CommandBindings.Add(
                new CommandBinding(
                    DecrementCounter,
                    (λ1, λ2) => viewModel.DecrementCounter(),
                    (λ1, λ2) =>
                        {
                            λ2.CanExecute = true;
                            λ2.Handled = true;
                        }));
        }
    }
}

MainView.xaml (there is NO code-behind, literally deleted!)

<Window 
    x:Class="WpfApplicationCleanSeparation.MainView" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:WpfApplicationCleanSeparation="clr-namespace:WpfApplicationCleanSeparation" 
    Title="MainWindow" 
    Height="100" 
    Width="100">
    <StackPanel>
        <TextBlock Text="{Binding Counter}"></TextBlock>
        <Button Content="Decrement" Command="WpfApplicationCleanSeparation:CommandRouter.DecrementCounter"></Button>
        <Button Content="Increment" Command="WpfApplicationCleanSeparation:CommandRouter.IncrementCounter"></Button>
    </StackPanel>
</Window>

MainViewModel.cs (includes the actual Model as well since this example is so simplified, please excuse the derailing of the MVVM pattern.

using System.ComponentModel;

namespace WpfApplicationCleanSeparation.ViewModels
{
    public class CounterModel
    {
        public int Data { get; private set; }

        public void IncrementCounter()
        {
            Data++;
        }

        public void DecrementCounter()
        {
            Data--;
        }
    }

    public class MainViewModel : INotifyPropertyChanged
    {
        private CounterModel Model { get; set; }
        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        public MainViewModel()
        {
            Model = new CounterModel();
        }

        public int Counter
        {
            get { return Model.Data; }
        }

        public void IncrementCounter()
        {
            Model.IncrementCounter();

            PropertyChanged(this, new PropertyChangedEventArgs("Counter"));
        }

        public void DecrementCounter()
        {
            Model.DecrementCounter();

            PropertyChanged(this, new PropertyChangedEventArgs("Counter"));
        }
    }
}

Just a quick and dirty and I hope it's useful to someone. I saw a few different approaches through various Google's but nothing was quite as simple and easy to implement with the least amount of code possible that I wanted. If there's a way to simplify even further please let me know, thanks.

Happy Coding :)

EDIT: To simplify my own code, you might find this useful for making the Adds into one-liners.

    private static void Wire(this UIElement element, RoutedCommand command, Action action)
    {
        element.CommandBindings.Add(new CommandBinding(command, (sender, e) => action(), (sender, e) => { e.CanExecute = true; }));
    }
链接地址: http://www.djcxy.com/p/56116.html

上一篇: 打开文件对话框MVVM

下一篇: 在不使用System.Windows.Input.ICommand的情况下在WPF中实现MVVM