从ViewModel打开窗口、对话框或消息框 – 第1部分





0/5 (0投票)
从ViewModel打开窗口、对话框或消息框
假设一个视图模型属于应用程序层,而应用程序层不引用上层,并且不能创建或使用可视化对象,那么如何基于视图或视图模型触发的一些逻辑,动态打开窗口、对话框或任何类型的消息框呢?
嗯,有很多方法可以做到这一点,一种是使用某种封装了该功能的Service,提供一个接口,这样视图模型实际上就不会直接与上层或WPF交互。
这个解决方案有些问题,因为该Service应该在表示层实现,并且应该以某种方式暴露给视图模型。
拥有DI容器是可行的,但仍然有点棘手。
我们还有其他选择吗?
假设你有一个视图模型,其中包含电子邮件消息的列表,并且该视图将其呈现为 ItemsControl
。 现在,你希望在单独的窗口中显示电子邮件消息的详细信息,该窗口由以下方式触发:
- 视图 - 双击或选择一封电子邮件,然后按Enter键,就像Outlook一样。
- 视图模型 - 视图模型的属性发生更改,例如:
ShowMessageDetails
。
在讨论我的解决方案之前,我想说一些关于这两种选择的事情
- 当引发
Routed
事件或执行Routed
命令时,由视图触发打开窗口非常简单,在这种情况下,你应该有一个OpenWindowAction
,由事件或命令触发。 - 基于属性更改由视图或视图模型触发打开窗口可能比较棘手,因为属性是一种状态,它可能与窗口状态同步:
Opened
或Closed
。 在这种情况下,更改属性应该打开或关闭窗口,关闭窗口也应该更新属性。
考虑到窗口可能由视图或视图模型基于事件、命令或属性更改打开,我提出了两种选择:自定义操作和行为。
在这篇文章中,我将介绍自定义操作解决方案,在下一篇文章中,我将介绍行为解决方案,后者有点棘手。
假设你有:MessageListViewModel
,用于电子邮件消息视图的MessageListView
,以及MessageDetailsViewModel
,MessageDetailsView
用于电子邮件详细信息视图,应该在窗口内呈现,比如MessageDetailsDialog
。
在MessageListViewModel
中有一个SelectedMessage
属性,让我们看一下MessageListView
<UserControl x:Class="WPFOutlook.PresentationLayer.Views.MessageListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewmodels="http://schemas.sela.co.il/advancedwpf"
xmlns:views="clr-namespace:WPFOutlook.PresentationLayer.Views"
xmlns:behaviors="clr-namespace:WPFOutlook.PresentationLayer.Behaviors"
xmlns:i="clr-namespace:System.Windows.Interactivity;
assembly=System.Windows.Interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<viewmodels:MessageListViewModel />
</UserControl.DataContext>
<Grid>
<DataGrid ItemsSource="{Binding Messages}"
SelectedItem="{Binding SelectedMessage}"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="From"
Binding="{Binding From}" IsReadOnly="True" />
<DataGridTextColumn Header="Subject"
Binding="{Binding Subject}" IsReadOnly="True" />
<DataGridTextColumn Header="Received"
Binding="{Binding Received}" IsReadOnly="True" />
<DataGridTextColumn Header="Size"
Binding="{Binding Size}" IsReadOnly="True" />
</DataGrid.Columns>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<behaviors:OpenWindowAction WindowUri="/Dialogs/MessageDetailsDialog.xaml"
IsModal="True"
Owner="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}}}"
DataContext="{Binding SelectedMessage}"
CloseCommand="{Binding CloseMessageDetailsCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
</Grid>
</UserControl>
正如你在第30行所看到的,我使用Blend
触发器将DataGrid
附加到MouseDoubleClick
路由事件。 每当引发此事件时,就会调用自定义的OpenWindowAction
,它当前显示我们的窗口。
OpenWindowAction
动作具有以下属性
WindowUri
– 指向定义我们窗口的XAML文件的简单UriIsModal
–true
将窗口作为模态窗口打开,false
作为非模态窗口打开Owner
– 我们的窗口的所有者,以防我们希望相对于所有者打开它,例如DataContext
– 要在我们新窗口数据上下文中设置的视图模型,在我们的例子中,是通过MessageListViewModel.SelectedMessage
检索的消息详细信息视图模型CloseCommand
– 通知窗口即将关闭的命令,以及是否可以关闭
当然,你可以添加其他属性,例如:窗口的类型,而不是或除了 WindowUri
之外,一个属性说明视图模型应该显示在弹出窗口中而不是窗口中,等等。
运行此示例并双击单个电子邮件,你将注意到以下内容
现在让我们看一下OpenWindowAction
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Interactivity;
using System.ComponentModel;
using System.Windows.Input;
using WPFOutlook.ApplicationLayer.Common;
using System.Windows.Threading;
using System.Diagnostics;
namespace WPFOutlook.PresentationLayer.Behaviors
{
public class OpenWindowAction : TriggerAction<DependencyObject>
{
protected override void Invoke(object parameter)
{
Assert(CloseCommandProperty.Name, CloseCommand, null);
Assert(WindowUriProperty.Name, WindowUri, null);
if (DataContext == null)
{
return;
}
if (_isOpen)
{
return;
}
var window = (Window)Application.LoadComponent(WindowUri);
window.Owner = Owner;
window.DataContext = DataContext;
window.Closing += window_Closing;
if (IsModal)
{
window.Show();
}
else
{
window.ShowDialog();
}
_isOpen = true;
}
private void window_Closing(object sender, CancelEventArgs e)
{
var window = sender as Window;
bool canClose = CloseCommand.CanExecute(window.DialogResult);
if (canClose)
{
CloseCommand.Execute(window.DialogResult);
_isOpen = false;
}
e.Cancel = !canClose;
}
}
}
正如你所看到的,Invoke
重写检查属性,从给定的窗口URI创建一个窗口,然后显示该窗口。
每当窗口尝试关闭时,都会通过调用CanExecute
方法来查询CloseCommand
。 如果一切正常,窗口将关闭。
你可以从 这里 下载代码。
下次,我将展示如何实现打开窗口的行为。