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

后端 未结 8 1604
醉梦人生
醉梦人生 2021-02-06 04:43

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 ViewM

相关标签:
8条回答
  • 2021-02-06 05:16

    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>
    
    0 讨论(0)
  • 2021-02-06 05:24

    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.

    0 讨论(0)
提交回复
热议问题