使用MVVM在WPF中处理对话框


235

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

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

我们执行此操作的方法之一是在视图模型上有一个事件,当需要对话框时,视图将订阅该事件。

public event EventHandler<MyDeleteArgs> RequiresDeleteDialog;

可以,但是这意味着视图需要代码,而我想远离这些代码。


为什么不绑定到视图中的帮助对象?
Paul Williams

1
不明白你的意思。
雷·布伊森

1
如果我理解这个问题,则不希望VM弹出对话框,也不希望在View中隐藏代码。此外,听起来您更喜欢命令而不是事件。我同意所有这些观点,因此我在View中使用了一个帮助器类,该类公开了处理对话框的命令。我在这里的另一个线程上回答了这个问题:stackoverflow.com/a/23303267/420400。但是,最后一句话听起来好像您在View中的任何地方根本都不需要任何代码。我了解这种担心,但是所讨论的代码只是有条件的,并且不可能更改。
Paul Williams

4
视图模型应该始终负责创建对话框的逻辑,这就是其存在的全部原因。也就是说,它不会(也不应该)费力地创建视图本身。我在codeproject.com/Articles/820324/…上写了一篇关于该主题的文章,其中显示了对话框的整个生命周期可以通过常规WPF数据绑定来管理,而又不会破坏MVVM模式。
Mark Feldman

Answers:


131

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

这非常有用:

<BooleanToVisibilityConverter x:Key="booltoVis" />

如:

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

这是将我实现为用户控件的方式。单击“ x”将关闭用户控件代码后面的一行代码中的控件。(由于我的Views在.exe中,而ViewModels在dll中,因此我对操纵UI的代码感到很满意。)

WPF对话框


20
是的,我也很喜欢这个想法,但是想看看该控件的一些示例,如如何显示它,以及从中检索对话框结果等。尤其是在Silverlight中的MVVM场景中。
Roboblob

16
您如何防止用户与该对话框覆盖下的控件进行交互?
Andrew Garrison '02

16
这种方法的问题在于,您无法从第一个对话框中打开第二个模态对话框,至少在没有对覆盖系统进行一些重大修改的情况下……
Thomas Levesque

6
这种方法的另一个问题是“对话框”无法移动。在我们的应用程序中,我们必须有一个可移动的对话框,以便用户可以看到其背后的内容。
2015年

12
这种方法对我来说似乎很糟糕。我想念什么?这比真正的对话框好吗?
乔纳森·伍德

51

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

您应该检出google WPF徒弟组,然后搜索Mediator。您将对答案感到非常满意...

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

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

请享用 !

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

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


2
马龙·格雷奇(Marlon grech)刚刚发布了中介的全新实现方式: marlongrech.wordpress.com/2009/04/16/…–
Roubachof

21
请注意:WPF信徒没有引入Mediator模式,这是一种经典的GoF模式...(dofactory.com/Patterns/PatternMediator.aspx)。否则,答案不错;)
Thomas Levesque

10
上帝保佑,不要使用调解员或织补的使者。除非您能以某种方式记住整个代码库中订阅和处理每个事件的所有点,否则这种带有数十条消息的代码很难调试。对于新开发者来说,这是一场噩梦。实际上,我认为整个MvvMLight库是一种大规模的反模式,因为它普遍且不必要地使用了异步消息传递。解决方案很简单:调用设计的单独对话框服务(即IDialogService)。该接口具有用于回调的方法和事件。
克里斯·博德曼

34

良好的MVVM对话框应:

  1. 仅使用XAML声明。
  2. 从数据绑定中获取所有行为。

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

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

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

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

您可能想要添加设置的样式Showing。我在文章中对此进行了解释。我希望这可以帮助你。


2
对于在MVVM中显示对话框窗口的问题,这是一种非常有趣的方法。
dthrasher 2010年

2
"Showing a dialog requires a code-behind"嗯,您可以在ViewModel中调用它
Brock Hensley 2013年

我要添加第3点-您可以自由绑定到视图内的其他对象。将对话框的代码留空意味着在视图中的任何地方都没有C#代码,并且数据绑定并不意味着绑定到VM。
Paul Williams

25

我将这种方法用于MVVM对话框。

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

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

uiDialogService来自哪个库?
aggietech

1
没有图书馆。只是一个小接口和实现:stackoverflow.com/questions/3801681/…。公平地说,它满足了我的需求:)(高度,宽度,属性设置等)
blindmeis

16

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

在此处查看完整的Silverlight 4解决方案,带有MVVM和Silverlight 4的模式对话框


