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


当前回答

我最终混合了Joe White的回答和Adam Mills的回答中的一些代码,因为我需要在编程创建的窗口中显示用户控件。因此,DialogCloser不需要在窗口上,它可以在用户控件本身上

<UserControl ...
    xmlns:xw="clr-namespace:Wpf"
    xw:DialogCloser.DialogResult="{Binding DialogResult}">

而DialogCloser会找到用户控件的窗口,如果它没有附加到窗口本身。

namespace 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.GetWindow();
      if (window != null)
        window.DialogResult = e.NewValue as bool?;
    }

    public static void SetDialogResult(DependencyObject target, bool? value)
    {
      target.SetValue(DialogResultProperty, value);
    }
  }

  public static class Extensions
  {
    public static Window GetWindow(this DependencyObject sender_)
    {
      Window window = sender_ as Window;        
      return window ?? Window.GetWindow( sender_ );
    }
  }
}

其他回答

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

 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值来触发关闭操作。:)

假设你的登录对话框是第一个被创建的窗口,在你的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;
        }
    }

供参考,我遇到了同样的问题,我想我找到了一个不需要全局变量或静态变量的工作,尽管它可能不是最好的答案。我让你们自己决定。

在我的例子中,实例化要显示的窗口的ViewModel(让我们称之为ViewModelMain)也知道LoginFormViewModel(使用上面的情况作为例子)。

所以我所做的是在LoginFormViewModel上创建一个属性,它的类型是ICommand(让我们称之为CloseWindowCommand)。然后,在窗口上调用. showdialog()之前,我将LoginFormViewModel上的CloseWindowCommand属性设置为我实例化的窗口的Window . close()方法。然后在LoginFormViewModel中,我所要做的就是调用CloseWindowCommand.Execute()来关闭窗口。

我认为这是一种变通/hack,但它在没有真正打破MVVM模式的情况下工作得很好。

你可以随意批评这个过程,我可以接受!:)

行为是这里最方便的方式。

一方面,它可以被绑定到给定的视图模型 信号“关闭表单!”) 另一方面,它可以访问表单本身,因此可以订阅必要的特定于表单的事件,或显示确认对话框,或其他任何东西。

写作的必要行为第一次被认为是无聊的。但是,从现在开始,您可以通过精确的一行XAML片段在所需的每个表单上重用它。如果有必要,可以将其提取为单独的程序集,以便将其包含到您想要的任何下一个项目中。

为什么不直接将窗口作为命令参数传递呢?

C#:

 private void Cancel( Window window )
  {
     window.Close();
  }

  private ICommand _cancelCommand;
  public ICommand CancelCommand
  {
     get
     {
        return _cancelCommand ?? ( _cancelCommand = new Command.RelayCommand<Window>(
                                                      ( window ) => Cancel( window ),
                                                      ( window ) => ( true ) ) );
     }
  }

XAML:

<Window x:Class="WPFRunApp.MainWindow"
        x:Name="_runWindow"
...
   <Button Content="Cancel"
           Command="{Binding Path=CancelCommand}"
           CommandParameter="{Binding ElementName=_runWindow}" />