为什么在MVVM模式中这样做是正确的,而使用messageBox不是?我看不出区别(MVVM Light)

5
我找到了一个使用MVVM Light向用户显示消息的小例子。我猜它是如何使用MVVM Light的,即它尊重MVVM模式。
视图代码后端:
namespace DialogosPruebas
{
    /// <summary>
    /// Lógica de interacción para MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            Messenger.Default.Register<DialogMessage>(
                this,
                msg =>
                {
                    var result = MessageBox.Show(
                        msg.Content,
                        msg.Caption,
                        msg.Button);

                    // Send callback
                    msg.ProcessCallback(result);
                });
        }
    }
}

ViewModel 是:

using System;
using System.Windows;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;

namespace DialogosPruebas.ViewModel
{
    /// <summary>
    /// This class contains properties that the main View can data bind to.
    /// <para>
    /// Use the <strong>mvvminpc</strong> snippet to add bindable properties to this ViewModel.
    /// </para>
    /// <para>
    /// You can also use Blend to data bind with the tool's support.
    /// </para>
    /// <para>
    /// See http://www.galasoft.ch/mvvm
    /// </para>
    /// </summary>
    public class MainViewModel : ViewModelBase
    {
        private const string Login = "abcd1234";

        public RelayCommand<string> CheckLoginCommand
        {
            get;
            private set;
        }

        /// <summary>
        /// The <see cref="Message" /> property's name.
        /// </summary>
        public const string MessagePropertyName = "Message";

        private string _message = "Login";

        /// <summary>
        /// Gets the Message property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public string Message
        {
            get
            {
                return _message;
            }
            set
            {
                if (_message == value)
                {
                    return;
                }    
                _message = value;
                RaisePropertyChanged(MessagePropertyName);
            }
        }

        /// <summary>
        /// Initializes a new instance of the MainViewModel class.
        /// </summary>
        public MainViewModel()
        {
            CheckLoginCommand = new RelayCommand<string>(CheckLogin);
        }

        private void CheckLogin(string text)
        {
            if (text == Login)
            {
                var message = new DialogMessage("Login confirmed, do you want to continue", DialogMessageCallback)
                {
                    Button = MessageBoxButton.OKCancel,
                    Caption = "Continue?"
                };
                Messenger.Default.Send(message);
            }
        }

        private void DialogMessageCallback(MessageBoxResult result)
        {
            if (result == MessageBoxResult.OK)
            {
                Message = "Continue";
            }
            else
            {
                Message = "Stop";
            }
        }
    }
}

AXML文件:
<Window x:Class="DialogosPruebas.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF4"
        Title="MainWindow" Height="350" Width="525">


    <Window.DataContext>
        <Binding Path="Main" Source="{StaticResource Locator}"/>
    </Window.DataContext>

    <Grid>
    <StackPanel x:Name="LayoutRoot" Background="Black">
        <TextBlock FontSize="36"
            FontWeight="Bold"
            Foreground="Purple"
            Text="{Binding Message}"
            VerticalAlignment="Center"
            HorizontalAlignment="Center"
            TextWrapping="Wrap" Margin="0,10" />

        <TextBox x:Name="LoginTextBox" TextWrapping="Wrap" Margin="10,0" FontSize="21.333" Text="Enter login">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="KeyUp">
                    <cmd:EventToCommand Command="{Binding CheckLoginCommand, Mode=OneWay}" CommandParameter="{Binding Text, ElementName=LoginTextBox}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </TextBox>

        <TextBlock TextWrapping="Wrap" Text="(Enter abcd1234 to trigger the message)" HorizontalAlignment="Center" Margin="0,10,0,0" FontSize="16" Foreground="White"/>

    </StackPanel>
    </Grid>
</Window>

我的疑问是,我们在视图的代码后面使用MessageBox,而ViewModel有以下代码:

var message = new DialogMessage("Login confirmed, do you want to continue", DialogMessageCallback)
                {
                    Button = MessageBoxButton.OKCancel,
                    Caption = "Continue?"
                };

                Messenger.Default.Send(message);

