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.
我已经创建了我的第一个 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.