.net 使用 MVVM 处理 WPF 中的对话框

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/454868/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 11:01:23  来源:igfitidea点击:

Handling Dialogs in WPF with MVVM

.netwpfdesign-patternsmvvmdialog

提问by Ray Booysen

In the MVVM pattern for WPF, handling dialogs is one of the more complex operations. As your view model does not know anything about the view, dialog communication can be interesting. I can expose an ICommandthat when the view invokes it, a dialog can appear.

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

Does anyone know of a good way to handle results from dialogs? I am speaking about windows dialogs such as MessageBox.

有谁知道处理对话框结果的好方法?我说的是 Windows 对话框,例如MessageBox.

One of the ways we did this was have an event on the viewmodel that the view would subscribe to when a dialog was required.

我们这样做的方法之一是在视图模型上有一个事件,当需要对话框时,视图将订阅该事件。

public event EventHandler<MyDeleteArgs> RequiresDeleteDialog;

This is OK, but it means that the view requires code which is something I would like to stay away from.

这没关系,但这意味着视图需要代码,这是我想远离的东西。

采纳答案by Jeffrey Knight

I suggest forgoing the 1990's modal dialogs and instead implementing a control as an overlay (canvas+absolute positioning) with visibility tied to a boolean back in the VM. Closer to an ajax type control.

我建议放弃 1990 年代的模态对话框,而是将控件实现为叠加(画布+绝对定位),其可见性与 VM 中的布尔值绑定。更接近于 ajax 类型的控件。

This is very useful:

这非常有用:

<BooleanToVisibilityConverter x:Key="booltoVis" />

as in:

如:

<my:ErrorControl Visibility="{Binding Path=ThereWasAnError, Mode=TwoWay, Converter={StaticResource booltoVis}, UpdateSourceTrigger=PropertyChanged}"/>

