我试图学习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行事件、命令和行为只是为了避免以“模式”和“纯粹”为名的一行方法有点荒谬的人....


当前回答

在你的视图/任何用户控件(或你想关闭的窗口)中创建一个依赖属性。像下图:

 public bool CloseTrigger
        {
            get { return (bool)GetValue(CloseTriggerProperty); }
            set { SetValue(CloseTriggerProperty, value); }
        }

        public static readonly DependencyProperty CloseTriggerProperty =
            DependencyProperty.Register("CloseTrigger", typeof(bool), typeof(ControlEventBase), new PropertyMetadata(new PropertyChangedCallback(OnCloseTriggerChanged)));

        private static void OnCloseTriggerChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
        {
            //write Window Exit Code
        }

并从ViewModel的属性中绑定它:

<Window x:Class="WpfStackOverflowTempProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"  Width="525"
        CloseTrigger="{Binding Path=CloseWindow,Mode=TwoWay}"

VeiwModel中的属性:

private bool closeWindow;

    public bool CloseWindow
    {
        get { return closeWindow; }
        set 
        { 
            closeWindow = value;
            RaiseChane("CloseWindow");
        }
    }

现在通过改变ViewModel中的CloseWindow值来触发关闭操作。:)

其他回答

好吧,这个问题已经提了将近6年了,我仍然找不到我认为正确的答案,所以请允许我分享我的“2美分”……

实际上我有两种方法,第一种是简单的…右边的第二个,所以如果你正在寻找正确的一个,跳过#1,跳到#2:

1. 简单快捷(但不完整)

如果我有一个小项目,我有时只是在ViewModel中创建一个CloseWindowAction:

        public Action CloseWindow { get; set; } // In MyViewModel.cs

无论谁装箱视图,或者在视图后面的代码中我设置了方法Action都会调用

(记住MVVM是关于View和ViewModel的分离…视图后面的代码仍然是视图,只要有适当的分离,你就没有违反模式)

如果某个ViewModel创建了一个新窗口:

private void CreateNewView()
{
    MyView window = new MyView();
    window.DataContext = new MyViewModel
                             {
                                 CloseWindow = window.Close,
                             }; 
    window.ShowDialog();
}

或者如果你想把它放在主窗口中,只需要把它放在视图的构造函数下面:

public MyView()
{
    InitializeComponent();           
    this.DataContext = new MainViewModel
                           {
                                CloseWindow = this.Close
                           };
}

当你想关闭窗口时,只需调用ViewModel上的Action。


2. 正确的方法

现在正确的方法是使用Prism(恕我直言),所有关于它的信息都可以在这里找到。

你可以创建一个交互请求,用你在新窗口中需要的任何数据填充它,午餐它,关闭它,甚至接收数据。所有这些都被封装并通过了MVVM。您甚至可以获得窗口如何关闭的状态,如用户是否取消或接受(OK按钮)窗口和数据返回,如果您需要它。它有点复杂,答案#1,但它更完整,是微软推荐的模式。

我给出的链接有所有的代码片段和示例,所以我不会在这里放置任何代码,只要阅读下载Prism快速入门的文章并运行它,它真的很容易理解,只是稍微啰嗦一点,使它工作,但好处比关闭一个窗口更大。

Application.Current.MainWindow.Close() 

这够了!

以下是我最初所做的,它确实有效,但它似乎相当冗长和丑陋(全局静态的任何东西都不好)

1: App.xaml.cs

public partial class App : Application
{
    // create a new global custom WPF Command
    public static readonly RoutedUICommand LoggedIn = new RoutedUICommand();
}

2: LoginForm.xaml

// bind the global command to a local eventhandler
<CommandBinding Command="client:App.LoggedIn" Executed="OnLoggedIn" />

3: LoginForm.xaml.cs

// implement the local eventhandler in codebehind
private void OnLoggedIn( object sender, ExecutedRoutedEventArgs e )
{
    DialogResult = true;
    Close();
}

4: LoginFormViewModel.cs

// fire the global command from the viewmodel
private void OnRemoteServerReturnedSuccess()
{
    App.LoggedIn.Execute(this, null);
}

我后来删除了所有这些代码,只让LoginFormViewModel在它的视图上调用Close方法。它最终变得更好,也更容易理解。在我看来,模式的意义在于让人们更容易理解你的应用程序在做什么,在这种情况下,MVVM使它比我没有使用它更难理解,而且现在是一个反模式。

这是简单的bug免费解决方案(与源代码),这是为我工作。

Derive your ViewModel from INotifyPropertyChanged Create a observable property CloseDialog in ViewModel public void Execute() { // Do your task here // if task successful, assign true to 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)); } } } Attach a Handler in View for this property change _loginDialogViewModel = new LoginDialogViewModel(); loginPanel.DataContext = _loginDialogViewModel; _loginDialogViewModel.PropertyChanged += OnPropertyChanged; Now you are almost done. In the event handler make DialogResult = true protected void OnPropertyChanged(object sender, PropertyChangedEventArgs args) { if (args.PropertyName == "CloseDialog") { DialogResult = true; } }

假设你的登录对话框是第一个被创建的窗口,在你的LoginViewModel类中试试这个:

    void OnLoginResponse(bool loginSucceded)
    {
        if (loginSucceded)
        {
            Window1 window = new Window1() { DataContext = new MainWindowViewModel() };
            window.Show();

            App.Current.MainWindow.Close();
            App.Current.MainWindow = window;
        }
        else
        {
            LoginError = true;
        }
    }