我试图学习WPF和MVVM问题,但遇到了障碍。 这个问题与这个问题类似,但不完全相同(handling-dialog -in-wpf-with-mvvm)…

我有一个使用MVVM模式编写的“Login”表单。

该表单有一个ViewModel,其中包含用户名和密码,它们使用普通的数据绑定绑定到XAML中的视图。 它还有一个“登录”命令,该命令被绑定到表单上的“登录”按钮,agan使用正常的数据绑定。

当“Login”命令触发时,它会调用ViewModel中的一个函数,该函数会触发并通过网络发送数据以登录。当这个函数完成时,有2个动作:

登录无效-我们只是显示一个消息框,一切正常 登录是有效的,我们需要关闭login表单,并让它返回true作为它的dialgresult…

问题是,ViewModel对实际的视图一无所知,所以它如何关闭视图并告诉它返回一个特定的dialgresult ??我可以在代码背后粘贴一些代码,和/或通过视图模型传递视图,但这似乎会击败整个点MVVM完全…


更新

最后,我违背了MVVM模式的“纯度”,让视图发布了一个Closed事件,并公开了一个Close方法。ViewModel会调用view。close。视图仅通过接口了解,并通过IOC容器连接,因此不会损失可测试性或可维护性。

公认的答案是-5票,这似乎相当愚蠢!虽然我很清楚通过“纯粹”解决问题所获得的良好感觉,但我肯定不是唯一一个认为200行事件、命令和行为只是为了避免以“模式”和“纯粹”为名的一行方法有点荒谬的人....


当前回答

在众多的答案中,我想补充以下几点。假设您在ViewModel上有一个ICommand,并且您希望该命令关闭它的窗口(或任何与此相关的其他操作),您可以使用以下内容。

var windows = Application.Current.Windows;
for (var i=0;i< windows.Count;i++ )
    if (windows[i].DataContext == this)
        windows[i].Close();

它并不完美,可能很难测试(因为它很难模拟/存根静态),但它比其他解决方案更干净。

埃里克

其他回答

我读了所有的答案,但我必须说,他们中的大多数都不够好,甚至更糟。

你可以用DialogService类很好地处理这个问题,它的职责是显示对话框窗口并返回对话框结果。我已经创建示例项目演示它的实现和使用。

以下是最重要的部分:

//we will call this interface in our viewmodels
public interface IDialogService
{
    bool? ShowDialog(object dialogViewModel, string caption);
}

//we need to display logindialog from mainwindow
public class MainWindowViewModel : ViewModelBase
{
    public string Message {get; set;}
    public void ShowLoginCommandExecute()
    {
        var loginViewModel = new LoginViewModel();
        var dialogResult = this.DialogService.ShowDialog(loginViewModel, "Please, log in");

        //after dialog is closed, do someting
        if (dialogResult == true && loginViewModel.IsLoginSuccessful)
        {
            this.Message = string.Format("Hello, {0}!", loginViewModel.Username);
        }
    }
}


public class DialogService : IDialogService
{
    public bool? ShowDialog(object dialogViewModel, string caption)
    {
        var contentView = ViewLocator.GetView(dialogViewModel);
        var dlg = new DialogWindow
        {
            Title = caption
        };
        dlg.PART_ContentControl.Content = contentView;

        return dlg.ShowDialog();
    }
}

这样不是更简单吗?比EventAggregator或其他类似的解决方案更简单,更可读和持久,但并非最不容易调试?

正如你所看到的,在我的视图模型中,我已经使用了我在这里的帖子中描述的ViewModel优先方法:在WPF中从ViewModel调用view的最佳实践

当然,在现实世界中,DialogService。ShowDialog必须有更多的选项来配置对话框,例如他们应该执行的按钮和命令。有不同的方式这样做,但它超出了范围:)

我实现了Joe White的解决方案,但遇到了偶尔出现的“只有在创建Window并显示为对话框后才能设置dialgresult”错误。

在视图关闭后,我一直保持ViewModel,偶尔我也会使用相同的VM打开一个新的视图。看起来,在旧视图被垃圾收集之前关闭新视图会导致dialgresultchanged试图在关闭的窗口上设置dialgresult属性,从而引发错误。

我的解决方案是改变dialgresultchanged来检查窗口的IsLoaded属性:

private static void DialogResultChanged(
    DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
    var window = d as Window;
    if (window != null && window.IsLoaded)
        window.DialogResult = e.NewValue as bool?;
}

做出此更改后,关闭对话框的任何附件都将被忽略。

我处理它的方法是在ViewModel中添加一个事件处理程序。当用户成功登录时,我将触发事件。在我的视图中,我将附加到这个事件,当它发射时,我将关闭窗口。

你可以让ViewModel公开一个View注册到的事件。然后,当ViewModel决定关闭视图的时间时,它会触发导致视图关闭的事件。如果你想要传回一个特定的结果值,那么你会在ViewModel中有一个属性。

我从Thejuan的回答中受到启发,写了一个更简单的附加属性。没有样式,没有触发器;相反,你可以这样做:

<Window ...
        xmlns:xc="clr-namespace:ExCastle.Wpf"
        xc:DialogCloser.DialogResult="{Binding DialogResult}">

这几乎就像WPF团队正确地将dialgresult设置为依赖属性一样干净。只是放一个bool?在你的ViewModel上实现INotifyPropertyChanged和voilà,你的ViewModel可以通过设置属性来关闭窗口(并设置它的dialgresult)。MVVM应该是这样的。

下面是DialogCloser的代码:

using System.Windows;

namespace ExCastle.Wpf
{
    public static class DialogCloser
    {
        public static readonly DependencyProperty DialogResultProperty =
            DependencyProperty.RegisterAttached(
                "DialogResult",
                typeof(bool?),
                typeof(DialogCloser),
                new PropertyMetadata(DialogResultChanged));

        private static void DialogResultChanged(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var window = d as Window;
            if (window != null)
                window.DialogResult = e.NewValue as bool?;
        }
        public static void SetDialogResult(Window target, bool? value)
        {
            target.SetValue(DialogResultProperty, value);
        }
    }
}

我也把这个贴在我的博客上。