就像@Elad Katz的方法一样,您的答案缺少链接的内容-请插入它来改善您的答案,因为这在SO上被认为是不错的答案。尽管如此,感谢您的贡献!:)
Yoda

6

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

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

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

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

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

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

希望这可以帮助。


6

有两种很好的方法来执行此操作,1)对话框服务(简单,干净),以及2)视图辅助。辅助视图提供了一些简洁的功能,但通常不值得。

对话服务

a)对话框服务接口,例如通过构造函数或某些依赖项容器:

interface IDialogService { Task ShowDialogAsync(DialogViewModel dlgVm); }

b)IDialogService的实现应打开一个窗口(或将一些控件注入活动窗口),创建一个与给定dlgVm类型名称相对应的视图(使用容器注册或约定,或者使用与数据模板类型关联的ContentPresenter)。ShowDialogAsync应该创建一个TaskCompletionSource并返回其.Task属性。DialogViewModel类本身需要一个事件,您可以在想要关闭它时在派生类中调用该事件,并在对话框视图中观看以实际关闭/隐藏对话框并完成TaskCompletionSource。

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

查看协助

这使您的视图侦听viewmodel上的事件。如果您愿意的话,可以将它们全部合并为Blend Behavior,以避免代码落后和资源占用(FMI,子类“ Behavior”类,以查看类固醇上的Blended Attached属性)。现在,我们将在每个视图上手动执行此操作:

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

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

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

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

这样一来,对话框就可以靠近实际使用的位置,仅使应用程序与当前活动相关的部分变暗,并且使用户可以在应用程序内四处移动,而不必手动将对话框推开,甚至有多个准操作。模式对话框在不同的选项卡或子视图上打开。


当然,对话框服务要容易得多,而且与我通常所做的一样。这也使从父视图模型关闭视图对话框变得容易,这在父视图模型正在关闭或取消时是必需的。
克里斯·博德曼

4

使用可冻结命令

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

这段代码需要一些工作,但这是目前为止最好的主意,尤其是对于文件或打印机对话框之类的系统对话框。对话框属于视图(如果有的话)。对于文件对话框,可以将结果(选定的文件名)作为参数传递给内部命令。
安东·泰克

3

我认为,对话框的处理应由视图负责,并且视图需要具有支持该视图的代码。

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

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


2
VM永远不会处理对话框,在我的示例中,它会简单地发生一个事件,该事件要求对话框启动并以某种EventArgs形式传递信息。如果该视图负责,它将如何将信息传递回VM?
2009年

假设VM需要删除某些内容。VM在View Delete上调用一个方法,该方法返回一个布尔值。然后,视图可以直接删除该项并返回true,或者显示确认对话框并根据用户答案返回true / false。
卡梅隆·麦克法兰

VM对对话框一无所知,只要求视图删除某些内容,该视图被确认或拒绝。
卡梅隆·麦克法兰

我一直认为MVVM的重点是Model:业务逻辑,ViewModel:GUI逻辑和View:无逻辑。这与您的最后一段矛盾。请解释!
David Schmitt,2009年

2
首先,必须确定请求预删除确认是业务逻辑还是视图逻辑。如果是业务逻辑,则模型中的DeleteFile方法一定不能这样做,而是返回确认问题对象。这将包括对进行实际删除的委托的引用。如果不是业务逻辑,则VM必须在DeleteFileCommand中使用两个ICommand成员来构建问题的VM。一个代表是,一个代表不。两种视图都可能有参数,而在RL中,大多数使用可能会遇到两种。
古格2009年



3

我已经实现了一种行为,该行为可以侦听ViewModel中的消息。它基于Laurent Bugnion解决方案,但是由于它不使用背后的代码并且更可重用,所以我认为它更优雅。

如何使WPF表现得像开箱即用支持MVVM


1
您应该在此处包括完整的代码,因为这就是SO良好答案的要求。尽管如此,链接的方法还是很简洁的,为此,谢谢!:)
Yoda

2
@yoda的完整代码很长,这就是为什么我宁愿链接到它。我已编辑答案,以反映更改并指向未断开的链接
Elad Katz 2016年

感谢您的改进。尽管如此,最好在SO上长时间提供代码3全页滚动,而不是有一天可能会脱机的链接。关于复杂主题的好文章总是很长-我看不到打开新标签页,切换到该标签页并在之前滚动过的同一页上滚动的任何好处。;)
Yoda

