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

aov*_*ven 8 wpf mvvm

我正在尝试使用MVVM(Model-View-ViewModel)模式实现WPF应用程序,并且我希望View部件位于Model和ViewModel部件(DLL)的单独程序集(EXE)中.

这里的转折是保持Model/ViewModel程序集清除任何WPF依赖项.这样做的原因是我想从具有不同(非WPF)UI技术的可执行文件中重用它,例如Mono下的WinForms或GTK#.

默认情况下,无法执行此操作,因为ViewModel公开了一个或多个ICommands.但ICommand类型是在System.Windows.Input命名空间中定义的,该命名空间属于WPF!

那么,有没有办法在不使用ICommand的情况下满足WPF绑定机制?

谢谢!

mor*_*lli 7

您应该能够在wpf层和单个命令处理程序类中定义单个WPF自定义路由命令.所有WPF类都可以使用适当的参数绑定到这一个命令.

然后,处理程序类可以将命令转换为您自己在ViewModel层中定义的自定义命令界面,并且独立于WPF.

最简单的示例是使用Execute方法的void委托的包装器.

所有不同的GUI层只需要在一个位置从其本机命令类型转换为自定义命令类型.


Neu*_*ino 6

抱歉戴夫,但我不太喜欢你的解决方案。首先,您必须在代码中手动为每个命令编写管道,然后您必须配置 CommandRouter 以了解应用程序中的每个视图/视图模型关联。

我采取了不同的方法。

我有一个 Mvvm 实用程序集(没有 WPF 依赖项),并在我的视图模型中使用它。在该程序集中,我声明了一个自定义 ICommand 接口和一个实现该接口的 DelegateCommand 类。

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我还有一个 Wpf 库程序集(它确实引用了系统 WPF 库),我从我的 WPF UI 项目中引用了它。在该程序集中,我声明了一个具有标准 System.Windows.Input.ICommand 接口的 CommandWrapper 类。CommandWrapper 是使用我的自定义 ICommand 实例构建的,并且只需将 Execute、CanExecute 和 CanExecuteChanged 直接委托给我的自定义 ICommand 类型。

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的 Wpf 程序集中,我还创建了一个 ValueConverter,当传递自定义 ICommand 的实例时,它会吐出 Windows.Input.ICommand 兼容的 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,我的视图模型可以将命令公开为自定义命令类型的实例,而不必依赖于 WPF,并且我的 UI 可以使用我的 ValueConverter 将 Windows.Input.ICommand 命令绑定到这些视图模型,如下所示。(已忽略 XAML 命名空间垃圾邮件)。

<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>
Run Code Online (Sandbox Code Playgroud)

现在,如果我真的很懒(我就是这样),并且不愿意每次都手动应用 CommandConverter,那么在我的 Wpf 程序集中,我可以创建自己的 Binding 子类,如下所示:

namespace WpfUtil
{
    using System.Windows.Data;


    public class CommandBindingExtension : Binding
    {
        public CommandBindingExtension(string path) : base(path)
        {
            Converter = new CommandConverter();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

所以现在我可以更简单地绑定到我的自定义命令类型,如下所示:

<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>
Run Code Online (Sandbox Code Playgroud)


Nir*_*Nir 5

WinForms没有使用MVVM样式视图模型所需的丰富数据绑定和命令基础结构.

就像你不能在客户端应用程序中重用Web应用程序MVC控制器一样(至少在没有创建大量包装器和适配器的情况下,最终只会使编写和调试代码变得更加困难而不向客户提供任何价值)在WinForms应用程序中重用WPF MVVM.

我没有在一个真实的项目中使用GTK#所以我不知道它能做什么或不能做什么但是我怀疑MVVM不是GTK#的最佳方法.

尝试将应用程序的大部分行为移动到模型中,使视图模型仅公开模型中的数据,并根据视图模型中没有逻辑的命令调用模型.

然后对于WinForms,只需删除视图模型并直接从UI调用模型,或者创建另一个基于WinForms的更有限数据绑定支持的中间层.

重复GTK#或编写MVC控制器和视图,为模型提供Web前端.

不要试图强迫一种技术成为针对他人优化的使用模式,不要从头开始编写自己的命令基础结构(我以前做过,而不是我最有效的选择),为每种技术使用最好的工具.