Implementing MVVM in WPF without using System.Wind

2020-05-27 08:19发布

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!

标签: wpf mvvm
8条回答
SAY GOODBYE
2楼-- · 2020-05-27 08:48

" you can't reuse a WPF MVVM in a WinForms application"

For this please see url http://waf.codeplex.com/ , i have used MVVM in Win Form, now whenver i would like to upgrade application's presentation from Win Form to WPF, it will be changed with no change in application logic,

But i have one issue with reusing ViewModel in Asp.net MVC, so i can make same Desktop win application in Web without or less change in Application logic..

Thanks...

查看更多
Deceive 欺骗
3楼-- · 2020-05-27 08:51

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.

查看更多
女痞
4楼-- · 2020-05-27 09:01

Instead of the VM exposing commands, just expose methods. Then use attached behaviors to bind events to the methods, or if you need a command, use an ICommand that can delegate to these methods and create the command through attached behaviors.

查看更多
家丑人穷心不美
5楼-- · 2020-05-27 09:05

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.

查看更多
混吃等死
6楼-- · 2020-05-27 09:06

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"));
        }
    }
}

Proof

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; }));
    }
查看更多
爷、活的狠高调
7楼-- · 2020-05-27 09:08

I think you are separating your Project at wrong point. I think you should share your model and business logic classes only.

VM is an adaptation of model to suit WPF Views. I would keep VM simple and do just that.

I can't imagine forcing MVVM upon Winforms. OTOH having just model & bussiness logic, you can inject those directly into a Form if needed.

查看更多
登录 后发表回答