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条回答
爱情/是我丢掉的垃圾
2楼-- · 2020-05-27 09:10

Off course this is possible. You can create just another level of abstraction. Add you own IMyCommand interface similar or same as ICommand and use that.

Take a look at my current MVVM solution that solves most of the issues you mentioned yet its completely abstracted from platform specific things and can be reused. Also i used no code-behind only binding with DelegateCommands that implement ICommand. Dialog is basically a View - a separate control that has its own ViewModel and it is shown from the ViewModel of the main screen but triggered from the UI via DelagateCommand binding.

See full Silverlight 4 solution here Modal dialogs with MVVM and Silverlight 4

查看更多
forever°为你锁心
3楼-- · 2020-05-27 09:14

Sorry Dave but I didn't like your solution very much. Firstly you have to code the plumbing for each command manually in code, then you have to configure the CommandRouter to know about each view/viewmodel association in the application.

I took a different approach.

I have an Mvvm utility assembly (which has no WPF dependencies) and which I use in my viewmodel. In that assembly I declare a custom ICommand interface, and a DelegateCommand class that implements that interface.

namespace CommonUtil.Mvvm
{
    using System;


    public interface ICommand
    {
        void Execute(object parameter);
        bool CanExecute(object parameter);

        event EventHandler CanExecuteChanged;
    }

    public class DelegateCommand : ICommand
    {
        public DelegateCommand(Action<object> execute) : this(execute, null)
        {

        }

        public DelegateCommand(Action<object> execute, Func<object, bool> canExecute)
        {
            _execute = execute;
            _canExecute = canExecute;
        }

        public void Execute(object parameter)
        {
            _execute(parameter);
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute == null || _canExecute(parameter);
        }


        public event EventHandler CanExecuteChanged;

        private readonly Action<object> _execute;
        private readonly Func<object, bool> _canExecute;
    }
}

I also have a Wpf library assembly (which does reference the System WPF libraries), which I reference from my WPF UI project. In that assembly I declare a CommandWrapper class which has the standard System.Windows.Input.ICommand interface. CommandWrapper is constructed using an instance of my custom ICommand and simply delegates Execute, CanExecute and CanExecuteChanged directly to my custom ICommand type.

namespace WpfUtil
{
    using System;
    using System.Windows.Input;


    public class CommandWrapper : ICommand
    {
        // Public.

        public CommandWrapper(CommonUtil.Mvvm.ICommand source)
        {
            _source = source;
            _source.CanExecuteChanged += OnSource_CanExecuteChanged;
            CommandManager.RequerySuggested += OnCommandManager_RequerySuggested;
        }

        public void Execute(object parameter)
        {
            _source.Execute(parameter);
        }

        public bool CanExecute(object parameter)
        {
            return _source.CanExecute(parameter);
        }

        public event System.EventHandler CanExecuteChanged = delegate { };


        // Implementation.

        private void OnSource_CanExecuteChanged(object sender, EventArgs args)
        {
            CanExecuteChanged(sender, args);
        }

        private void OnCommandManager_RequerySuggested(object sender, EventArgs args)
        {
            CanExecuteChanged(sender, args);
        }

        private readonly CommonUtil.Mvvm.ICommand _source;
    }
}

In my Wpf assembly I also create a ValueConverter that when passed an instance of my custom ICommand spits out an instance of the Windows.Input.ICommand compatible CommandWrapper.

namespace WpfUtil
{
    using System;
    using System.Globalization;
    using System.Windows.Data;


    public class CommandConverter : IValueConverter
    {

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return new CommandWrapper((CommonUtil.Mvvm.ICommand)value);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new System.NotImplementedException();
        }
    }
}

Now my viewmodels can expose commands as instances of my custom command type without having to have any dependency on WPF, and my UI can bind Windows.Input.ICommand commands to those viewmodels using my ValueConverter like so. (XAML namespace spam ommited).

<Window x:Class="Project1.MainWindow">

    <Window.Resources>
        <wpf:CommandConverter x:Key="_commandConv"/>
    </Window.Resources>

    <Grid>
        <Button Content="Button1" Command="{Binding CustomCommandOnViewModel,
                                        Converter={StaticResource _commandConv}}"/>
    </Grid>

</Window>

Now if I'm really lazy (which I am) and can't be bothered to have to manually apply the CommandConverter every time then in my Wpf assembly I can create my own Binding subclass like this:

namespace WpfUtil
{
    using System.Windows.Data;


    public class CommandBindingExtension : Binding
    {
        public CommandBindingExtension(string path) : base(path)
        {
            Converter = new CommandConverter();
        }
    }
}

So now I can bind to my custom command type even more simply like so:

<Window x:Class="Project1.MainWindow"
                xmlns:wpf="clr-namespace:WpfUtil;assembly=WpfUtil">

    <Window.Resources>
        <wpf:CommandConverter x:Key="_commandConv"/>
    </Window.Resources>

    <Grid>
        <Button Content="Button1" Command="{wpf:CommandBinding CustomCommandOnViewModel}"/>
    </Grid>

</Window>
查看更多
登录 后发表回答