在WPF的MVVM模式中,处理对话框是比较复杂的操作之一。由于视图模型不知道视图的任何信息,因此对话框通信可能会很有趣。我可以公开一个ICommand,当视图调用它时,就会出现一个对话框。

有人知道处理对话框结果的好方法吗?我说的是windows对话框,比如MessageBox。

其中一种方法是在视图模型上设置一个事件,当需要对话框时,视图会订阅该事件。

public event EventHandler<MyDeleteArgs> RequiresDeleteDialog;

这是可以的,但这意味着视图需要代码,这是我想要避免的。


当前回答

我遇到了同样的情况,并将MessageBox包装成一个设计器不可见控件。详情在我的博客里

http://geekswithblogs.net/mukapu/archive/2010/03/12/user-prompts-messagebox-with-mvvm.aspx

同样可以扩展到任何模态对话框,文件浏览控件等。

其他回答

使用可冻结命令

<Grid>
        <Grid.DataContext>
            <WpfApplication1:ViewModel />
        </Grid.DataContext>


        <Button Content="Text">
            <Button.Command>
                <WpfApplication1:MessageBoxCommand YesCommand="{Binding MyViewModelCommand}" />
            </Button.Command>
        </Button>

</Grid>
public class MessageBoxCommand : Freezable, ICommand
{
    public static readonly DependencyProperty YesCommandProperty = DependencyProperty.Register(
        "YesCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty OKCommandProperty = DependencyProperty.Register(
        "OKCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty CancelCommandProperty = DependencyProperty.Register(
        "CancelCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty NoCommandProperty = DependencyProperty.Register(
        "NoCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(
        "Message",
        typeof (string),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata("")
        );

    public static readonly DependencyProperty MessageBoxButtonsProperty = DependencyProperty.Register(
        "MessageBoxButtons",
        typeof(MessageBoxButton),
        typeof(MessageBoxCommand),
        new FrameworkPropertyMetadata(MessageBoxButton.OKCancel)
        );

    public ICommand YesCommand
    {
        get { return (ICommand) GetValue(YesCommandProperty); }
        set { SetValue(YesCommandProperty, value); }
    }

    public ICommand OKCommand
    {
        get { return (ICommand) GetValue(OKCommandProperty); }
        set { SetValue(OKCommandProperty, value); }
    }

    public ICommand CancelCommand
    {
        get { return (ICommand) GetValue(CancelCommandProperty); }
        set { SetValue(CancelCommandProperty, value); }
    }

    public ICommand NoCommand
    {
        get { return (ICommand) GetValue(NoCommandProperty); }
        set { SetValue(NoCommandProperty, value); }
    }

    public MessageBoxButton MessageBoxButtons
    {
        get { return (MessageBoxButton)GetValue(MessageBoxButtonsProperty); }
        set { SetValue(MessageBoxButtonsProperty, value); }
    }

    public string Message
    {
        get { return (string) GetValue(MessageProperty); }
        set { SetValue(MessageProperty, value); }
    }

    public void Execute(object parameter)
    {
        var messageBoxResult = MessageBox.Show(Message);
        switch (messageBoxResult)
        {
            case MessageBoxResult.OK:
                OKCommand.Execute(null);
                break;
            case MessageBoxResult.Yes:
                YesCommand.Execute(null);
                break;
            case MessageBoxResult.No:
                NoCommand.Execute(null);
                break;
            case MessageBoxResult.Cancel:
                if (CancelCommand != null) CancelCommand.Execute(null); //Cancel usually means do nothing ,so can be null
                break;

        }
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;


    protected override Freezable CreateInstanceCore()
    {
        throw new NotImplementedException();
    }
}

我认为处理对话框应该是视图的责任,视图需要有代码来支持这一点。

如果你改变ViewModel - View交互来处理对话框,那么ViewModel依赖于该实现。处理这个问题最简单的方法是让视图负责执行任务。如果这意味着显示一个对话框,那么很好,但也可以是状态栏中的状态消息等。

我的观点是,MVVM模式的全部观点是将业务逻辑从GUI中分离出来,因此您不应该在业务层(ViewModel)中混合GUI逻辑(以显示对话框)。

关于这个主题,我已经写了一篇相当全面的文章,还为MVVM对话框开发了一个弹出式库。严格遵守MVVM不仅是可能的,而且在正确实现时非常干净,而且它可以很容易地扩展到自己不遵守MVVM的第三方库:

https://www.codeproject.com/Articles/820324/Implementing-Dialog-Boxes-in-MVVM

我已经实现了一个从ViewModel监听消息的行为。它基于Laurent Bugnion的解决方案,但由于它不使用代码,而且更可重用,所以我认为它更优雅。

如何使WPF的行为,如果MVVM是支持开箱即用

有两种好方法可以做到这一点,1)对话框服务(简单,干净),2)视图辅助。视图辅助提供了一些简洁的特性,但通常不值得这样做。

对话框的服务

A)一个对话服务接口,比如via构造函数或一些依赖容器:

接口IDialogService { 任务ShowDialogAsync(DialogViewModel dlgVm); }

b) Your implementation of IDialogService should open a window (or inject some control into the active window), create a view corresponding to the name of the given dlgVm type (use container registration or convention or a ContentPresenter with type associated DataTemplates). ShowDialogAsync should create a TaskCompletionSource and return its .Task proptery. The DialogViewModel class itself needs an event you can invoke in the derived class when you want to close, and watch in the dialog view to actually close/hide the dialog and complete the TaskCompletionSource.

b)要使用,只需在某个dialogviewmodel派生类的实例上调用await this.DialogService.ShowDialog(myDlgVm)。等待返回后,查看你在对话框VM中添加的属性,以确定发生了什么;你甚至不需要回调。

查看帮助

这让你的视图监听视图模型上的事件。如果您愿意的话,可以将这些都打包到一个Blend Behavior中,以避免背后的代码和资源使用(FMI,子类化“Behavior”类,以查看类固醇上的一种可混合的附加属性)。现在,我们将手动在每个视图上执行此操作:

a)创建一个带有自定义有效负载(DialogViewModel派生类)的OpenXXXXXDialogEvent。

b)让视图在OnDataContextChanged事件中订阅该事件。如果旧值!= null并且在窗口的Unloaded事件中,请务必隐藏并取消订阅。

c)当事件触发时,让视图打开你的视图,它可能在你页面的资源中,或者你可以通过约定在其他地方定位它(比如在对话框服务方法中)。

这种方法更灵活,但需要做更多的工作才能使用。我不怎么用它。例如,一个很好的优点是能够将视图物理地放置在一个选项卡中。我已经使用一种算法将其放置在当前用户控件的边界中,或者如果不够大,则遍历可视树,直到找到足够大的容器。

这允许对话框靠近它们实际使用的地方,只使应用程序中与当前活动相关的部分变暗,并让用户在应用程序中移动而不必手动将对话框推开,甚至在不同的选项卡或子视图上打开多个准模态对话框。