@EladKatz我已经看到您在提供的链接中共享了一些WPF实现。您是否有解决方案,可以通过ViewModel打开新窗口?基本上我有两种形式,每种都有一种ViewModel。一个用户单击一个按钮,另一个窗体弹出,viewmodel1将其对象发送到viewmodel2。在窗体2中,用户可以更改对象,并且当他们关闭窗口时,更新后的对象将被发送回第一个ViewModel。您对此有什么解决方案吗?
Ehsan

2

我认为视图可能具有处理视图模型中事件的代码。

根据事件/场景,它还可以具有一个事件触发器,用于订阅视图模型事件,以及一个或多个要响应调用的动作。




1

Karl Shifflett创建了一个示例应用程序,用于使用服务方法和Prism InteractionRequest方法显示对话框。

我喜欢这种服务方法-灵活性较差,因此用户不太可能破坏某些东西:)它也与我的应用程序的WinForms部分(MessageBox.Show)保持一致,但是如果您打算显示很多不同的对话框,则InteractionRequest是更好的方法。

http://karlshifflett.wordpress.com/2010/11/07/in-the-box-ndash-mvvm-training/


1

我知道这是一个古老的问题,但是当我进行此搜索时,发现了很多相关问题,但是没有找到一个明确的答案。因此,我对对话框/消息框/弹出窗口进行了自己的实现,并与我分享!
我认为它是“ MVVM证明”,并且我尝试使其变得简单而适当,但是我对WPF还是陌生的,所以请随时发表评论,甚至提出请求。

https://github.com/Plasma-Paris/Plasma.WpfUtils

您可以像这样使用它:

public RelayCommand YesNoMessageBoxCommand { get; private set; }
async void YesNoMessageBox()
{
    var result = await _Service.ShowMessage("This is the content of the message box", "This is the title", System.Windows.MessageBoxButton.YesNo);
    if (result == System.Windows.MessageBoxResult.Yes)
        // [...]
}

如果您想要更复杂的弹出窗口,则可以这样:

var result = await _Service.ShowCustomMessageBox(new MyMessageBoxViewModel { /* What you want */ });

它显示了这样的事情:

2


1

标准方法

在WPF中花了多年时间解决这个问题之后,我终于弄清楚了在WPF中实现对话框的标准方法。这是此方法的优点:

  1. 清洁
  2. 不违反MVVM设计模式
  3. ViewModal从不引用任何UI库(WindowBase,PresentationFramework等)
  4. 非常适合自动化测试
  5. 对话框可以轻松替换。

那么关键是什么。是DI + IoC

