我创建了我的第一个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
)
谢谢
答案 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或任何会触发视图的内容。
**并且在你说之前,我不相信你违反任何mvvm规则这样做,显示消息框或对话是一个与演示相关的逻辑,应该在你的解决方案的视图部分处理; 通过发送消息的视图模型实际上并不知道任何关于视图的想法,他只是做一些工作并广播状态消息, 最后是关于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}}}"