ViewModel应该如何关闭表单?


247

我正在尝试学习WPF和MVVM问题,但是遇到了麻烦。这个问题与这个问题相似(但不完全相同)

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

此表单具有一个ViewModel,其中包含用户名和密码,这些用户名和密码使用常规数据绑定绑定到XAML中的视图。它还具有一个“登录”命令,该命令绑定到表单上的“登录”按钮,使用常规数据绑定绑定。

当“ Login”命令触发时,它将调用ViewModel中的一个函数,该函数将关闭并通过网络发送数据以进行登录。完成此函数后,将执行2个操作:

  1. 登录名无效-我们只显示一个MessageBox,一切正常

  2. 登录名有效,我们需要关闭“登录名”表单,并让它返回true,因为它是DialogResult...

问题是,ViewModel对实际视图一无所知,那么如何关闭视图并告诉它返回特定的DialogResult?我可以在CodeBehind中粘贴一些代码,和/或将View传递给ViewModel,但这似乎将完全击败MVVM的整个方面。


更新资料

最后,我只是违反了MVVM模式的“纯度”,并让View发布了一个Closed事件,并公开了一个Close方法。然后,ViewModel将仅调用view.Close。该视图仅通过接口已知,并通过IOC容器连接,因此不会丢失可测试性或可维护性。

接受的答案是-5票,似乎很愚蠢!虽然我很清楚通过“纯粹”解决问题会带来的良好感觉,但我当然不是唯一认为200行事件,命令和行为只是为了避免使用一种方法的人。 “模式”和“纯度”的名称有点荒谬。


2
我没有对接受的答案进行投票,但是我猜测投票的原因是,即使它可能在一种情况下工作,它通常也无济于事。您自己在另一条评论中说过:“虽然登录表单是一个“两个字段”对话框,但我还有许多其他问题要复杂得多(因此需要MVVM),但仍然需要关闭...”
Joe白色,2010年

1
我明白您的意思,但我个人认为,即使对于一般情况,简单的Close方法仍然是最佳解决方案。其他更复杂的对话框上的其他所有内容都是MVVM和数据绑定,但是在这里实现巨大的“解决方案”而不是简单的方法似乎很愚蠢……
Orion Edwards

2
您可以检查对话框结果asimsajjad.blogspot.com/2010/10/…的以下链接,这将返回对话框resutl并从viewModel中关闭视图
Asim Sajjad 2010年

3
请更改此问题的可接受答案。有很多很好的解决方案比有人质疑将MVVM用于此功能要好得多。那不是答案,这是回避。
ScottCher 2011年

2
@OrionEdwards我认为您在这里打破常规是正确的。设计模式的主要目的是通过使整个团队遵循相同的规则来加快开发周期,增加可维护性并简化您的代码。这不能通过添加对外部库的依赖关系并实现数百条代码行来完成一项任务来实现,而完全忽略了更简单的解决方案,只是因为一个人固执地牺牲了模式的“纯度”。只要确保到your've做了什么文件,你的代码(ķ EEP 牛逼小号园艺和小号 imple)。
M463

Answers:


324

Thejuan的回答启发了我,编写了一个更简单的附加属性。没有样式,没有触发器;相反,您可以执行以下操作:

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

这几乎就像WPF团队正确无误地使DialogResult成为依赖项属性一样干净。只要放一个bool? DialogResult在ViewModel上属性并实现INotifyPropertyChanged,然后打开,您的ViewModel就可以通过设置属性来关闭Window(并设置其DialogResult)。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);
        }
    }
}

我也把这个发布在我的博客上


3
这是我最喜欢的答案!做好附加财产的写作。
豪尔赫·巴尔加斯

2
不错的选择,但是此解决方案中有一个细微的错误。如果对话框的视图模型是单例,则DialogResult值将结转到对话框的下一次使用。这意味着它将在显示自己之前立即取消或接受,因此该对话框将不会再次显示。
Gone Coding

13
@HiTech Magic,听起来像是该bug首先是使用Singleton ViewModel。(笑)说真的,为什么在地球上你想要一个单身的ViewModel?在全局变量中保持可变状态是一个坏主意。使测试成为噩梦,而测试是您首先使用MVVM的原因之一。
乔·怀特