这将向视图发出请求,到代码后台,然后使用MessageBox

为什么这比直接在ViewModel中使用MessageBox的解决方案更好呢:

private void CheckLogin(string text)
        {
            if (text == Login)
            {
                MessageBox.Show("Login correct");
            }
        }

有什么不同吗?在这两种情况下,我都使用了MessageBox,并且必须等待用户的响应。

我已经阅读过,在viewModel中使用MessageBox不是一个好主意,但我不明白在这种情况下有什么区别。


4
这基本上是更好的解决方案,因为您不希望ViewModel知道任何关于UI的信息。通过使用消息系统,View能够显示MessageBox而不直接将它与ViewModel耦合(ViewModel不应包含任何UI控件)。 - d.moncada
2
MVVM是一种将最琐碎的任务复杂化的好方法。 :) - Gigi
3
@Gigi,那不是真的。MVVM 是解决可能存在或不存在的问题的一种解决方案。请看我的回答。我创建了自己的多平台框架,发现自己不得不抽象出许多看似“简单”和“琐碎”的东西,由于平台无关性。在平台“A”中被认为“琐碎”的任何事情,在平台“B”中可能不受支持或具有完全不同的方法。 - Federico Berasategui
@HighCore 我对 MVVM 模式非常熟悉 :) 在许多情况下,它非常好用,但在其他情况下则显得过于复杂。如果你正在进行跨平台开发,则确实需要抽象化。但对于许多较为简单的应用程序来说,抽象化本身往往会成为阻碍。正如这里所示的情况,当视图需要根据 VM 进行条件更新时 - 消息框、动画等等 - 使用 MVVM 就没有不使用它那么容易了。 - Gigi
3
编写一个单元测试来覆盖这个方法,然后回来自己回答这个问题。 - user1228
你可能会发现 https://github.com/brianchance/MvvmCross-UserInteraction 很有趣。虽然它是为 MvvmCross 设计的,但其思想是相同的。 - Derek Beattie
2个回答

7

我认为这种方法可行的原因有两个:

1-你的ViewMoeels需要进行单元测试。

在单元测试中,弹出模态对话框(例如MessageBox)会导致各种问题。解耦的Messenger方法是安全的,因为在单元测试中,要么没有人监听消息,要么有一个模拟的监听器只返回“Yes”以应对所有面向用户的提示。

2-你的ViewModels应该在其他平台上被重用。

如果你只针对Windows(WPF),那就不用太担心这个问题。

完全将UI与ViewModels分离的主要问题是,如果你要在其他平台上重复使用你的ViewModels,那么你就需要将代码抽象化并在每个情况下提供特定于平台的代码。

例如,在Android中没有MessageBox.Show(),因此,如果您打算在那里重用ViewModel的“应用程序逻辑”,则需要将该代码抽象化并为每种情况提供特定于平台的代码。

如果这些都对你不重要,那么在ViewModels中引发MessageBoxes以及其他视图特定问题(例如窗口关闭)也是可以的,这些问题可能由于MVVM所需的抽象而过于复杂,而没有任何收益。


我在考虑这个解决方案。一个包装器,一个dll,它是一个接口,用枚举来设置messageBox的类型,这样视图模型就不需要使用MessageBoxButton,因为这在Silverlight中不存在。另一个dll项目实现了这个接口。实现接口的类不是MVVM,只使用messageBox。这是一个好的解决方案吗?视图模型可以执行单元测试,并不知道类展示对话框的方式。 - Álvaro García
1
@ÁlvaroGarcía 看起来不错。我已经实现了类似的解决方案。请记住,所有与平台无关的代码可能存储在 PCL(可移植类库)中。 - Federico Berasategui

2

区别在于,通过发送一条消息(DialogMessage),您的ViewModel要求View显示一条消息。如何显示消息实际上取决于View。在这种情况下,View将显示一个简单的MessageBox,但它也可以使用UserControl来显示自定义对话框。

使用消息,ViewModel不需要知道消息将如何显示,因此它仍然与View分离。


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