动态窗口与消息框的实现策略

WPF应用程序开发中,经常需要根据视图或视图模型的逻辑动态地打开窗口、对话框或消息框。但是,视图模型不应该直接创建或使用视觉对象,这就需要寻找其他解决方案。一种方法是使用服务来封装这些操作,提供一个接口,使得视图模型不直接与上层或WPF交互。然而,这种方法存在一定的问题,因为服务需要在表示层实现,并且需要以某种方式暴露给视图模型。虽然可以使用依赖注入容器来实现,但这种方法仍然有些棘手。那么,还有其他的选择吗?

假设有一个视图模型,其中包含一系列电子邮件消息,视图将其渲染为一个ItemsControl。现在,希望在触发某些操作时,比如双击邮件或按下Enter键(就像Outlook那样),在单独的窗口中显示邮件消息的详细信息。视图模型的某个属性变化时,比如ShowMessageDetails属性变化,也会触发这一操作。

在讨论解决方案之前,想先谈谈这两种触发方式:一种是通过视图触发窗口打开,另一种是通过视图模型的属性变化触发。对于第一种方式,当路由事件被触发或路由命令被执行时,打开窗口相对简单。在这种情况下,可以有一个OpenWindowAction,由事件或命令触发。对于第二种方式,由于属性是一个状态,并且可能与窗口状态同步(打开或关闭),因此可能会更加复杂。在这种情况下,改变属性应该打开或关闭窗口,同时关闭窗口也应该更新属性。

考虑到窗口可能由视图或视图模型基于事件、命令或属性变化打开,提出了两种解决方案:自定义动作和行为。在这篇文章中,将介绍自定义动作的解决方案,而在下一篇文章中,将介绍行为解决方案,后者稍微复杂一些。

假设有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行,将DataGrid与一个Blend触发器关联,监听MouseDoubleClick路由事件。每当此事件被触发时,就会调用自定义的OpenWindowAction,它目前负责显示窗口。

OpenWindowAction动作具有以下属性:

  • WindowUri – 一个简单的Uri指向定义窗口的XAML文件
  • IsModal – 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; } } }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485