3
MVVM的重点不是将您的逻辑紧密耦合到任何特定的UI吗?在这种情况下,笨蛋?最肯定不能被WinForm等其他UI使用,并且DialogCloser特定于WPF。那么这如何适合作为解决方案呢?另外,为什么编写2x-10x代码只是为了通过绑定关闭窗口?
David Anderson

2
@DavidAnderson,无论如何我都不会尝试将MVVM与WinForms一起使用;它的数据绑定支持太弱,并且MVVM依赖于经过深思熟虑的绑定系统。而且它远不及2x-10x的代码。您只需编写一次该代码,而不必为每个窗口编写一次。之后,它是一个单行绑定和一个通知属性,使用与您在视图中其他所有内容所使用的相同的机制(因此,例如,您不需要注入额外的视图接口即可处理关闭窗口)。欢迎您进行其他折衷,但是对我来说,这通常是一笔不错的交易。
乔·怀特

64

从我的角度来看,这个问题很好,因为该方法不仅可用于“登录”窗口,而且可用于任何类型的窗口。我已经审查了很多建议,但对我来说都没有建议。请查看我的建议,该建议来自MVVM设计模式文章

每个ViewModel类都应从该类继承,该类WorkspaceViewModel具有RequestClose事件和类型的CloseCommand属性ICommand。该CloseCommand属性的默认实现将引发该RequestClose事件。

为了关闭OnLoaded窗口,应该重写窗口的方法:

void CustomerWindow_Loaded(object sender, RoutedEventArgs e)
{
    CustomerViewModel customer = CustomerViewModel.GetYourCustomer();
    DataContext = customer;
    customer.RequestClose += () => { Close(); };
}

OnStartup您应用的方法:

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        MainWindow window = new MainWindow();
        var viewModel = new MainWindowViewModel();
        viewModel.RequestClose += window.Close;
        window.DataContext = viewModel;

        window.Show();
    }

我认为中的RequestClose事件和CloseCommand属性实现WorkspaceViewModel非常清楚,但是我将展示它们的一致性:

public abstract class WorkspaceViewModel : ViewModelBase
// There's nothing interesting in ViewModelBase as it only implements the INotifyPropertyChanged interface
{
    RelayCommand _closeCommand;
    public ICommand CloseCommand
    {
        get
        {
            if (_closeCommand == null)
            {
                _closeCommand = new RelayCommand(
                   param => Close(),
                   param => CanClose()
                   );
            }
            return _closeCommand;
        }
    }

    public event Action RequestClose;

    public virtual void Close()
    {
        if ( RequestClose != null )
        {
            RequestClose();
        }
    }

    public virtual bool CanClose()
    {
        return true;
    }
}

和的源代码RelayCommand

public class RelayCommand : ICommand
{
    #region Constructors

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }
    #endregion // Constructors

    #region ICommand Members

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    #endregion // ICommand Members

    #region Fields

    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    #endregion // Fields
}

言:对于这些消息来源,请不要对我不好!如果我昨天有他们,那将节省我几个小时。

PPS欢迎任何意见或建议。


2
嗯,您在customer.RequestCloseXAML文件后面的代码中迷上了事件处理程序,这是否违反了MVVM模式?您可能还应该首先绑定到Click关闭按钮上的事件处理程序,因为您无论如何都已经触摸了后面的代码并执行了this.Close()!对?
GONeale 2012年

1
我对事件方法没有太多问题,但是我不喜欢RequestClose一词,因为对我而言,它仍然意味着对View实现的大量了解。我更喜欢公开诸如IsCancelled之类的属性,这些属性在给定上下文的情况下往往更有意义,而暗示该视图应该作为响应的内容则更少。
jpierson

18

我使用附加行为关闭了窗口。将ViewModel上的“ signal”属性绑定到附加的行为(我实际上使用了触发器)将其设置为true时,该行为将关闭窗口。

http://adammills.wordpress.com/2009/07/01/window-close-from-xaml/


