我试图学习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添加一个事件,并指示窗口在该事件触发时关闭自己。
要了解更多细节,请参阅我的博客文章,从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实现。
您可以使用这个官方包中的行为。
我读了所有的答案,但我必须说,他们中的大多数都不够好,甚至更糟。
你可以用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必须有更多的选项来配置对话框,例如他们应该执行的按钮和命令。有不同的方式这样做,但它超出了范围:)
好吧,这个问题已经提了将近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快速入门的文章并运行它,它真的很容易理解,只是稍微啰嗦一点,使它工作,但好处比关闭一个窗口更大。