MVVMLight:在命令中询问信息/确认?

时间:2015-01-23 07:58:08

标签: c# wpf command mvvm-light separation-of-concerns

我创建了我的第一个MVVMLight项目,我提出了一个问题:

我有一个按钮,上面有一个命令。当命令执行时,在不同的用例中,我要向最终用户获取/提供信息,如:

  • 询问项目是否为新项目时​​应保存项目
  • 确认所有内容均已正确保存

我知道我可以做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

谢谢

3 个答案:

答案 0 :(得分:3)

Laurent Bugnion在他的mvvmlight库中引入了一个非常方便的助手类Messenger,使用它可以在视图模型,视图或视图模型/视图之间发送和接收通知和/或信息。 这里是如何运作的

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

要在您的情况下应用此功能,您必须在后面的代码中添加一些与视图相关的逻辑,以显示DialogueBox和确认消息。  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或任何会触发视图的内容。

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

答案 1 :(得分:0)

关于SoC的问题基本上有两个方面: 1.如何从视图模型触发UI交互(例如显示消息框)(这意味着无UI)。 2.您如何分离当前需求的不同形式,例如在一个地方展示确认,而不是在另一个地方。

第一个问题归结为“隐藏界面背后的UI交互”。其他评论者已经解决了这个问题,所以我在此不再赘述。

关于第二个问题:您当然可以在命令中将实际调用放入消息框(意味着相应的接口调用,但让它保持简单)并使用某些条件来决定是否调用。一般来说,这种方法有两个可能的问题: - 您的命令“增加权重”,因为它必须在每个上下文中运行,从而累积代码和逻辑依赖性。 - 您的“显示确认”和其他内容的逻辑在其他情况下也可能有用,但您无法重复使用它。

这些问题的可能答案是“链接命令”。即有一个“ConfirmationCommand”(对象或方法),它接收消息和委托或命令以进行后续活动。它会显示一个消息框,并根据按钮单击调用一个代理或另一个代理。在一个上下文中,您可以简单地使用SaveCommand,在另一个上下文中,您可以使用ConfirmationComand 附加到它的SaveCommand。 这样,您就可以从较小的命令链构建您的acuall命令逻辑,这些命令反过来变得更通用,更少依赖于上下文,因此更具可重用性。 当然还有更多内容,例如传递参数的问题,但这应该足以让您对该方法有一个大概的了解,

HIH,

答案 2 :(得分:-1)

创建一个“Dialog Service”类,该类具有显示MessageBox的公共ShowMessage方法。

然后,从此类中提取接口并将其用作viewmodel中的成员。使用dependency injection你可以注入它,甚至更好,让像Unity这样的IOC容器将它注入你的viewmodel。这样对话框服务将实例化消息框。如果您是创建单元测试,则可以通过从单元测试中实例化新的对话框服务来模拟对话框服务。

编辑

示例:

private RelayCommand<Window> doSomethingCommand;

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

窗口参数应该作为commandParameter绑定到XAML窗口Element,如下所示:

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

第二次编辑

以下是如何从UserControl

绑定到窗口的方法
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, 
AncestorType={x:Type Window}}}"