到目前为止,这是唯一不需要在窗口中隐藏任何代码的答案(并且实际上确实关闭了模式窗口,而不是建议其他方法)。可惜的是,它需要使用Style和Trigger以及这么多的东西,所以它需要这么多的复杂性-似乎确实应该通过单行附加行为来实现。
乔·怀特2010年

4
现在可以通过单行附加行为来实现。见我的回答:stackoverflow.com/questions/501886/...
乔·怀特

15

这里有很多评论争论MVVM的优缺点。对我来说,我同意尼尔。这是正确使用模式的问题,MVVM并不总是适合。人们似乎已经愿意牺牲软件设计的所有最重要的原理,以使其适合MVVM。

就是说,..我认为您的案子可能需要一些重构才能很好地解决。

在大多数情况下,我遇到过,WPF使您无需花费多个即可Window。也许您可以尝试使用Frames和Pages代替Windows和DialogResults。

在您的情况下,我的建议是LoginFormViewModel处理LoginCommand,如果登录无效,请将属性设置为LoginFormViewModel适当的值(false或类似的枚举值UserAuthenticationStates.FailedAuthentication)。对于成功登录(true或其他一些枚举值),您将执行相同的操作。然后,您可以使用DataTrigger响应各种用户身份验证状态的,并可以使用简单的Setter来更改的Source属性Frame

DialogResult我想让登录窗口返回一个让我感到困惑的地方;那DialogResult实际上是ViewModel的一个属性。在我公认的WPF有限经验中,当某些事情感觉不对时,通常是因为我在考虑如何在WinForms中完成相同的事情。

希望能有所帮助。


10

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

男人,这很简单,效果很好。目前,我正在使用这种方法。
Erre Efe

它仅适用于MAIN窗口。因此,请勿将其用于其他任何窗口。
Oleksii 2015年

7

这是一种简单干净的解决方案-您将一个事件添加到ViewModel中,并指示Window在触发该事件时自行关闭。

有关更多详细信息,请参阅我的博客文章“ ViewModel的关闭窗口”

XAML:

<Window
  x:Name="this"
  xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"  
  xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions">
  <i:Interaction.Triggers>
    <i:EventTrigger SourceObject="{Binding}" EventName="Closed">
      <ei:CallMethodAction
        TargetObject="{Binding ElementName=this}"
        MethodName="Close"/>
    </i:EventTrigger>
  </i:Interaction.Triggers>
<Window>

ViewModel:

private ICommand _SaveAndCloseCommand;
public ICommand SaveAndCloseCommand
{
  get
  {
    return _SaveAndCloseCommand ??
      (_SaveAndCloseCommand = new DelegateCommand(SaveAndClose));
  }
}
private void SaveAndClose()
{
  Save();
  Close();
}

public event EventHandler Closed;
private void Close()
{
  if (Closed != null) Closed(this, EventArgs.Empty);
}

注意:该示例使用Prism DelegateCommand(请参见Prism:Commanding),但是任何ICommand实现都可以用于该问题。

您可以使用官方软件包中的行为。


2
+1,但您应在答案本身中提供更多详细信息,例如,此解决方案需要引用Expression Blend Interactivity程序集。
surfen 2011年

6

我要处理的方法是在ViewModel中添加事件处理程序。用户成功登录后,我将触发该事件。在我的视图中,我将附加到此事件,并在触发该事件时将其关闭。


2
那也是我通常所做的。即使考虑所有这些新奇的wpf命令元素,这似乎有点肮脏。
Botz3000,2009年

4

这是我最初所做的工作,确实可以工作,但是它看起来有些冗长和丑陋(全局静态的东西永远都不好)

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使其变得比我以前不使用它要难得多,并且现在是一种模式。


3

仅供参考,我遇到了同样的问题,我想我想出了一种解决方法,尽管它可能不是最佳答案,但不需要全局变量或静态变量。我让你们自己决定。

在我的情况下,实例化要显示的窗口的ViewModel(将其称为ViewModelMain)也知道LoginFormViewModel(以上述情况为例)。

