MVVMLight:在命令中询问 information/confirmation?

MVVMLight: Ask information/confirmation in a command?

我已经创建了我的第一个 MVVMLight 项目,但我有一个问题:

我有一个按钮,上面绑定了一个命令。当命令执行时,在不同的用例中,我必须 get/give 向最终用户提供信息,例如:

我知道我可以做 MessageBox.Show/...但是在哪里?因为关于关注点分离,我猜它应该在 ViewModel 中?那么我应该使用什么机制呢?

我的ViewModel基本上是这样的:

public class MainViewModel : BaseViewModel
{
    private static readonly Logger m_logger = LoggerProvider.GetLogger("MyPath.MainViewModel");
    private ISerializationService m_serializationService;
    public ICommand TrySaveCommand { get; set; }

    //Lot of other fields here

    public MainViewModel()
    {
        m_serializationService = ServiceLocator.Current.GetInstance<ISerializationService>();
        TrySaveCommand = new RelayCommand(TrySave);
    }
    private void TrySave()
    {
        DispatcherHelper.RunAsync(() =>
        {           
            //Here I need to get the path where I save on some condition 
            m_serializationService.SaveProject(pathIGotFromTheUser);
            //Give a feedback that everything has been correctly saved(for test purpose, a MessageBox.Show() )
        });
    }
}

那么我应该怎样做才能从用户那里获取到文件上的信息来保存呢? (使用 SaveFileDialog )并显示它已正确保存(使用 MessageBox.Show

谢谢

创建一个 "Dialog Service" class,它有一个 public ShowMessage 方法显示 MessageBox.

然后,从此 class 中提取一个接口并将其用作视图模型中的成员。使用 dependency injection you can inject it, or even better, let an IOC container like Unity 将其注入您的视图模型。这样对话服务将实例化消息框。如果您要创建单元测试,则可以通过从单元测试中实例化一个新的对话服务来模拟对话服务。

编辑

示例:

private RelayCommand<Window> doSomethingCommand;

    public RelayCommand<Window> DoSomethingCommand
    {
        get
        {
            return doSomethingCommand
                ?? (doSomethingCommand= new RelayCommand<Window>(
                                      window =>
                                      {
                                         dialogService.SaveFileDialog.ShowDialog(window);
                                         // save the file
                                      }));
        }
    }

window 参数应在 XAML 中作为 commandParameter 绑定到 window Element,如下所示:

 <Button Content="" Command="{Binding DoSomethingCommand, Mode=OneWay}" 
         CommandParameter="{Binding ElementName=window, Mode=OneWay}"/>

第二次编辑

这是从 UserControl 绑定到 window 的方法:

CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, 
AncestorType={x:Type Window}}}"

你关于SoC的问题主要有两个方面: 1. 如何从视图模型(这意味着 UI 免费)触发 UI 交互(例如显示消息框)。 2. 您如何区分当前需求的不同化身,例如在一个地方显示确认,而不是在另一个地方。

第一个问题归结为"hide UI interactions behind an interface"。其他网友已经讨论过,这里不再赘述。

关于第二个问题:你当然可以把对messagebox的实际调用(意思是各自的接口调用,但让我们保持简单)放在你的命令中,并根据一些条件来决定是否调用。一般来说,这种方法有两个可能的问题: - 您的命令 "gains weight",因为它必须在每个上下文中运行,因此积累了代码和逻辑依赖性。 - 你的 "show confirmation" 和其他东西的逻辑在其他情况下也可能有用,但你不能重复使用它。

这些问题的可能答案是 "chaining of commands"。 IE。有一个 "ConfirmationCommand"(对象或方法)接收消息和委托或命令以进行后续活动。它显示一个消息框并调用一个委托或另一个委托,具体取决于按钮的单击。在一种情况下,您可以简单地使用 SaveCommand,在另一种情况下,您可以使用 ConfirmationComand and 附加到它的 SaveCommand。 通过这种方式,您可以从更小的命令链构建 acuall 命令逻辑,这些命令反过来变得更通用,更少依赖于上下文,因此更可重用。 当然还有更多,比如传递参数的问题,但是这应该足以让你对方法有一个大概的了解,

嗨,

Laurent Bugnion 在他的 mvvmlight 库中引入了一个非常方便的助手 class,称为 Messenger,使用它您可以在视图模型、视图或 viewmodel/view。 这是如何运作的

  • 使用 Messenger.Default.Send<..>(..) 视图模型广播消息,
  • 该消息将被注册到它的任何视图或视图模型拦截(使用 Messenger.Default.Register<>(..)),并根据该通知执行适当的逻辑(例如显示消息或对话框 ..)

要在您的案例中应用此功能,您必须在代码后面添加一些与视图相关的逻辑,以显示对话框和确认消息。 MainWndow.xaml.cs

 public partial class MainWindow : Window
{

    public MainWindow()
    {            
        Messenger.Default.Register<NotificationMessage>(this, (m) =>
        {
            switch (m.Notification)
            {
                case "SaveFile":
                    var dlg = new SaveFileDialog();
                    if (dlg.ShowDialog() == true)
                    {
                        var filename = dlg.FileName;
                        Messenger.Default.Send<String>( filename,"FileSaved");
                    }
                    break;
                case "WentWell":
                    MessageBox.Show("Everything went well Wohoo");
                    break;

            }
        });
    }
}

此处视图将根据广播的 ViewModel 通知显示对话框或确认消息框
在 MainWindowViewModel

 public class MainViewModel : ViewModelBase
{
    private static readonly Logger m_logger = LoggerProvider.GetLogger("MyPath.MainViewModel");
    private ISerializationService m_serializationService;

    private RelayCommand _trySaveCommand;
    public RelayCommand TrySaveCommand
    {
        get
        {
            return _trySaveCommand
                ?? (_trySaveCommand = new RelayCommand(
                () =>
                {                        
                    Messenger.Default.Send(new NotificationMessage("SaveFile"));    
                }));
        }
    }

    public MainViewModel()
    {
        m_serializationService = ServiceLocator.Current.GetInstance<ISerializationService>();
        Messenger.Default.Register<string>(this, "FileSaved", (pathIGotFromTheUser) =>
        {
            m_serializationService.SaveProject(pathIGotFromTheUser);
            //Give a feedback that everything has been correctly saved(for test purpose, a MessageBox.Show() )
            Messenger.Default.Send<NotificationMessage>(new NotificationMessage("WentWell"));
        });           
    }

与按钮关联的 TrySaveCommand 或任何将触发视图的内容。

**在你说之前,我不认为你这样做违反了任何 mvvm 规则,显示消息框或对话是与演示相关的逻辑,应该在你的解决方案的视图部分处理; 通过发送消息的视图模型实际上并不知道任何关于视图的想法,他只是做一些工作并广播状态消息, 最后是关于 Messenger 的一些深入信息 Class HERE.