MVVM中如何成功实现MessageBox.Show()功能?

48
我有一个WPF应用程序,ViewModel中调用了MessageBox.Show()(以检查用户是否真的想要删除),这实际上是可行的,但违反了MVVM的规范,因为ViewModel不应明确确定视图上发生的事情。
因此,我现在在考虑如何最好地在我的MVVM应用程序中实现MessageBox.Show()功能,以下是选项:
1. 我可以在XAML中创建一个包含文本“Are you sure...?”、两个按钮Yes和No的消息,并在模板上创建触发器,使其根据称为AreYourSureDialogueBoxIsVisible的ViewModelProperty折叠/显示。当我需要这个对话框时,将AreYourSureDialogueBoxIsVisible分配给"true",并在ViewModel中通过DelegateCommand处理两个按钮。
2. 我也可以尝试使用XAML中的触发器来处理此问题,以便Delete按钮实际上只是使某个Border元素出现其中包含消息和按钮,而Yes按钮执行实际删除操作。
这两个解决方案似乎都太复杂了,对于以前仅需几行代码的MessageBox.Show()。
你是如何成功在你的MVVM应用程序中实现对话框框的?

相似问题:https://dev59.com/P0XRa4cB1Zd3GeqPvNpB - Tiberiu Ana
13个回答

13

服务来拯救。使用 Onyx(免责声明,我是作者),实现起来就像这样简单:

public void Foo()
{
    IDisplayMessage dm = this.View.GetService<IDisplayMessage>();
    dm.Show("Hello, world!");
}

在运行应用程序时,这将间接调用MessageBox.Show("Hello, world!")。在测试时,可以模拟IDisplayMessage服务并提供给ViewModel,在测试期间执行想要完成的任何操作。


我在想间接访问View是否仍被视为遵循MVVM实践。 - VCD
2
大多数人认为这不是问题,但我并不同意。这里没有紧密耦合,所以我要求有人展示一下为什么这里存在设计问题。上述例子中的反模式是使用服务定位器而不是依赖注入。无论如何,IDisplayMessage概念才是真正的答案,它既不依赖于服务定位器也不间接访问视图。 - wekempf

5
扩展Dean Chalk的答案,现在他的链接已经失效:
在App.xaml.cs文件中,我们将确认对话框与视图模型连接起来。
protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);
    var confirm = (Func<string, string, bool>)((msg, capt) => MessageBox.Show(msg, capt, MessageBoxButton.YesNo) == MessageBoxResult.Yes);
    var window = new MainWindowView();
    var viewModel = new MainWindowViewModel(confirm);
    window.DataContext = viewModel;
    ...
}

在视图(MainWindowView.xaml)中,我们有一个按钮,调用 ViewModel 中的一个命令。
<Button Command="{Binding Path=DeleteCommand}" />

视图模型(MainWindowViewModel.cs)使用委托命令显示“确定吗?”对话框并执行操作。在此示例中,它是一个类似于SimpleCommand,但任何ICommand的实现都应该可以。
private readonly Func<string, string, bool> _confirm;

//constructor
public MainWindowViewModel(Func<string, string, bool> confirm)
{
    _confirm = confirm;
    ...
}

#region Delete Command
private SimpleCommand _deleteCommand;
public ICommand DeleteCommand
{
    get { return _deleteCommand ?? (_deleteCommand = new SimpleCommand(ExecuteDeleteCommand, CanExecuteDeleteCommand)); }
}

public bool CanExecuteDeleteCommand()
{
    //put your logic here whether to allow deletes
    return true;
}

public void ExecuteDeleteCommand()
{
    bool doDelete =_confirm("Are you sure?", "Confirm Delete");
    if (doDelete)
    {
        //delete from database
        ...
    }
}
#endregion

5
你提到的两种方法中,我更喜欢第二种。页面上的“删除”按钮只会弹出“确认删除对话框”,而实际上是由“确认删除对话框”来执行删除操作。
你有没有看过Karl Shifflett的WPF Line Of Business Slides and Demos?我知道他做了类似的东西。我会想起来在哪里可以找到。
编辑:查看演示 #11 "Data Validation in MVVM" (EditContactItemsControlSelectionViewModel.DeleteCommand)。Karl从ViewModal调用一个弹出窗口(什么!?:-))。我其实更喜欢你的想法。看起来更容易进行单元测试。