Here's how I have one implemented as a user control. Clicking on the 'x' closes the control in a line of code in the usercontrol's code behind. (Since I have my Views in an .exe and ViewModels in a dll, I don't feel bad about code that manipulates UI.)

下面是我如何实现一个用户控件。单击“x”将在用户控件代码后面的一行代码中关闭控件。(因为我的视图在 .exe 中,而视图模型在 dll 中,我对操作 UI 的代码并不感到难过。)

Wpf dialog

WPF 对话框

回答by Roubachof

You should use a mediator for this. Mediator is a common design pattern also known as Messengerin some of its implementations. It's a paradigm of type Register/Notify and enables your ViewModel and Views to communicate through a low-coupled messaging mecanism.

您应该为此使用调解器。Mediator 是一种常见的设计模式,在其某些实现中也称为Messenger。它是 Register/Notify 类型的范例,使您的 ViewModel 和 Views 能够通过低耦合消息传递机制进行通信。

You should check out the google WPF Disciples group, and just search for Mediator. You will be much happy with the answers...

您应该查看 google WPF Disciples 组,然后搜索 Mediator。你会很高兴得到答案......

You can however start with this:

但是,您可以从以下开始:

http://joshsmithonwpf.wordpress.com/2009/04/06/a-mediator-prototype-for-wpf-apps/

http://joshsmithonwpf.wordpress.com/2009/04/06/a-mediator-prototype-for-wpf-apps/

Enjoy !

享受 !

Edit: you can see the answer to this problem with the MVVM Light Toolkit here:

编辑:您可以在此处使用 MVVM Light Toolkit 查看此问题的答案:

http://mvvmlight.codeplex.com/Thread/View.aspx?ThreadId=209338

http://mvvmlight.codeplex.com/Thread/View.aspx?ThreadId=209338

回答by user92541

A good MVVM dialog should:

一个好的 MVVM 对话框应该:

  1. Be declared with only XAML.
  2. Get all of it's behavior from databinding.
  1. 仅使用 XAML 声明。
  2. 从数据绑定中获取它的所有行为。

Unfortunately, WPF doesn't provide these features. Showing a dialog requires a code-behind call to ShowDialog(). The Window class, which supports dialogs, can't be declared in XAML so it can't easily be databound to the DataContext.

不幸的是,WPF 不提供这些功能。显示对话框需要对 进行代码隐藏调用ShowDialog()。支持对话框的 Window 类不能在 XAML 中声明,因此它不能轻松地数据绑定到DataContext.

To solve this, I wrote a XAML stub control that sits in the logical tree and relays databinding to a Windowand handles showing and hiding the dialog. You can find it here: http://www.codeproject.com/KB/WPF/XAMLDialog.aspx

为了解决这个问题,我编写了一个位于逻辑树中的 XAML 存根控件,并将数据绑定中继到 aWindow并处理显示和隐藏对话框。你可以在这里找到它:http: //www.codeproject.com/KB/WPF/XAMLDialog.aspx

It's really simply to use and doesn't require any strange changes to your ViewModel and doesn't require events or messages. The basic call looks like this:

它真的很容易使用,不需要对您的 ViewModel 进行任何奇怪的更改,也不需要事件或消息。基本调用如下所示:

<dialog:Dialog Content="{Binding Path=DialogViewModel}" Showing="True" />

You probably want to add a style that sets Showing. I explain it in my article. I hope this helps you.

您可能想要添加一种设置Showing. 我在我的文章中解释过。我希望这可以帮助你。

回答by blindmeis

I use thisapproach for dialogs with MVVM.

我使用这种方法与 MVVM 进行对话。

All I have to do now is call the following from my view model.

我现在要做的就是从我的视图模型中调用以下内容。

var result = this.uiDialogService.ShowDialog("Dialogwindow title goes here", dialogwindowVM);

回答by Roboblob

My current solution solves most of the issues you mentioned yet its completely abstracted from platform specific things and can be reused. Also i used no code-behind only binding with DelegateCommands that implement ICommand. Dialog is basically a View - a separate control that has its own ViewModel and it is shown from the ViewModel of the main screen but triggered from the UI via DelagateCommand binding.

我当前的解决方案解决了您提到的大部分问题,但它完全从特定于平台的事物中抽象出来并且可以重用。我也没有使用代码隐藏,只与实现 ICommand 的 DelegateCommands 绑定。Dialog 基本上是一个视图——一个单独的控件,它有自己的 ViewModel,它从主屏幕的 ViewModel 显示,但通过 DelagateCommand 绑定从 UI 触发。

See full Silverlight 4 solution here Modal dialogs with MVVM and Silverlight 4

在此处查看完整的 Silverlight 4 解决方案使用 MVVM 和 Silverlight 4 的模态对话框

回答by Mike Rowley

I really struggled with this concept for a while when learning (still learning) MVVM. What I decided, and what I think others already decided but which wasn't clear to me is this:

在学习(仍在学习)MVVM 时,我确实在这个概念上挣扎了一段时间。我决定的,以及我认为其他人已经决定但我不清楚的是:

My original thought was that a ViewModel should not be allowed to call a dialog box directly as it has no business deciding how a dialog should appear. Beacause of this I started thinking about how I could pass messages much like I would have in MVP (i.e. View.ShowSaveFileDialog()). However, I think this is the wrong approach.

我最初的想法是不应该允许 ViewModel 直接调用对话框,因为它没有决定对话框应该如何显示的业务。因此,我开始考虑如何像在 MVP 中一样传递消息(即 View.ShowSaveFileDialog())。但是,我认为这是错误的方法。

It is OK for a ViewModel to call a dialog directly. However, when you are testing a ViewModel , that means that the dialog will either pop up during your test, or fail all together (never really tried this).

ViewModel 可以直接调用对话框。但是,当您测试 ViewModel 时,这意味着该对话框将在您的测试期间弹出,或者一起失败(从未真正尝试过)。

So, what needs to happen is while testing is to use a "test" version of your dialog. This means that for ever dialog you have, you need to create an Interface and either mock out the dialog response or create a testing mock that will have a default behaviour.

因此,需要发生的是在测试时使用对话框的“测试”版本。这意味着对于您拥有的任何对话,您都需要创建一个接口并模拟对话响应或创建一个具有默认行为的测试模拟。

You should already be using some sort of Service Locator or IoC that you can configure to provide you the correct version depending on the context.

您应该已经在使用某种服务定位器或 IoC,您可以对其进行配置以根据上下文为您提供正确的版本。

Using this approach, your ViewModel is still testable and depending on how you mock out your dialogs, you can control the behaviour.

使用这种方法,您的 ViewModel 仍然是可测试的,并且根据您模拟对话框的方式,您可以控制行为。

Hope this helps.

希望这可以帮助。

回答by Chris Bordeman

There are two good ways to do this, 1) a dialog service (easy, clean), and 2) view assisted. View assisted provides some neat features, but is usually not worth it.

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

