65.9K
CodeProject 正在变化。 阅读更多。
Home

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

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2011 年 3 月 25 日

CPOL

3分钟阅读

viewsIcon

12055

从ViewModel打开窗口、对话框或消息框

假设一个视图模型属于应用程序层,而应用程序层不引用上层,并且不能创建或使用可视化对象,那么如何基于视图或视图模型触发的一些逻辑,动态打开窗口、对话框或任何类型的消息框呢?

嗯,有很多方法可以做到这一点,一种是使用某种封装了该功能的Service,提供一个接口,这样视图模型实际上就不会直接与上层或WPF交互。

这个解决方案有些问题,因为该Service应该在表示层实现,并且应该以某种方式暴露给视图模型。

拥有DI容器是可行的,但仍然有点棘手。

我们还有其他选择吗?

假设你有一个视图模型,其中包含电子邮件消息的列表,并且该视图将其呈现为 ItemsControl。 现在,你希望在单独的窗口中显示电子邮件消息的详细信息,该窗口由以下方式触发:

  1. 视图 - 双击或选择一封电子邮件,然后按Enter键,就像Outlook一样。
  2. 视图模型 - 视图模型的属性发生更改,例如: ShowMessageDetails

在讨论我的解决方案之前,我想说一些关于这两种选择的事情

  • 当引发Routed事件或执行Routed命令时,由视图触发打开窗口非常简单,在这种情况下,你应该有一个OpenWindowAction,由事件或命令触发。
  • 基于属性更改由视图或视图模型触发打开窗口可能比较棘手,因为属性是一种状态,它可能与窗口状态同步:OpenedClosed。 在这种情况下,更改属性应该打开或关闭窗口,关闭窗口也应该更新属性。

考虑到窗口可能由视图或视图模型基于事件、命令或属性更改打开,我提出了两种选择:自定义操作和行为。

在这篇文章中,我将介绍自定义操作解决方案,在下一篇文章中,我将介绍行为解决方案,后者有点棘手。

假设你有:MessageListViewModel,用于电子邮件消息视图的MessageListView,以及MessageDetailsViewModelMessageDetailsView用于电子邮件详细信息视图,应该在窗口内呈现,比如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动作具有以下属性

  1. WindowUri – 指向定义我们窗口的XAML文件的简单Uri
  2. IsModaltrue 将窗口作为模态窗口打开,false 作为非模态窗口打开
  3. Owner – 我们的窗口的所有者,以防我们希望相对于所有者打开它,例如
  4. DataContext – 要在我们新窗口数据上下文中设置的视图模型,在我们的例子中,是通过MessageListViewModel.SelectedMessage检索的消息详细信息视图模型
  5. CloseCommand – 通知窗口即将关闭的命令,以及是否可以关闭

当然,你可以添加其他属性,例如:窗口的类型,而不是或除了 WindowUri之外,一个属性说明视图模型应该显示在弹出窗口中而不是窗口中,等等。

运行此示例并双击单个电子邮件,你将注意到以下内容

imageimage

现在让我们看一下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。 如果一切正常,窗口将关闭。

你可以从 这里 下载代码。

下次,我将展示如何实现打开窗口的行为。

© . All rights reserved.