【问题标题】:MVVM: Is code-behind evil or just pragmatic?MVVM:代码隐藏是邪恶的还是实用的?
【发布时间】:2017-03-08 12:55:24
【问题描述】:

想象一下,您想要一个 Save & Close 和一个 Cancel & Close 按钮在您花哨的 WPF MVVM 窗口上?

你会怎么做? MVVM 规定您将按钮绑定到 ICommand,控制反转规定您的 View 可能知道您的 ViewModel,但反之则不然。

在网上搜索,我找到了一个解决方案,它有一个 ViewModel 关闭事件,View 订阅如下:

private void OnLoaded(Object sender
    , RoutedEventArgs e)
{
    IFilterViewModel viewModel = (IFilterViewModel)DataContext;
    viewModel.Closing += OnViewModelClosing;
}

private void OnViewModelClosing(Object sender
    , EventArgs<Result> e)
{
    IFilterViewModel viewModel = (IFilterViewModel)DataContext;
    viewModel.Closing -= OnViewModelClosing;
    DialogResult = (e.Value == Result.OK) ? true : false;
    Close();
}

但那是我迄今为止设计得非常好的 MVVM 的代码隐藏。

另一个问题是在显示主窗口时显示许可问题消息框。我可以再次像上面那样使用Window.Loaded 事件,但这也破坏了 MVVM,不是吗?

在这些情况下,是否有一种干净的方法,或者应该务实而不是迂腐?

【问题讨论】:

  • "MVVM 指示您将按钮绑定到 ICommand" 是吗?您仍然可以添加 Click 处理程序而不会破坏任何内容。务实。
  • mvvm != 没有代码隐藏。另一方面,模式为我们服务,我们不为模式服务。

标签: c# wpf mvvm data-binding code-behind


【解决方案1】:

看看一些工具包,例如MVVMLight 具有 EventToCommand,它允许您将命令绑定到事件。我通常会尽力限制 View 中的逻辑,因为它更难测试。

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:command="http://www.galasoft.ch/mvvmlight"

...

<i:Interaction.Triggers>
   <i:EventTrigger EventName="Loaded">
     <command:EventToCommand Command="{Binding YourCommandInVM}"/>
   </i:EventTrigger>
</i:Interaction.Triggers>

【讨论】:

  • 谢谢,这将有助于解决消息框的问题。但是一个特性的整个工具包依赖是要付出沉重的代价的。 :-(
  • @Dee J. Doena 完全理解,但是 1)它还有其他有用的功能 2)它所做的并不神奇,我认为它是开源的,所以你可以看看源代码看看有什么困难的方法:)
  • 您刚刚努力将代码从 view.cs 移到标记中。标记很可能(如果不是更多)被合并等损坏,那么后面的代码就是。
  • @Ian 你这是什么意思?我已将代码(事件处理程序主体)从视图移动到视图模型。您是在建议我应该测试绑定吗?它是强类型的,你想在那里测试什么?唯一可以弄乱的就是事件名称。
  • 这并不是说你会在不尝试的情况下搞砸“事件处理程序的主体”,并且在编译时不会检查标记中的“强类型”,所以在你之前不会显示错误使用那部分用户界面。
【解决方案2】:

使用背后的代码没有错或对,这主要是基于意见,取决于您的偏好。

这个example 展示了如何使用 MVVM 设计模式关闭窗口,而无需隐藏代码。

<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding ShowLoginCommand}" CommandParameter="{Binding ElementName=LoginWindow}"/> 
<!-- the CommandParameter should bind to your window, either by name or relative or what way you choose, this will allow you to hold the window object and call window.Close() -->

基本上你将窗口作为参数传递给命令。 IMO 你的视图模型不应该知道控制,所以这个版本不是那么好。我会将Func&lt;object&gt;/ 某个接口传递给视图模型,以便使用依赖注入关闭窗口。

【讨论】:

  • 您能否给我一个代码 sn-p,说明如何编写将 Window.`Close()' 方法作为命令参数的 XAML?
  • 添加了主要部分,更详细的例子请看例子。
【解决方案3】:

有时我会使用变通方法。

假设你有一个view“MainWindow”和一个viewmodel“MainWindowVM”。

public class MainWindowVM
{
    private MainWindow mainWindow;
    public delegate void EventWithoudArg();
    public event EventWithoudArg Closed;

    public MainWindowVM()
    {
        mainWindow = new MainWindow();
        mainWindow.Closed += MainWindow_Closed;
        mainWindow.DataContext = this;
        mainWindow.Loaded += MainWindow_Loaded;
        mainWindow.Closing += MainWindow_Closing;
        mainWindow.Show();
    }

    private void MainWindow_Loaded()
    {
        //your code
    }

    private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        //your code
    }

    private void MainWindow_Closed()
    {
        Closed?.Invoke();
    }
}

在这里,我将我的视图存储在一个私有变量中,以便您可以在需要时访问它。它破坏了一点 MVVM。
在我的视图模型中,我创建了一个新的 view 并显示它。
在这里,我还捕获了 view 的关闭事件并将其传递给自己的事件。
您还可以为您的view.Loaded.Closing 事件添加一个方法。

在 App.xaml.cs 中,您只需创建一个新的 viewmodel 对象。

public partial class App : Application
{
    public App()
    {
        MainWindowVM mainWindowVM = new MainWindowVM();
        mainWindowVM.Closed += Mwvm_Close;
    }

    private void Mwvm_Close()
    {
        this.Shutdown();
    }
}

我创建一个新的viewmodel 对象并捕获它自己的关闭事件并将其绑定到应用程序的关闭方法。

【讨论】:

    【解决方案4】:

    您的描述表明视图模型是某种文档视图。如果这是正确的,那么我会将Save, Close, etc. 留给文档容器处理,例如应用程序或主窗口,因为这些命令位于文档之上的级别,就像复制/粘贴位于应用程序级别一样。事实上,ApplicationCommands 已经为 Save 和 Close 预定义了命令,这表明了框架作者的某种方法。

    【讨论】:

      【解决方案5】:

      首先,创建一个只包含Close 方法的接口:

      interface IClosable
      {
          void Close();
      }
      

      接下来,让你的窗口实现IClosable:

      class MyWindow : Window, IClosable
      {
          public MyWindow()
          {
              InitializeComponent();
          }
      }
      

      然后让视图将自己作为IClosable作为命令参数传递给视图模型:

      <Button Command="{Binding CloseCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
      

      最后,命令调用Close

      CloseCommand = new DelegateCommand<IClosable>( view => view.Close() );
      

      我们现在有什么?

      • 我们有一个关闭窗口的按钮
      • 我们在代码隐藏中没有代码除了, IClosable
      • 视图模型对视图一无所知,它只是获取一个可以关闭的任意对象
      • 该命令可以轻松进行单元测试

      【讨论】:

      • 但是如果用户以另一种方式关闭窗口,例如使用操作系统的窗口管理器.....(NT窗口角落的X。)
      • 这不是原始问题的一部分,但可以通过EventTrigger 使用InvokeCommandAction 完成,然后使用相同的命令...
      • 或者只是在标记中将该按钮标记为关闭按钮,并在后面的代码中放置一个简单的单行事件处理程序,VM 公开一个 Close() 方法,然后视图可以调用。
      猜你喜欢
      • 2011-01-02
      • 2011-03-21
      • 2011-04-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-28
      • 1970-01-01
      • 2010-09-16
      相关资源
      最近更新 更多