在WPF MVVM中打开对话框

16

我有一个需要在用户输入信息后从按钮中打开对话框的应用程序。

目前,我是这样做的(它可以正常工作):

  • 按钮单击会在ViewModel中生成一个命令。
  • ViewModel引发一个事件,该事件由Controller监听。
  • Controller确定新窗口的详细信息(即View、ViewModel和Model),并打开它(ShowDialog)。
  • 当窗口关闭时,Controller将结果添加到eventargs并返回给ViewModel。
  • ViewModel将信息传递给Model。

这里有很多步骤,但它们都很合理,而且没有太多的输入。

代码看起来像这样(窗口要求用户输入姓名):

ViewModel:

AskUserNameCommand = DelegateCommand(AskUserNameExecute);
...

public event EventHandler<AskUserEventArgs> AskUserName;

void AskUserNameExecute(object arg) {
    var e = new AskUserNameEventArgs();
    AskUserName(this, e);
    mModel.SetUserName(e.UserName);
}

控制器:

mViewModel.AskUserName += (sender,e) => {
    var view = container.Resolve<IAskUserNameView>();
    var model = container.Resolve<IAskUserNameModel>();
    var viewmodel = container.Resolve<IAskUserNameViewModel>(view, model);
    if (dlg.ShowDialog() ?? false)
        e.UserName = model.UserName;
}
我的问题是MVVM模式中水平通信如何工作。让控制器介入模型之间的数据传输似乎有些不对。我已经研究了中介者模式,以便让模型直接通信。但我不喜欢这个想法,因为它使模型依赖于GUI的实现细节。(即如果对话框用文本框替换,则需要更改模型)。

1
你看过以下问题了吗? https://dev59.com/PnRB5IYBdhLWcg3w9L3n https://dev59.com/13I-5IYBdhLWcg3wy72n https://dev59.com/yUrSa4cB1Zd3GeqPW30G - Guge
是的,我看过它们,但它们都建议使用广播模式来解决实例之间的问题。 - adrianm
“按钮点击在ViewModel中生成一个命令。”这是什么意思?“ViewModel引发一个事件,控制器监听该事件。”哪个控制器? - hyankov
5个回答

15

我并不太喜欢当前的大部分建议,因为各种原因,所以我想链接到一个几乎完全相同的带有答案的问题:

MVVM下的打开文件对话框

具体来说,Cameron MacFarland的回答就是我想要的。提供一个通过界面提供IO和/或用户交互的服务是解决此问题的方法,以下是原因:

  • 它可测试
  • 它抽象掉了任何对话框的实现方式,因此处理这些类型的事情的策略可以更改而不影响组成代码。
  • 不依赖于任何通信模式。你在其他地方看到的许多建议都依赖于中介者,比如事件聚合器。 这些解决方案依赖于与中介者另一侧的伙伴实现双向通信,这既难以实现又是一份非常松散的契约。
  • ViewModel保持自治。我和你一样,觉得控制器和ViewModel之间的通信不太好。如果没有其他原因,ViewModel应保持自治,这样可以更容易地进行测试。

希望这可以帮助你。


谢谢,这让我思考了一下。我不想将 ioc-container 注入到 viewmodel 中,所以我可能会创建某种 IControllerService,供 viewmodel 调用。这比我现在使用的基于事件的通信要干净得多。 - adrianm
1
如果您正在使用IoC,我建议将像IDialogService这样的内容声明为依赖项。我通常认为IoC是所有组成对象都可以使用的服务目录。 - Anderson Imes

2

我使用这种基于MVVM的方法来处理对话框。

现在,我只需要从我的ViewModel中调用以下代码来处理对话框。

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

0

我遇到了类似的问题。这是我如何解决它们以及为什么我所做的事情。

我的解决方案:

我的MainWindowViewModel有一个名为Modal的ModalViewModelBase类型属性。如果我的代码需要某个视图成为模态视图,它会将对其的引用放入此属性中。MainWindowView通过INotifyPropertyChanged机制监视此属性。如果Modal设置为某个VM,则MainWindowView类将获取VM并将其放入ModalView窗口中,适当的UserControl将通过DataTemplates显示,该窗口使用ShowDialog显示。ModalViewModelBase具有DialogResult属性和IsFinished属性。当模态VM将IsFinished设置为true时,视图将关闭。

我还有一些特殊技巧,可以从希望向用户请求输入的backgroundworker线程执行交互式操作。

我的推理:

模态视图的原则是在显示模态视图时禁用其他视图。这是View本质上是无样式的逻辑的一部分。这就是为什么我在MainWindowViewModel中有一个属性的原因。如果我要更进一步,我应该使Main VM中所有其他VM的每个其他属性或命令在模态模式下抛出异常,但我觉得这太过于过度了。

实际上拒绝用户进行其他操作的视图机制,不一定要使用弹出窗口和showdialog来执行,也可以将模态视图放置在现有窗口中,但禁用所有其他操作,或者采取其他策略。这种与视图相关的逻辑应该放在视图本身中。(一个典型的设计师无法编写此逻辑,似乎是次要问题。我们有时都需要帮助。)

所以我是这样做的。我只提供建议,可能还有其他思考方式,我希望您也能得到更多回复。


这是一个有趣的方式,但你没有说数据传输是如何工作的。当对话框关闭时,用户输入是如何从Dialog(View)Model传递到Main(View)Model的? - adrianm
MainViewModel在对话框视图关闭后仍然引用DialogViewModel。 - Guge
好的,那大致上和我的做法一样,但你是在ViewModel中实现的。 - adrianm

0

我在类似的场景中使用了 Prism v2 的 EventAggregator。Prism 的好处是,在你的 MVVM 应用程序中,你不必使用整个框架。你可以提取 EventAggregator 功能,并将其与当前设置一起使用。


EventAggregator很好,但它是一个广播服务。我找不到一种简单的方法来配置它以调用特定实例的侦听器。假设用户打开对话框,关闭它,然后再次打开它。现在有两个视图/视图模型/模型实例监听事件,直到GC开始清理。 如果侦听器取消订阅事件,则可以解决此问题,但这会导致ViewModel / Model中的某种IDisposable模式。 - adrianm
@adrianm 你说得对。如果你希望将消息传递到特定的实例,那么事件聚合器是不合适的。 - Anderson Imes

-1

控制器?在MVVM中? - Alexey Khrenov

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