我试图学习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行事件、命令和行为只是为了避免以“模式”和“纯粹”为名的一行方法有点荒谬的人....
供参考,我遇到了同样的问题,我想我找到了一个不需要全局变量或静态变量的工作,尽管它可能不是最好的答案。我让你们自己决定。
在我的例子中,实例化要显示的窗口的ViewModel(让我们称之为ViewModelMain)也知道LoginFormViewModel(使用上面的情况作为例子)。
所以我所做的是在LoginFormViewModel上创建一个属性,它的类型是ICommand(让我们称之为CloseWindowCommand)。然后,在窗口上调用. showdialog()之前,我将LoginFormViewModel上的CloseWindowCommand属性设置为我实例化的窗口的Window . close()方法。然后在LoginFormViewModel中,我所要做的就是调用CloseWindowCommand.Execute()来关闭窗口。
我认为这是一种变通/hack,但它在没有真正打破MVVM模式的情况下工作得很好。
你可以随意批评这个过程,我可以接受!:)
虽然这并没有回答如何通过视图模型做到这一点的问题,但它确实展示了如何仅使用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属性,它为我工作。
我实现了Joe White的解决方案,但遇到了偶尔出现的“只有在创建Window并显示为对话框后才能设置dialgresult”错误。
在视图关闭后,我一直保持ViewModel,偶尔我也会使用相同的VM打开一个新的视图。看起来,在旧视图被垃圾收集之前关闭新视图会导致dialgresultchanged试图在关闭的窗口上设置dialgresult属性,从而引发错误。
我的解决方案是改变dialgresultchanged来检查窗口的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?;
}
做出此更改后,关闭对话框的任何附件都将被忽略。