因此,我要做的是在LoginFormViewModel上创建一个ICommand类型的属性(我们称其为CloseWindowCommand)。然后,在我对Window调用.ShowDialog()之前,我将LoginFormViewModel的CloseWindowCommand属性设置为实例化的Window的window.Close()方法。然后在LoginFormViewModel内部,我要做的就是调用CloseWindowCommand.Execute()关闭窗口。

我想这是一个解决方法/破解,但在不破坏MVVM模式的情况下可以很好地工作。

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


我不确定我是否完全理解了它,但这是否意味着必须在LoginWindow之前实例化MainWindow?如果可能的话,我想避免这种情况
Orion Edwards

3

这可能已经很晚了,但是我遇到了同样的问题,并且找到了适合我的解决方案。

我不知道如何创建没有对话框的应用程序(也许这只是一个思维障碍)。所以我对MVVM陷入僵局并显示了一个对话框。所以我看到了这篇CodeProject文章:

http://www.codeproject.com/KB/WPF/XAMLDialog.aspx

这是一个UserControl,它基本上允许一个窗口位于另一个窗口的可视树之内(xaml中不允许)。它还公开了一个名为IsShowing的布尔DependencyProperty。

您可以设置一个样式,通常在资源字典中,只要通过触发器,只要控件!= null的Content属性,它就基本上显示对话框:

<Style TargetType="{x:Type d:Dialog}">
    <Style.Triggers>
        <Trigger Property="HasContent"  Value="True">
            <Setter Property="Showing" Value="True" />
        </Trigger>
    </Style.Triggers>
</Style>

在要显示对话框的视图中,只需执行以下操作:

<d:Dialog Content="{Binding Path=DialogViewModel}"/>

并且在您的ViewModel中,您所要做的就是将属性设置为一个值(注意:ViewModel类必须支持InotifyPropertyChanged,以便视图知道发生了什么)。

像这样:

DialogViewModel = new DisplayViewModel();

要将ViewModel与View匹配,您在资源字典中应该有以下内容:

<DataTemplate DataType="{x:Type vm:DisplayViewModel}">
    <vw:DisplayView/>
</DataTemplate>

有了这些,您就获得了一个显示对话框的单行代码。您遇到的问题是,仅使用上面的代码就无法真正关闭对话框。因此,您必须在DisplayModel继承的ViewModel基类中放入一个事件,而不是上面的代码,编写此代码

        var vm = new DisplayViewModel();
        vm.RequestClose += new RequestCloseHandler(DisplayViewModel_RequestClose);
        DialogViewModel = vm;

然后,您可以通过回调处理对话框的结果。

这似乎有些复杂,但是一旦奠定基础,就非常简单。再次,这是我的实现,我确定还有其他方法:)

希望这会有所帮助,它救了我。


3

好的,所以这个问题已经有将近6年的历史了,我仍然在这里找不到我认为是正确的答案,所以请允许我分享我的“ 2美分” ...

我实际上有2种方法,第一种是简单的方法...第二种是正确的方法,因此,如果您正在寻找正确的方法,只需跳过#1并跳到#2即可

1.快速简便(但不完整)

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

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

无论创建视图的人是谁,或者在后面的视图代码中,我只需设置操作将调用的方法即可:

(请记住,MVVM是关于将View和ViewModel分离的……该视图背后的代码仍然是View,只要有适当的分离,您就不会违反模式)

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

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

或者,如果要在主窗口中将其放置在View的构造函数下:

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

当您要关闭窗口时,只需在ViewModel上调用Action。


2.正确的方法

现在正确的方法是使用Prism(IMHO),有关此功能的更多信息,请参见此处

您可以提出一个交互请求,在新窗口中填充所需的任何数据,进行午餐,关闭它,甚至接收回数据。所有这些封装和MVVM批准。您甚至可以了解窗口如何关闭的状态,例如用户CanceledAccepted(确定按钮)窗口以及是否需要返回数据。答案#1有点复杂,但要完整得多,并且是Microsoft推荐的模式。

我给出的链接包含所有代码片段和示例,因此我不会在这里放置任何代码,只需阅读下载Prism Quick Start并运行它的文章,就可以很简单地将代码详细一点使其工作,但好处远不只是关闭一个窗口。


