MVVM模式中,事件处理程序应该放在哪里?

4
我正在编写一个简单的MVVM应用程序来学习正确的代码设计。虽然花了一些时间,但事情进展顺利。
我的问题是如何处理事件,以及代码应该放在ViewModel还是Code-Behind中。
首先,有两种绑定事件的技术,一种是使用Blend交互性DLL来绑定到命令,另一种是使用MethodBindingExtension类。
使用交互性DLL,它允许使用EventArgs转换器将事件参数转换为仅包含所需数据的UI不可知类型。我认为MethodBindingExtension没有做到这一点,但它更加灵活。但是,当您需要设置事件参数值时,此事件参数转换器似乎无法帮助您?(或者它允许将值转换回去,还没有检查过这些类)
我喜欢使用MethodBindingExtension,并且现在我的ViewModel中有这段代码。我不喜欢它的地方在于我正在使用特定的UI类型,尽管现在并不是很重要,但从理论上讲,也许可以改进。
我该怎么处理这个问题?将它移动到代码后台?留在 ViewModel 中并使用参数转换器?还是保持现状?
public void Window_DropFile(DragEventArgs e) {
    if (e.Data.GetDataPresent(DataFormats.FileDrop)) {
        string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
        foreach (string file in files) {
            ReadScriptFile(file);
        }
    }
}

public void Window_PreviewDragOver(DragEventArgs e) {
    e.Effects = DragDropEffects.All;
    e.Handled = true;
}

public void Header_PreviewLeftMouseButtonDown(IScriptViewModel sender, MouseButtonEventArgs e) {
    if (sender == SelectedItem && sender.CanEditHeader && !sender.IsEditingHeader) {
        sender.IsEditingHeader = true;
        e.Handled = true;
    }
}
3个回答

7
一般来说: 你的MVVM viewmodel应该只包含数据和命令。当然,有很多例外情况,但是基本上,它应该只包含与视图相关的命令和项。
我看到很多人对此感到困惑,特别是在将事件处理程序应用于组件时。问题在于;当您在viewmodel中为UI组件设置事件处理程序时,viewmodel绑定到实际实现(具有UI组件)的视图,而不是一般“视图”。
作为一个经验法则;你应该能够复制并粘贴你的viewmodel,并在另一个实现中使用它,它应该可以编译。它不应该包含对UI元素本身的引用,也不应通过事件处理程序间接引用它们。
所以,是的,你应该把这些事件处理程序放在代码后面,或者找到一个能够在视图/XAML中处理它的框架或组件。从这样的事件调用一个命令是完全可以的。更实际的方法是:把它们放在最适合它们的位置,并使您的代码最可读/可维护。如果您理解MVVM的概念,您将尽量减少这些混合模型的出现,这通常已经足够好了。

2

事件处理程序只是为了方便传统代码的移植而添加的,在纯粹的MVVM中,您根本不需要使用它们。您的MVVM平台将有一些东西将事件指向视图模型中的命令处理程序,在MVVM Lite中,您可以使用EventToCommand,这里是我为在画布上拖动项目编写的一些代码片段:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd ="http://www.galasoft.ch/mvvmlight"
.
.
.
<i:Interaction.Triggers>
    <i:EventTrigger EventName="MouseDown">
        <cmd:EventToCommand Command="{Binding MouseDownCommand}" PassEventArgsToCommand="True" />
    </i:EventTrigger>
    <i:EventTrigger EventName="MouseUp">
        <cmd:EventToCommand Command="{Binding MouseUpCommand}" PassEventArgsToCommand="True" />
    </i:EventTrigger>
    <i:EventTrigger EventName="MouseMove">
        <cmd:EventToCommand Command="{Binding MouseMoveCommand}" PassEventArgsToCommand="True" />
    </i:EventTrigger>
</i:Interaction.Triggers>

请注意,事件参数也被传递到命令处理程序中,这使得视图模型可以在拖动期间捕获鼠标。这段代码是在我纯粹成为MVVM(Model-View-ViewModel)支持者之前编写的,现在我还设置了一个转换器(通过EventArgsConverter / EventArgsConverterParameter)将事件参数类型转换为自定义类型,以避免从视图模型代码引用Windows库。这意味着拖动操作也可以进行单元测试。

1
我会使用附加属性来完成这样的任务。例如,对于FileDrop,我会实现一个类似于以下内容的附加属性:
public static class WindowExtensions
{
    public static readonly DependencyProperty ReadScriptFilesCommandProperty = DependencyProperty.RegisterAttached(
        "ReadScriptFilesCommand",
        typeof(ICommand),
        typeof(WindowExtensions),
        new PropertyMetadata(default(ICommand), OnReadScriptFilesCommandChanged));

    private static void OnReadScriptFilesCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Window window = d as Window;
        if (window == null)
            return;

        if (e.NewValue is ICommand)
        {
            window.Drop += WindowOnDrop;
        }
        if (e.OldValue != null)
        {
            window.Drop -= WindowOnDrop;
        }
    }

    private static void WindowOnDrop(object sender, DragEventArgs e)
    {
        Window window = sender as Window;
        if (window == null)
            return;
        if (e.Data.GetDataPresent(DataFormats.FileDrop))
        {
            string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
            ICommand readScriptFilesCommand = GetReadScriptFilesCommand(window);
            readScriptFilesCommand.Execute(files);
        }
    }

    public static void SetReadScriptFilesCommand(DependencyObject element, ICommand value)
    {
        element.SetValue(ReadScriptFilesCommandProperty, value);
    }

    public static ICommand GetReadScriptFilesCommand(DependencyObject element)
    {
        return (ICommand)element.GetValue(ReadScriptFilesCommandProperty);
    }
}

因此,您可以在视图中的Window上设置它,并将其链接到视图模型中的ICommand。该命令接受string[]并执行逻辑。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接