ViewModel应该如何关闭表单?

268

我正在尝试学习WPF和MVVM模式,但遇到了一个问题。这个问题类似于此问题(在WPF中处理对话框),但并不完全相同...

我使用MVVM模式编写了一个“登录”表单。

该表单有一个ViewModel来保存用户名和密码,并使用正常的数据绑定将它们绑定到XAML视图上。还有一个“Login”命令,使用正常的数据绑定将其绑定到表单上的“Login”按钮。

当“Login”命令触发时,它会调用ViewModel中的一个函数,该函数会发送数据经过网络进行登录。当该函数完成时,会出现两种情况:

  1. 登录无效-我们只需弹出一个消息框就可以了

  2. 登录有效,我们需要关闭登录窗口并返回true作为它的DialogResult

问题是,ViewModel对实际视图一无所知,那么它如何关闭视图并告诉它返回特定的DialogResult??我可以在CodeBehind中添加一些代码,或者将视图传递给ViewModel,但这似乎会完全破坏MVVM的整个目的...


更新

最终,我打破了MVVM模式的“纯洁性”,使View发布了一个Closed事件,并公开了一个Close方法。然后ViewModel只需调用view.Close. View仅通过接口知道,并通过IOC容器连接,因此没有失去可测试性或可维护性。

看起来,被接受的答案获得-5票似乎相当愚蠢!虽然我十分清楚在保持“纯粹”的同时解决问题所带来的好感,但我肯定不是唯一一个认为200行事件、命令和行为只为了避免使用一个一行代码的方法而使用“模式”和“纯洁性”有点荒谬的人...


我使用附加行为来关闭窗口。 将 ViewModel 上的“signal”属性绑定到附加行为上(我实际上使用触发器)。 当它设置为 true 时,该行为会关闭窗口。http://adammills.wordpress.com/2009/07/01/window-close-from-xaml/ - Adam Mills
25个回答

0
我已经阅读了所有的答案,但必须说,它们大部分都不够好甚至更糟。
您可以使用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 first方法:WPF中从ViewModel调用View的最佳实践

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


0

这里是简单、无错误的解决方案(包括源代码),对我来说它是有效的。

  1. INotifyPropertyChanged 派生您的 ViewModel

  2. 在 ViewModel 中创建一个可观察属性 CloseDialog

    public void Execute()
    {
        // 在此处执行任务
    
        // 如果任务成功,将 true 分配给 CloseDialog
        CloseDialog = true;
    }
    
    private bool _closeDialog;
    public bool CloseDialog
    {
        get { return _closeDialog; }
        set { _closeDialog = value; OnPropertyChanged(); }
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
    
    private void OnPropertyChanged([CallerMemberName]string property = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }
    

    }

  3. 为此属性更改附加视图处理程序

        _loginDialogViewModel = new LoginDialogViewModel();
        loginPanel.DataContext = _loginDialogViewModel;
        _loginDialogViewModel.PropertyChanged += OnPropertyChanged;
    
  4. 现在您几乎完成了。在事件处理程序中使 DialogResult = true

    protected void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
    {
        if (args.PropertyName == "CloseDialog")
        {
            DialogResult = true;
        }
    }
    

0
我会选择这种方式:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;    
using GalaSoft.MvvmLight.Messaging; 

// View

public partial class TestCloseWindow : Window
{
    public TestCloseWindow() {
        InitializeComponent();
        Messenger.Default.Register<CloseWindowMsg>(this, (msg) => Close());
    }
}

// View Model

public class MainViewModel: ViewModelBase
{
    ICommand _closeChildWindowCommand;

    public ICommand CloseChildWindowCommand {
        get {
            return _closeChildWindowCommand?? (_closeChildWindowCommand = new RelayCommand(() => {
                Messenger.Default.Send(new CloseWindowMsg());
        }));
        }
    }
}

public class CloseWindowMsg
{
}

-4

当您需要关闭窗口时,只需将此代码放入视图模型中:

ta-da

  foreach (Window window in Application.Current.Windows)
        {
            if (window.DataContext == this)
            {
                window.Close();
                return;
            }
        }

一个ViewModel绝不应以任何方式包含UIElement,因为这可能会导致错误。 - WiiMaxx
如果DataContext被多个窗口继承,会发生什么? - Kylo Ren
1
哒哒,这完全不是MVVM。 - Alexandru Dicu

-10
Application.Current.MainWindow.Close() 

够了!


6
只有当你想关闭的窗口是主窗口时才成立……这对于登录对话框来说是非常不可能的假设…… - surfen

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