好的方法,但是ViewModels的分辨率和分配不能总是那么直接。如果同一视图模型是许多Windows的DataContext怎么办?
Kylo Ren

然后,我想您必须立即关闭所有窗口,记住一个Action可以一次触发许多委托,只需使用+=添加一个委托,然后调用该Action,它将触发所有这些...。或者您将必须在您的VM上做出特殊的逻辑,以便它可以知道要关闭哪个窗口(也许有关闭动作的集合)...。但是我认为将多个视图绑定到一个VM并不是最佳实践,最好将一个View和一个VM实例绑定在一起,并且最好是父VM来管理绑定到所有View的所有子VM。
mFeinstein '16

3
public partial class MyWindow: Window
{
    public ApplicationSelection()
    {
      InitializeComponent();

      MyViewModel viewModel = new MyViewModel();

      DataContext = viewModel;

      viewModel.RequestClose += () => { Close(); };

    }
}

public class MyViewModel
{

  //...Your code...

  public event Action RequestClose;

  public virtual void Close()
  {
    if (RequestClose != null)
    {
      RequestClose();
    }
  }

  public void SomeFunction()
  {
     //...Do something...
     Close();
  }
}

2

您可以让ViewModel公开View注册到的事件。然后,当ViewModel决定关闭视图的时间时,它将触发该事件,从而导致视图关闭。如果希望将特定的结果值传递回去,那么您将在ViewModel中具有该属性。


我同意这一点-简单很有价值。我必须考虑下一个初级开发人员受雇接管该项目时会发生什么。我的猜测是,按照您的描述,他将有更好的机会解决这个问题。除非您认为自己将永远维护此代码?+1
院长

2

为了增加大量答案,我想添加以下内容。假设您的ViewModel上有一个ICommand,并且您希望该命令关闭其窗口(或与此有关的任何其他操作),则可以使用类似以下的内容。

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

它不是完美的,并且可能很难测试(因为很难模拟/添加静态值),但是它比其他解决方案更干净(IMHO)。

埃里克


当我看到您的简单回答时,我感到非常高兴!但它也不起作用!我需要使用Visual Basic打开和关闭。您知道VB中(windows [i] .DataContext == this)的等效性吗?
Ehsan

我终于明白了!:) 谢谢。如果windows(i).DataContext是我
Ehsan

您是否也知道打开窗口的简单方法?我也需要在子视图模型中发送和接收一些数据,反之亦然。
Ehsan

1

我实现了Joe White的解决方案,但偶尔会遇到问题,“ 仅在创建Window并显示为对话框 ”错误后才可以设置DialogResult ”。

在关闭视图之后,我一直使用ViewModel,偶尔我后来使用相同的VM打开一个新的View。似乎在旧视图被垃圾回收之前关闭新视图导致DialogResultChanged试图在关闭的窗口上设置DialogResult属性,从而引发错误。

我的解决方案是更改DialogResultChanged以检查窗口的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?;
}

进行此更改后,关闭对话框的所有附件都将被忽略。


谢谢你,先生。我遇到了同样的问题
DJ Burb 2014年

1

我最终混合了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_ );
    }
  }
}

1

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

  • 一方面,它可以绑定到给定的视图模型(可以表示“关闭表单!”)

  • 另一方面,它可以访问表单本身,因此可以订阅必要的表单特定事件,显示确认对话框或其他任何内容。

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


0

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

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}" />

我认为将VM限制为Window类型不是一个好主意。
Shimmy Weitzhandler,2011年

2
我认为将VM限制为Window某种不是“纯” MVVM 的类型不是一个好主意。请参见以下答案,其中VM不仅限于Window对象。
Shimmy Weitzhandler,2011年

这样,依赖关系就放在了Button上,这肯定不会总是这样。还将UI类型传递给ViewModel是一种不良做法。
Kylo Ren

0

另一个解决方案是在DialogResult之类的View Model中使用INotifyPropertyChanged创建属性,然后在“隐藏代码”中编写以下代码:

public class SomeWindow: ChildWindow
{
    private SomeViewModel _someViewModel;

