我一直在玩弄哪里放“你确定吗?”在我的MVVM WPF应用程序中输入提示。
我倾向于认为这些纯粹是视图的一部分。如果ViewModel公开DeleteCommand
,那么我希望该命令立即删除。
要将此类提示集成到ViewModel中,它必须公开单独的RequestDeleteCommand
,DeletePromptItem
属性以绑定提示,并且还可以将其作为触发器显示提示。
即使这样,也没有什么能阻止直接调用DeleteCommand
的单元测试,除非我在ViewModel中放置特定逻辑,要求DeletePromptItem
将作为参数提供的项目与DeleteCommand
相匹配
然而,对我来说,这一切似乎只是ViewModel中的噪音。提示更像是一个用户界面问题,以防止错误点击等。对我来说,这表明它应该在视图中,确认提示调用DeleteCommand。
有什么想法吗?
答案 0 :(得分:15)
提示绝对不应该是ViewModel的一部分,但这并不一定意味着最好的解决方案是在View中对它们进行硬编码(尽管这是一种非常合理的第一种方法)。
我知道有两种方法可以减少View和ViewModel之间的耦合:使用交互服务和触发交互请求。两者都得到了很好的解释here;你可能想看看。
一般的想法是,您抽象异步交互的完成方式,并使用与基于事件的逻辑更相似的内容,同时允许ViewModel表达它希望与用户交互的一部分操作;最终结果是您可以记录此交互并对其进行单元测试。
编辑:我应该补充一点,我已经在原型项目中探索过使用Prism 4和交互请求,我对非常高兴的结果(带有一点框架)您可以使用XAML完全指定特定交互请求中的内容!)。
答案 1 :(得分:8)
然而,对我来说,这一切似乎只是ViewModel中的噪音。提示更像是一个用户界面问题,以防止错误点击等。对我来说,这表明它应该在视图中,确认提示调用DeleteCommand。
我同意;这样的提示应该在视图中处理,因为最终视图是用户看到和交互的内容,而不是视图模型。一旦您的视图获得了用户确认应调用DeleteCommand
的信息,请继续并在视图模型中调用它。
我看到它的方式,单元测试与用户交互并没有任何关系,除非您正在测试视图本身。
答案 2 :(得分:6)
在我看来,提示用户包含两部分:
第2部分显然不属于ViewModel 但第1部分确实属于那里。
为了实现这种分离,我使用了ViewModel可以使用的服务,我可以为其提供特定于我所处环境的实现(WPF,Silverlight,WP7)。
这导致代码如下:
interface IMessageBoxManager
{
MessageBoxResult ShowMessageBox(string text, string title,
MessageBoxButtons buttons);
}
class MyViewModel
{
IMessageBoxManager _messageBoxManager;
// ...
public void Close()
{
if(HasUnsavedChanges)
{
var result = _messageBoxManager.ShowMessageBox(
"Unsaved changes, save them before close?",
"Confirmation", MessageBoxButtons.YesNoCancel);
if(result == MessageBoxResult.Yes)
Save();
else if(result == MessageBoxResult.Cancel)
return; // <- Don't close window
else if(result == MessageBoxResult.No)
RevertUnsavedChanges();
}
TryClose(); // <- Infrastructure method from Caliburn Micro
}
}
这种方法不仅可以用来显示消息框,还可以用来显示其他窗口,如this answer中所述。
答案 3 :(得分:3)
我建议通过管理模态窗口的服务来完成此操作。不久前我也遇到过这个问题。 This博文对我帮助很大。
尽管它是一个银光灯,但与wpf相比,它不应该有太大差异。
答案 4 :(得分:1)
我认为这取决于提示,但一般来说,需要提示用户的代码逻辑通常在视图模型中,例如用户按下按钮删除列表项,命令被触发VM,逻辑运行,很明显,这可能会影响另一个实体,用户必须选择他们想做的事情,此时你不应该要求View提示用户,所以我看不到任何其他选择但要在VM中处理它。这是我一直不安的事情,但我只是在我的基本VM中写了一个Confirm方法,它为dsiplay调用一个对话服务提示并返回true或false:
/// <summary>
/// A method to ask a confirmation question.
/// </summary>
/// <param name="messageText">The text to you the user.</param>
/// <param name="showAreYouSureText">Optional Parameter which determines whether to prefix the message
/// text with "Are you sure you want to {0}?".</param>
/// <returns>True if the user selected "Yes", otherwise false.</returns>
public Boolean Confirm(String messageText, Boolean? showAreYouSureText = false)
{
String message;
if (showAreYouSureText.HasValue && showAreYouSureText.Value)
message = String.Format(Resources.AreYouSureMessage, messageText);
else
message = messageText;
return DialogService.ShowMessageBox(this, message, MessageBoxType.Question) == MessageBoxResult.Yes;
}
对我而言,这是灰色交叉领域之一,我有时无法在MVVM中得到一个明确的答案,所以我对其他poeples方法感兴趣。
答案 5 :(得分:1)
看看这个:
我在视图模型中使用了类似的技术,因为我认为它是视图模型的一部分,它会询问是否继续删除,而不是任何可视对象或视图。使用所描述的技术,您的模型不会引用任何我不喜欢的视觉引用,而是引用某种调用确认对话框或消息框或其他任何内容的服务。
答案 6 :(得分:1)
我过去处理它的方式是在ViewModel中放置一个事件,当需要显示对话框时触发该事件。 View挂钩到事件并处理显示确认对话框,并通过其EventArgs将结果返回给调用者。
答案 7 :(得分:1)
我认为“你确定吗?”提示属于viewmodel,因为它的应用程序逻辑而不是像动画等纯粹的ui。
所以最好的选择是在deletecommand执行方法中调用“你确定”service dialog。
编辑:ViewModel代码
IMessageBox _dialogService;//come to the viewmodel with DI
public ICommand DeleteCommand
{
get
{
return this._cmdDelete ?? (this._cmdDelete = new DelegateCommand(this.DeleteCommandExecute, this.CanDeleteCommandExecute));
}
}
将逻辑放在execute方法
中 private void DeleteCommandExecute()
{
if (!this.CanDeleteCommandExecute())
return;
var result = this.dialogService.ShowDialog("Are you sure prompt window?", YesNo);
//check result
//go on with delete when yes
}
对话框服务可以是您想要的任何内容,但删除之前要检查的应用程序逻辑位于您的viewmodel中。
答案 8 :(得分:0)
就我个人而言,我认为它只是View的一部分,因为没有数据
答案 9 :(得分:0)
我使用EventAggregator
模式解决了这类问题。
您可以看到它解释here
答案 10 :(得分:0)
在将旧的WinForms应用程序移植到WPF时,请执行此操作。我认为需要牢记的重要一点是,WPF通过在视图模型与带有事件的视图(即INotifyPropertyChanged.PropertyChanged
,INotifyDataErrorInfo.ErrorsChanged
等)之间发信号来完成其幕后工作。我对这个问题的解决方案是举一个例子并运行它。在我的视图模型中:
/// <summary>
/// Occurs before the record is deleted
/// </summary>
public event CancelEventHandler DeletingRecord;
/// <summary>
/// Occurs before record changes are discarded (i.e. by a New or Close operation)
/// </summary>
public event DiscardingChangesEvent DiscardingChanges;
然后视图可以侦听这些事件,在需要时提示用户,并在有指示的情况下取消该事件。
请注意,CancelEventHandler
是由框架为您定义的。但是,对于DiscardingChanges
,您需要一个三态结果来指示您要如何处理该操作(即保存更改,放弃更改或取消正在执行的操作)。为此,我做了自己的事情:
public delegate void DiscardingChangesEvent(object sender, DiscardingChangesEventArgs e);
public class DiscardingChangesEventArgs
{
public DiscardingChangesOperation Operation { get; set; } = DiscardingChangesOperation.Cancel;
}
public enum DiscardingChangesOperation
{
Save,
Discard,
Cancel
}
我试图提出一个更好的命名约定,但这是我能想到的最好的。
因此,将其付诸实践看起来像这样:
ViewModel(这实际上是我基于CRUD的视图模型的基类)
protected virtual void New()
{
// handle case when model is dirty
if (ModelIsDirty)
{
var args = new DiscardingChangesEventArgs(); // defaults to cancel, so someone will need to handle the event to signal discard/save
DiscardingChanges?.Invoke(this, args);
switch (args.Operation)
{
case DiscardingChangesOperation.Save:
if (!SaveInternal())
return;
break;
case DiscardingChangesOperation.Cancel:
return;
}
}
// continue with New operation
}
protected virtual void Delete()
{
var args = new CancelEventArgs();
DeletingRecord?.Invoke(this, args);
if (args.Cancel)
return;
// continue delete operation
}
查看:
<UserControl.DataContext>
<vm:CompanyViewModel DeletingRecord="CompanyViewModel_DeletingRecord" DiscardingChanges="CompanyViewModel_DiscardingChanges"></vm:CompanyViewModel>
</UserControl.DataContext>
查看隐藏代码:
private void CompanyViewModel_DeletingRecord(object sender, System.ComponentModel.CancelEventArgs e)
{
App.HandleRecordDeleting(sender, e);
}
private void CompanyViewModel_DiscardingChanges(object sender, DiscardingChangesEventArgs e)
{
App.HandleDiscardingChanges(sender, e);
}
以及属于每个视图可以使用的App类的一部分的静态方法:
public static void HandleDiscardingChanges(object sender, DiscardingChangesEventArgs e)
{
switch (MessageBox.Show("Save changes?", "Save", MessageBoxButton.YesNoCancel))
{
case MessageBoxResult.Yes:
e.Operation = DiscardingChangesOperation.Save;
break;
case MessageBoxResult.No:
e.Operation = DiscardingChangesOperation.Discard;
break;
case MessageBoxResult.Cancel:
e.Operation = DiscardingChangesOperation.Cancel;
break;
default:
throw new InvalidEnumArgumentException("Invalid MessageBoxResult returned from MessageBox.Show");
}
}
public static void HandleRecordDeleting(object sender, CancelEventArgs e)
{
e.Cancel = MessageBox.Show("Delete current record?", "Delete", MessageBoxButton.YesNo) == MessageBoxResult.No;
}
通过这些静态方法集中对话框,我们以后可以轻松地将其换出以用于自定义对话框。