261

我正在尝试学习 WPF 和 MVVM 问题,但遇到了障碍。这个问题与这个问题相似但不完全相同(handling-dialogs-in-wpf-with-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 行事件、命令和行为只是为了避免单行方法的人“模式”和“纯度”的名称有点可笑....

4

25 回答 25

336

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

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

这几乎就像 WPF 团队做对了并且首先将 DialogResult 设置为依赖属性一样干净。只需bool? DialogResult在您的 ViewModel 上放置一个属性并实现 INotifyPropertyChanged,瞧,您的 ViewModel 只需设置一个属性即可关闭窗口(并设置其 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);
        }
    }
}

我也在我的博客上发布了这个。

于 2010-07-25T14:06:54.007 回答
67

从我的角度来看,这个问题非常好,因为同样的方法不仅适用于“登录”窗口,而且适用于任何类型的窗口。我已经审查了很多建议,但没有一个适合我。请查看我从MVVM 设计模式文章中获得的建议。

每个 ViewModel 类都应该继承自该类型WorkspaceViewModelRequestClose事件和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
}

PS不要因为这些来源而对我不好!如果我昨天有它们,那将节省我几个小时......

PPS欢迎提出意见或建议。

于 2010-01-20T11:05:21.800 回答
15

有很多评论在这里争论 MVVM 的优缺点。对我来说,我同意 Nir;这是适当使用模式的问题,MVVM 并不总是适合。人们似乎已经愿意牺牲软件设计中所有最重要的原则,只是为了让它适应 MVVM。

也就是说,..我认为您的案例可能非常适合进行一些重构。

在我遇到的大多数情况下,WPF 使您能够在没有多个Windows 的情况下度过难关。也许您可以尝试使用Frames 和Pages 而不是 Windows 和DialogResults。

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

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

希望有帮助。

于 2009-03-24T12:54:46.417 回答
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;
        }
    }
于 2009-12-28T20:09:09.047 回答
9

这是一个简单而干净的解决方案 - 您将一个事件添加到 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>

视图模型:

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实现都可以用于此问题。

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

于 2011-10-06T08:14:18.963 回答
6

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

于 2009-04-28T21:08:34.630 回答
4

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

1:应用程序.xaml.cs

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

2:登录表单.xaml

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

3:登录表单.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 比我没有使用它更难理解,现在它是一种模式。

于 2009-02-02T00:31:41.560 回答
4
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();
  }
}
于 2016-08-30T07:14:36.293 回答
3

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

于 2011-02-23T22:00:27.900 回答
3

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

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

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

我想这是一种解决方法/hack,但它在没有真正破坏 MVVM 模式的情况下运行良好。

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

于 2009-04-29T18:04:12.957 回答
3

这可能很晚了,但我遇到了同样的问题,我找到了一个适合我的解决方案。

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

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

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

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

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

有了所有这些,您将获得一个显示对话框的单行代码。你得到的问题是你不能只用上面的代码关闭对话框。所以这就是为什么你必须在 ViewModel 基类中放入一个事件,DisplayViewModel 继承自它,而不是上面的代码,写这个

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

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

这可能看起来有点复杂,但是一旦奠定了基础,它就非常简单了。这又是我的实现,我敢肯定还有其他实现:)

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

于 2009-05-28T18:08:04.913 回答
3

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

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

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

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

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

无论是谁创建了 View,或者在 View 后面的代码中,我只需设置 Action 将调用的 Method:

(记住 MVVM 是关于 View 和 ViewModel 的分离...... View 的代码仍然是 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(恕我直言),所有关于它的信息都可以在这里找到

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

我提供的链接包含所有代码片段和示例,因此我不会在此处放置任何代码,只需阅读下载 Prism 快速入门并运行它的文章即可,理解起来非常简单,只是稍微冗长一点让它发挥作用,但好处不仅仅是关闭一个窗口。

于 2015-06-13T23:17:02.067 回答
2

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

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

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

埃里克

于 2011-03-05T02:11:26.623 回答
1

我最终将Joe White 的答案Adam Mills 的答案中的一些代码混合在一起,因为我需要在以编程方式创建的窗口中显示用户控件。所以 DialogCloser 不必在窗口上,它可以在用户控件本身上

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

如果 DialogCloser 未附加到窗口本身,则 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_ );
    }
  }
}
于 2014-03-27T16:13:37.730 回答
1

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

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}" />
于 2010-10-11T18:49:37.310 回答
1

我实现了 Joe White 的解决方案,但偶尔遇到“ DialogResult can be set only after Window is created and shown as dialog ”错误。

在 View 关闭后,我一直保留 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?;
}

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

于 2012-07-15T19:56:36.243 回答
1

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

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

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

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

于 2018-05-24T13:09:29.343 回答
0

虽然这没有回答如何通过视图模型执行此操作的问题,但它确实显示了如何仅使用 XAML + blend SDK 来执行此操作。

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

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>

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

于 2015-04-02T00:05:04.480 回答
0

另一种解决方案是在视图模型中使用 INotifyPropertyChanged 创建属性,如 DialogResult,然后在后面的代码中写下:

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_PropertyChanged. DialogResultPropertyName可以是SomeViewModel.

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

于 2012-03-21T12:54:08.607 回答
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
{
}
于 2014-08-08T21:34:02.103 回答
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 调用视图的最佳实践

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

于 2015-03-14T18:12:27.137 回答
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;
        }
    }
    
于 2015-05-30T12:50:33.223 回答
0

Dependency Property在您的View/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 中的值来触发关闭操作。:)

于 2016-02-08T10:39:27.833 回答
-2

在需要关闭窗口的地方,只需将其放在视图模型中:

田田

  foreach (Window window in Application.Current.Windows)
        {
            if (window.DataContext == this)
            {
                window.Close();
                return;
            }
        }
于 2014-01-31T08:02:41.513 回答
-8
Application.Current.MainWindow.Close() 

够了!

于 2011-07-21T09:52:06.503 回答