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


当前回答

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

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

其他回答

这是一个简单而干净的解决方案——您向ViewModel添加一个事件,并指示窗口在该事件触发时关闭自己。

要了解更多细节,请参阅我的博客文章,从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: commands),但是可以使用任何ICommand实现。

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

我会这样做:

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

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

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

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

这是一个UserControl,基本上允许一个窗口位于另一个窗口的可视树中(在xaml中不允许)。它还公开了一个名为isdisplays的布尔型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>

通过所有这些,您可以得到一行代码来显示对话框。您遇到的问题是,仅使用上述代码无法真正关闭对话框。这就是为什么你必须在DisplayViewModel基类中放入一个事件,而不是上面的代码,写这个

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

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

这可能看起来有点复杂,但一旦奠定了基础,就非常简单了。同样,这是我的实现,我相信还有其他人:)

希望这能帮到你,它救了我。

虽然这并没有回答如何通过视图模型做到这一点的问题,但它确实展示了如何仅使用XAML +混合SDK来做到这一点。

我选择从Blend SDK下载并使用两个文件,这两个文件都可以通过NuGet从微软获得。这些文件是:

system . windows . interactive .dll和Microsoft.Expression.Interactions.dll

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行为,你可以使用IsDefault和IsCancel属性,只要窗口显示为w/ window . showdialog()。 我个人有问题w/一个按钮,有IsDefault属性设置为true,但它是隐藏的页面加载时。在显示后,它似乎不想很好地播放,所以我只是设置窗口。如上所示的dialgresult属性,它为我工作。

好吧,这个问题已经提了将近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快速入门的文章并运行它,它真的很容易理解,只是稍微啰嗦一点,使它工作,但好处比关闭一个窗口更大。