下面是它的工作原理。我正在使用MVVM Light,但是这种方法也可以扩展到其他框架:

  1. 将WPF应用程序项目添加到您的解决方案。称之为App
  2. 添加一个ViewModal类库。称之为VM
  3. 应用引用了VM项目。VM项目对App一无所知。
  4. 这两个项目添加对MVVM Light的NuGet引用。这些天我正在使用MVVM Light Standard,但是您也可以使用完整的Framework版本。
  5. 将接口IDialogService添加到VM项目:

    public interface IDialogService
    {
      void ShowMessage(string msg, bool isError);
      bool AskBooleanQuestion(string msg);
      string AskStringQuestion(string msg, string default_value);
    
      string ShowOpen(string filter, string initDir = "", string title = "");
      string ShowSave(string filter, string initDir = "", string title = "", string fileName = "");
      string ShowFolder(string initDir = "");
    
      bool ShowSettings();
    }
  6. 在中公开IDialogService类型的公共静态属性ViewModelLocator,但保留注册部分以供View层执行。这是关键。:

    public static IDialogService DialogService => SimpleIoc.Default.GetInstance<IDialogService>();
  7. 在App项目中添加此接口的实现。

    public class DialogPresenter : IDialogService
    {
        private static OpenFileDialog dlgOpen = new OpenFileDialog();
        private static SaveFileDialog dlgSave = new SaveFileDialog();
        private static FolderBrowserDialog dlgFolder = new FolderBrowserDialog();
    
        /// <summary>
        /// Displays a simple Information or Error message to the user.
        /// </summary>
        /// <param name="msg">String text that is to be displayed in the MessageBox</param>
        /// <param name="isError">If true, Error icon is displayed. If false, Information icon is displayed.</param>
        public void ShowMessage(string msg, bool isError)
        {
                if(isError)
                        System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.OK, MessageBoxImage.Error);
                else
                        System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.OK, MessageBoxImage.Information);
        }
    
        /// <summary>
        /// Displays a Yes/No MessageBox.Returns true if user clicks Yes, otherwise false.
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public bool AskBooleanQuestion(string msg)
        {
                var Result = System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes;
                return Result;
        }
    
        /// <summary>
        /// Displays Save dialog. User can specify file filter, initial directory and dialog title. Returns full path of the selected file if
        /// user clicks Save button. Returns null if user clicks Cancel button.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="initDir"></param>
        /// <param name="title"></param>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public string ShowSave(string filter, string initDir = "", string title = "", string fileName = "")
        {
                if (!string.IsNullOrEmpty(title))
                        dlgSave.Title = title;
                else
                        dlgSave.Title = "Save";
    
                if (!string.IsNullOrEmpty(fileName))
                        dlgSave.FileName = fileName;
                else
                        dlgSave.FileName = "";
    
                dlgSave.Filter = filter;
                if (!string.IsNullOrEmpty(initDir))
                        dlgSave.InitialDirectory = initDir;
    
                if (dlgSave.ShowDialog() == DialogResult.OK)
                        return dlgSave.FileName;
                else
                        return null;
        }
    
    
        public string ShowFolder(string initDir = "")
        {
                if (!string.IsNullOrEmpty(initDir))
                        dlgFolder.SelectedPath = initDir;
    
                if (dlgFolder.ShowDialog() == DialogResult.OK)
                        return dlgFolder.SelectedPath;
                else
                        return null;
        }
    
    
        /// <summary>
        /// Displays Open dialog. User can specify file filter, initial directory and dialog title. Returns full path of the selected file if
        /// user clicks Open button. Returns null if user clicks Cancel button.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="initDir"></param>
        /// <param name="title"></param>
        /// <returns></returns>
        public string ShowOpen(string filter, string initDir = "", string title = "")
        {
                if (!string.IsNullOrEmpty(title))
                        dlgOpen.Title = title;
                else
                        dlgOpen.Title = "Open";
    
                dlgOpen.Multiselect = false;
                dlgOpen.Filter = filter;
                if (!string.IsNullOrEmpty(initDir))
                        dlgOpen.InitialDirectory = initDir;
    
                if (dlgOpen.ShowDialog() == DialogResult.OK)
                        return dlgOpen.FileName;
                else
                        return null;
        }
    
        /// <summary>
        /// Shows Settings dialog.
        /// </summary>
        /// <returns>true if User clicks OK button, otherwise false.</returns>
        public bool ShowSettings()
        {
                var w = new SettingsWindow();
                MakeChild(w); //Show this dialog as child of Microsoft Word window.
                var Result = w.ShowDialog().Value;
                return Result;
        }
    
        /// <summary>
        /// Prompts user for a single value input. First parameter specifies the message to be displayed in the dialog 
        /// and the second string specifies the default value to be displayed in the input box.
        /// </summary>
        /// <param name="m"></param>
        public string AskStringQuestion(string msg, string default_value)
        {
                string Result = null;
    
                InputBox w = new InputBox();
                MakeChild(w);
                if (w.ShowDialog(msg, default_value).Value)
                        Result = w.Value;
    
                return Result;
        }
    
        /// <summary>
        /// Sets Word window as parent of the specified window.
        /// </summary>
        /// <param name="w"></param>
        private static void MakeChild(System.Windows.Window w)
        {
                IntPtr HWND = Process.GetCurrentProcess().MainWindowHandle;
                var helper = new WindowInteropHelper(w) { Owner = HWND };
        }
    }
  8. 尽管其中一些功能是通用的(ShowMessageAskBooleanQuestion等),但其他功能特定于此项目并使用custom Window。您可以以相同的方式添加更多自定义窗口。关键是将特定于UI的元素保留在View层中,而仅使用VM层中的POCO公开返回的数据
  9. 使用此类在接口的View层中执行IoC注册。您可以在主视图的构造函数中执行此操作(InitializeComponent()调用后):

    SimpleIoc.Default.Register<IDialogService, DialogPresenter>();
  10. 妳去 现在,您可以在VM和View层访问所有对话框功能。您的VM层可以这样调用这些函数:

    var NoTrump = ViewModelLocator.DialogService.AskBooleanQuestion("Really stop the trade war???", "");
  11. 很干净,你看。VM层对UI层如何向用户显示是/否问题一无所知,并且仍然可以成功地使用对话框返回的结果。