    public SomeWindow()
    {
        InitializeComponent();

        this.Loaded += SomeWindow_Loaded;
        this.Closed += SomeWindow_Closed;
    }

    void SomeWindow_Loaded(object sender, RoutedEventArgs e)
    {
        _someViewModel = this.DataContext as SomeViewModel;
        _someViewModel.PropertyChanged += _someViewModel_PropertyChanged;
    }

    void SomeWindow_Closed(object sender, System.EventArgs e)
    {
        _someViewModel.PropertyChanged -= _someViewModel_PropertyChanged;
        this.Loaded -= SomeWindow_Loaded;
        this.Closed -= SomeWindow_Closed;
    }

    void _someViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == SomeViewModel.DialogResultPropertyName)
        {
            this.DialogResult = _someViewModel.DialogResult;
        }
    }
}

最重要的片段是_someViewModel_PropertyChangedDialogResultPropertyName可以是中的一些公共const字符串SomeViewModel

我使用这种技巧在View控件中进行一些更改,以防在ViewModel中很难做到这一点。在ViewModel中的OnPropertyChanged中,您可以在View中做任何您想做的事情。ViewModel仍然是“可单元测试的”,后面代码中的一些小代码没有区别。


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
{
}

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的第一种方法,该方法在我的文章中进行了介绍: 在WPF中从ViewModel调用View的最佳实践

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


0

尽管这不能回答如何通过viewmodel做到这一点的问题,但这确实说明了如何仅使用XAML + blend SDK做到这一点。

我选择从Blend SDK下载和使用两个文件,您可以通过Microsoft通过NuGet将它们打包为两个文件。这些文件是:

System.Windows.Interactivity.dll和Microsoft.Expression.Interactions.dll

Microsoft.Expression.Interactions.dll为您提供了不错的功能,例如在视图模型或其他目标上设置属性或调用方法的功能,并且内部还包含其他小部件。

一些XAML:

<Window x:Class="Blah.Blah.MyWindow"
    ...
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
  ...>
 <StackPanel>
    <Button x:Name="OKButton" Content="OK">
       <i:Interaction.Triggers>
          <i:EventTrigger EventName="Click">
             <ei:ChangePropertyAction
                      TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                      PropertyName="DialogResult"
                      Value="True"
                      IsEnabled="{Binding SomeBoolOnTheVM}" />                                
          </i:EventTrigger>
    </Button>
    <Button x:Name="CancelButton" Content="Cancel">
       <i:Interaction.Triggers>
          <i:EventTrigger EventName="Click">
             <ei:ChangePropertyAction
                      TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                      PropertyName="DialogResult"
                      Value="False" />                                
          </i:EventTrigger>
    </Button>

    <Button x:Name="CloseButton" Content="Close">
       <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <!-- method being invoked should be void w/ no args -->
                    <ei:CallMethodAction
                        TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                        MethodName="Close" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
    </Button>
 <StackPanel>
</Window>

请注意,如果您只是想执行简单的OK / Cancel行为,只要使用Window.ShowDialog()显示窗口,就可以使用IsDefault和IsCancel属性。
我个人遇到一个问题,其中一个按钮的IsDefault属性设置为true,但是在加载页面时它被隐藏了。它显示后似乎并不想很好地播放,所以我只是按上面所示设置Window.DialogResult属性,它对我有用。


0

这是简单的无错误解决方案(带有源代码),对我有用。

  1. 从中派生您的ViewModel INotifyPropertyChanged

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

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

    }

  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

Dependency PropertyView/ any中创建一个UserControl(或Window您要关闭)。如下所示:

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

现在,通过更改CloseWindowViewModel中的值来触发关闭操作。:)


-2

在需要关闭窗口的地方,只需将其放在viewmodel中:

塔达

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

一个ViewModel必须不包含的UIElement以任何方式,因为这样可以创造错误的
WiiMaxx

如果要继承DataContext是几个窗口怎么办?
Kylo Ren

ta-da,这完全不是MVVM。
亚历山德鲁·迪库

-10
Application.Current.MainWindow.Close() 

够了!


3
-1仅当要关闭的窗口是主窗口时才为true ...登录对话框的假设可能性很小...
surfen 2011年
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.