我最终使用方案#1解决了这个问题。它不会弹出窗口,而是通过从Visibility=collapsed触发到Visibility=Visible,将"边框元素"插入屏幕顶部。我创建了DelegateCommands:TurnOnDialogueBoxDelete、DeleteItem和CancelDialogueBoxDelete。我创建了ViewModelProperties:DialogueBoxDeleteStatus、ItemIdMarkedForDeletion和DialogueBoxDeleteText。所以它并不像简单的MessageBox.Show()那样简单,但运行得很好。我相信,随着其他对话框的需要,这些功能在未来可以被抽象化。 - Edward Tanguay
如果 (System.Windows.MessageBox.Show("您确定要删除吗?", "", System.Windows.MessageBoxButton.YesNo) == System.Windows.MessageBoxResult.Yes) { // 执行操作 } - Rakesh Ravi G

5

我只需创建一个接口(例如IMessageDisplay),将其注入到VM中,它具有像MessageBox(ShowMessage()等)一样的方法。您可以使用标准messagebox或更特定于WPF的东西来实现它(我使用Prajeesh在CodePlex上的这个)。这样一来,所有内容都是分离和可测试的。


3
< p > 为了确保任何其他人都能继续阅读并且感到满意:

我希望处理“通知”类型的消息框(即,我不关心DialogResult),但我对大多数解决方案的问题是它们似乎间接地强制你选择您的View实现(也就是说,我当前有一个MessageBox.Show,但如果我以后决定在我的View中直接操作隐藏面板的可见性,那么这将无法与传递给ViewModel的INotification接口非常匹配)。

所以我采用了快速而简单的方法:

ViewModel有一个string NotificationMessage属性,更改通知到PropertyChanged

View订阅PropertyChanged,如果看到NotificationMessage属性出现,就做它想要做的事情。

好吧,这意味着View具有代码behind,并且PropertyChanged的名称被硬编码,但无论如何,在XAML中都会被硬编码。这意味着我避免了所有与可见性转换器和指示通知是否仍然可见的属性等相关的麻烦事。

(诚然,这仅适用于有限的用例(fire and forget),我没有考虑如何扩展它。)


3

那么,是否可以在视图的代码后台处理像"MessageBoxRequested"这样的事件呢?(反正这只是视图的代码,所以我认为在代码后台处理这个事件没有任何问题。)


真的,MVVM 对我来说是连接模型和视图。但是视图也应该自己表现出行为。 - jeuxjeux20

2

这个话题有很多答案,从创建自定义类到使用第三方库都有。如果你想要带有漂亮视觉效果的酷炫弹出窗口,我建议使用第三方库。

但是,如果你只想在WPF应用程序中使用微软的常规消息框,这里是一个MVVM/单元测试友好的实现:

最初,我想从消息框中继承并使用接口进行包装,但由于消息框没有公共构造函数,所以我无法这样做,因此这里提供了一个“简单”的解决方案:

在Visual Studio中反编译消息框,你可以看到所有的方法重载,我检查了我想要的方法,然后创建了一个新类并添加了这些方法,并使用接口进行包装,完成!现在你可以使用Ninject来绑定接口和类,注入它并使用Moq进行单元测试等。

创建一个接口(只添加了一些重载方法,因为我不需要它们全部):

public interface IMessageBox
    {
        /// <summary>Displays a message box that has a message, title bar caption, and button; and that returns a result.</summary>          
        MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button);

        /// <summary>Displays a message box that has a message, title bar caption, button, and icon; and that returns a result.</summary>           
        MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon);

        /// <summary>Displays a message box that has a message and title bar caption; and that returns a result.</summary>            
        MessageBoxResult Show(string messageBoxText, string caption);
    }

然后我们有一个将继承它的类:
public class MessageBoxHelper : IMessageBox
    {
        /// <summary>Displays a message box that has a message, title bar caption, button, and icon; and that returns a result.</summary>            
        public MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button,
            MessageBoxImage icon)
        {
            return MessageBox.Show(messageBoxText, caption, button, icon, MessageBoxResult.None,
                MessageBoxOptions.None);
        }

        /// <summary>Displays a message box that has a message, title bar caption, and button; and that returns a result.</summary>            
        public MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button)
        {
            return MessageBox.Show(messageBoxText, caption, button, MessageBoxImage.None, MessageBoxResult.None,
                MessageBoxOptions.None);
        }

        /// <summary>Displays a message box that has a message and title bar caption; and that returns a result.</summary>            
        public MessageBoxResult Show(string messageBoxText, string caption)
        {
            return MessageBox.Show(messageBoxText, caption, MessageBoxButton.OK, MessageBoxImage.None,
                MessageBoxResult.None, MessageBoxOptions.None);
        }

        /// <summary>Displays a message box that has a message and that returns a result.</summary>           
        public MessageBoxResult Show(string messageBoxText)
        {
            return MessageBox.Show(messageBoxText, string.Empty, MessageBoxButton.OK, MessageBoxImage.None,
                MessageBoxResult.None, MessageBoxOptions.None);
        }
    }

现在只需在注入等操作时使用此方法,然后您就拥有了一个脆弱的抽象,可以完成任务...这取决于您将在何处使用它。我的情况是一个简单的应用程序,只需要做一些事情,因此没有必要过度工程化解决方案。希望这能帮助到某些人。


1

我为我们制作了一个简单的MessageBox包装控件,以便在纯MVVM解决方案中使用,并仍然允许进行单元测试。详细信息请参阅我的博客链接

mukapu


链接中的解决方案已经失效了,你能修复一下吗? - Abin

1

0

最近我遇到了一个问题,需要将ViewModels中的MessageBox.Show替换为一些完全符合MVVM标准的消息框机制。

为了实现这一点,我使用了InteractionRequest<Notification>InteractionRequest<Confirmation>以及交互触发器,并编写了自己的消息框视图。

我实现的内容已经发布在这里


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接