其他免费津贴

  1. 对于编写单元测试,可以IDialogService在Test项目中提供的自定义实现,并在IoC中的测试类的构造函数中注册该类。
  2. 您需要导入一些名称空间,例如Microsoft.Win32访问“打开”和“保存”对话框。我之所以省略了它们,是因为这些对话框还有WinForms版本,而且有人可能想创建自己的版本。另外请注意,其中使用的某些标识符DialogPresenter是我自己的窗口的名称(例如SettingsWindow)。您需要将它们从界面和实现中删除,或者提供自己的窗口。
  3. 如果您的VM执行多线程,请DispatcherHelper.Initialize()在应用程序生命周期的早期调用MVVM Light 。
  4. 除了DialogPresenter在View层中注入了哪些注入之外,还应在其他ViewModals中注册ViewModelLocator,然后应公开该类型的公共静态属性以供View层使用。像这样:

    public static SettingsVM Settings => SimpleIoc.Default.GetInstance<SettingsVM>();
  5. 在大多数情况下,您的对话框不应包含诸如绑定或设置DataContext等之类的任何代码后缀。您甚至不应将其作为构造函数参数传递。XAML可以为您完成所有任务,如下所示:

    <Window x:Class="YourViewNamespace.SettingsWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:local="clr-namespace:YourViewProject"
      xmlns:vm="clr-namespace:YourVMProject;assembly=YourVMProject"
      DataContext="{x:Static vm:ViewModelLocator.Settings}"
      d:DataContext="{d:DesignInstance Type=vm:SettingsVM}" />
  6. 设置DataContext这种方式可以为您带来各种设计时优势,例如Intellisense和自动完成功能。

希望对大家有帮助。


0

在询问任务或对话框的视图模型应如何时,我正在考虑类似的问题。

我当前的解决方案如下所示:

public class SelectionTaskModel<TChoosable> : ViewModel
    where TChoosable : ViewModel
{
    public SelectionTaskModel(ICollection<TChoosable> choices);
    public ReadOnlyCollection<TChoosable> Choices { get; }
    public void Choose(TChoosable choosen);
    public void Abort();
}

当视图模型确定需要用户输入时,它会SelectionTaskModel为用户提供一个带有可能选择的实例。基础结构负责调出相应的视图,该视图将在适当的时候Choose()根据用户的选择调用该函数。


0

我也遇到了同样的问题。我想出了一种在View和ViewModel之间进行相互通信的方法。您可以启动从ViewModel到View的消息发送,以告诉它显示一个消息框,并且它将报告结果。然后,ViewModel可以响应从View返回的结果。

我在博客中演示了这一点:



0

抱歉,但是我必须提一下。在Prism项目中找到Prism.Wpf.Interactivity命名空间之前,我已经完成了一些建议的解决方案。您可以使用交互请求和弹出窗口动作来滚动自定义窗口,或者为了更简单的需求,内置了“通知和确认”弹出窗口。这些创建真正的窗口,并以此进行管理。您可以在对话框中传递具有所需依赖关系的上下文对象。自从找到解决方案以来,我们一直在使用该解决方案。我们这里有许多高级开发人员,没有人能提出更好的建议。我们之前的解决方案是将对话框服务转换为叠加层,并使用presenter类来实现它,但是您必须为所有对话框视图模型提供工厂,等等。

这不是小事,但也不是超级复杂。它内置于Prism中,因此是最佳(或更好)练习恕我直言的方法。

我的2美分!


-1

编辑:是的,我同意这不是正确的MVVM方法,并且我现在使用的方法与blindmeis建议的方法类似。

您可以采用的一种方法是

在主视图模型(在其中打开模态)中:

void OpenModal()
{
    ModalWindowViewModel mwvm = new ModalWindowViewModel();
    Window mw = new Window();
    mw.content = mwvm;
    mw.ShowDialog()
    if(mw.DialogResult == true)
    { 
        // Your Code, you can access property in mwvm if you need.
    }
}

并在您的模态窗口View / ViewModel中:

XAML:

<Button Name="okButton" Command="{Binding OkCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">OK</Button>
<Button Margin="2" VerticalAlignment="Center" Name="cancelButton" IsCancel="True">Cancel</Button>

ViewModel:

public ICommand OkCommand
{
    get
    {
        if (_okCommand == null)
        {
            _okCommand = new ActionCommand<Window>(DoOk, CanDoOk);
        }
        return _okCommand ;
    }
}

void DoOk(Window win)
{
    <!--Your Code-->
    win.DialogResult = true;
    win.Close();
}

bool CanDoOk(Window win) { return true; }

或类似于此处发布的WPF MVVM:如何关闭窗口


2
我不是反对派,但我怀疑这是因为视图模型直接引用了视图。
Brian Gideon

@BrianGideon,谢谢您的评论。我同意这不是分离的解决方案。实际上,我没有使用与blindmeis建议的类似的东西。再次感谢。
西蒙妮

当不容易进入视图时,这是一种不好的形式。
克里斯·博德曼
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.