DIALOG SERVICE

对话服务

a) a dialog service interface like via constructor or some dependency container:

a) 一个对话服务接口,如通过构造函数或一些依赖容器:

interface IDialogService { Task ShowDialogAsync(DialogViewModel dlgVm); }

interface IDialogService { Task 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) 您的 IDialogService 实现应该打开一个窗口(或将一些控件注入活动窗口),创建一个与给定 dlgVm 类型的名称相对应的视图(使用容器注册或约定或具有关联 DataTemplates 类型的 ContentPresenter)。ShowDialogAsync 应该创建一个 TaskCompletionSource 并返回其 .Task 属性。DialogViewModel 类本身需要一个事件,当您想要关闭时,您可以在派生类中调用该事件,并在对话框视图中观察以实际关闭/隐藏对话框并完成 TaskCompletionSource。

b) To use, simply call await this.DialogService.ShowDialog(myDlgVm) on your instance of some DialogViewModel-derived class. After await returns, look at properties you've added on your dialog VM to determine what happened; you don't even need a callback.

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

VIEW ASSISTED

查看辅助

This has your view listening to an event on the viewmodel. This could all be wrapped up into a Blend Behavior to avoid code behind and resource usage if you're so inclined (FMI, subclass the "Behavior" class to see a sort of Blendable attached property on steroids). For now, we'll do this manually on each view:

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

a) Create an OpenXXXXXDialogEvent with a custom payload (a DialogViewModel derived class).

a) 使用自定义负载(DialogViewModel 派生类)创建 OpenXXXXXDialogEvent。

b) Have the view subscribe to the event in its OnDataContextChanged event. Be sure to hide and unsubscribe if the old value != null and in the Window's Unloaded event.

b) 让视图订阅其 OnDataContextChanged 事件中的事件。如果旧值 != null 并且在 Window 的 Unloaded 事件中,请确保隐藏和取消订阅。

c) When the event fires, have the view open your view, which might be in a resource on your page, or you could locate it by convention elsewhere (like in the the dialog service approach).

c) 当事件触发时,让视图打开您的视图,该视图可能位于您页面上的资源中,或者您可以按照约定将其定位到其他地方(如在对话服务方法中)。

This approach is more flexible, but requires more work to use. I don't use it much. The one nice advantage are the ability to place the view physically inside a tab, for example. I have used an algorithm to place it in the current user control's bounds, or if not big enough, traverse up the visual tree until a big enough container is found.

这种方法更灵活,但需要更多的工作才能使用。我用得不多。例如,一个不错的优势是能够将视图物理放置在选项卡内。我使用了一种算法将其放置在当前用户控件的边界中,或者如果不够大,则向上遍历可视化树,直到找到足够大的容器。

This allows dialogs to be close to the place they're actually used, only dim the part of the app related to the current activity, and let the user move around within the app without having to manually push dialogs away, even have multiple quasi-modal dialogs open on different tabs or sub-views.

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

回答by Maxm007

Use a freezable command

使用可冻结命令

<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();
    }
}

回答by jbe

An interesting alternative is to use Controllers which are responsible to show the views (dialogs).

一个有趣的替代方法是使用负责显示视图(对话框)的控制器。

How this works is shown by the WPF Application Framework (WAF).

WPF 应用程序框架 (WAF)展示了其工作原理。

回答by Cameron MacFarland

I think that the handling of a dialog should be the responsibility of the view, and the view needs to have code to support that.

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

If you change the ViewModel - View interaction to handle dialogs then the ViewModel is dependant on that implementation. The simplest way to deal with this problem is to make the View responsible for performing the task. If that means showing a dialog then fine, but could also be a status message in the status bar etc.

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

My point is that the whole point of the MVVM pattern is to separate business logic from the GUI, so you shouldn't be mixing GUI logic (to display a dialog) in the business layer (the ViewModel).

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