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

使用 Prism 通过 TreeView 和 ToolBar 导航不同的模块

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2012年6月21日

CPOL

10分钟阅读

viewsIcon

29895

downloadIcon

2146

开发导航主题始于文章“使用 Prism 通过 TreeView 进行模块导航”。

动机

开发导航主题始于文章“使用 Prism 通过 TreeView 进行模块导航”。本次开发将扩展 TreeView,消除许多不一致和歧义。将改进功能。我们将添加新的解决方案。请尊重我找到合适解决方案的方式。其中一些是在撰写本文的过程中出现的。顺便说一句,这些解决方案是使用 Unity 2.0 容器研究的。在这里,我决定将其重构为 MEF。这部分我将使用 Visual Studio 2012 RC 进行检查。

你需要什么?

  • C# v 4、Xaml 知识。
  • PRISM 模型基础知识。
  • 耐心对待英语作者。

引言

准备一篇关于导航的文章,以免主题不明确,省略了很多功能,并使用了一些简化。最大的简化是缺乏数据呈现和对导航的影响。您将看到一些内置的 .Net Framework 命令,它们与 WPF 配合良好,并将继续沿用标准的 PRISM 架构。

开始工作

输入元素采用了我上一篇文章的草稿,将不再关注基础知识。我们将从基础设施开始。第一件重要的事情是创建和注册 ToolBar 的适配器类。Container 是 ToolBarTray ToolBar 控件,它允许完全利用 ToolBar 的功能。幸运的是,Prism 4 版本没有他们的资源适配器中包含该控件。适配器非常简单,并且基于 Prism 中标准适配器的原理。向项目 NavInfrastructure 添加一个新目录,名为 Prism。我创建了一个新的适配器,考虑到 MEF 版本与 Unity Container 的 ExportAttribute 不同,它需要进行装饰。只需创建一个新类,该类继承自特定适配器的装饰器,以指示适配器的类型——我在主类下方的同一文件中完成了这项工作。

using System;
using System.Collections.Specialized;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Practices.Prism.Regions;
 
namespace NavInfrastructure.Prism
{
    /// <summary>  
    /// Adapter that creates a new <see cref="AllActiveRegion"/> and binds all
    /// the views to the adapted <see cref="ToolBarTray"/>. 
    /// </summary>  
      public class ToolBarTrayRegionAdapter : RegionAdapterBase<ToolBarTray>    {
        public ToolBarTrayRegionAdapter(IRegionBehaviorFactory regionBehaviorFactory)
            : base(regionBehaviorFactory)
        {
        }
        //// Recomendate documentation PRISM Appendix E: Extending Prism 
        protected override void Adapt(IRegion region, ToolBarTray regionTarget)
        {
            // Correctly reference
            if (region == null) throw new ArgumentNullException("region");
            if (regionTarget == null) throw new ArgumentNullException("regionTarget"); 
            // Reaction of the changes field ToolBars in ToolBarTray
            region.Views.CollectionChanged += (sender, e) =>
            {
                switch (e.Action)
                {
                    case NotifyCollectionChangedAction.Add:
                        foreach (FrameworkElement element in e.NewItems)
                        {
                            regionTarget.ToolBars.Add(element as ToolBar);
                        }
                        break;
 
                    case NotifyCollectionChangedAction.Remove:
                        foreach (UIElement elementLoopVariable in e.OldItems)
                        {
                            var element = elementLoopVariable;
                            if (regionTarget.ToolBars.Contains(element))
                            {
                                regionTarget.ToolBars.Remove(element as ToolBar);
                            }
                        }
                        break;
                }
            };
        }
        protected override IRegion CreateRegion()
        {
            // Recomendate documentation PRISM Appendix E: Extending Prism 
            return new AllActiveRegion();
        }
    }
    /// <summary>    /// Version MEF as extension.
    /// </summary>    [Export(typeof(ToolBarTrayRegionAdapter))]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public class MefToolBarTrayRegionAdapter:ToolBarTrayRegionAdapter
    { 
        [ImportingConstructor]
        public MefToolBarTrayRegionAdapter(IRegionBehaviorFactory regionBehaviorFactory)
            :base(regionBehaviorFactory)
        {
        }
    }
}

不要忘记在 NavShell 项目的 Bootstrapper 类中注册适配器。我们在 ConfigureRegionAdapterMappings 方法中进行。打开 NavShell 项目的 Bootstrapper.cs 文件,就在我们下方写下类声明,Visual Studio 本身会显示我们可以覆盖的方法。我们选择 ConfigureRegionAdapterMappings。毕竟,该方法如下所示。

protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
        {
            // Mapping base regions adapters 
            RegionAdapterMappings regionAdapterMappings = base.ConfigureRegionAdapterMappings();
            // and custom adapters
            regionAdapterMappings.RegisterMapping(typeof(ToolBarTray), this.Container.GetExportedValue<toolbartrayregionadapter />());
            // Return all.
            return regionAdapterMappings;
        }

一旦我们注册了新的适配器。Prism 机制本身将自动将所有继承自 ToolBar 的类传输到由适配器支持的区域。为了确保,请重新生成解决方案。

区域适配器。

我们将添加一个需要解决的新区域。打开“NavShell.xaml”文件,并在屏幕截图中标出的红色箭头指示的位置添加更正。

我们添加行的定义

<RowDefinition Height="Auto" />

在每个 Grid.Row 附加一个框用于放置,添加第一个。

在 NameRegions 类项目 NavInfrastruktura 中,应创建一个名为新区域的新静态字段。

using System;
namespace NavInfrastructure
{
    public static class NameRegions
    {
        public static string NavigationTreeViewRegion = "NavigationTreeViewRegion";
        public static string MainRegion = "MainRegion";
        public static string ToolBarsRegion = "ToolBarsRegion";
    }
}

直接在新边框的定义下添加控件,并通过 Canvas 采用我们的适配器 ToolBarTray 控件。现在我们有

<local:RegionBorderControl Grid.Row="1"
                           Grid.Column="0"
                           Grid.ColumnSpan="3"
                           RegionName="ToolBars"
                           Style="{StaticResource RegionBorderControlStyle}">
    <ToolBarTray prism:RegionManager.RegionName="{x:Static infra:NameRegions.ToolBarsRegion}" />
</local:RegionBorderControl>

重新生成解决方案。

默认 ToolBar

时间是老虎最喜欢的东西。

那些使用 MVVM 模式的人经常会遇到 Command。我想在 .Net WPF ApplicationCommands 中使用的部分,并添加一些自己的命令,这些命令与现有命令结合起来,将为我们提供一套完整的、已准备好实施的框架,以及将在 MainToolBar MainMenuShell 和 ContextMenu 中执行的命令。为此,在 NavInfrastructure 项目中创建一个带有命令框架的静态类。

FileCommand 类

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
 
namespace NavInfrastructure
{
    /// <summary>
    /// My command class.
    /// Static class is more efficient and faster than instances of the class
    /// </summary>
    public static class FileCommand
    {
        // This constructor is executed when is registration library in the application.
        static FileCommand()
        {
            // Initialize command Send to mail.
            SendToMail = new RoutedUICommand(
                "SendToMail",
                "SendToMail",
                typeof(FileCommand),
                new InputGestureCollection { new KeyGesture(Key.E, ModifierKeys.Alt) });
            SaveAll = new RoutedUICommand(
                "SaveAll",
                "SaveAll",
                typeof(FileCommand),
                new InputGestureCollection { new KeyGesture(Key.D, ModifierKeys.Alt) });
            // You can use a version of ApplicationCommands but also requires implementation.
            Open = new RoutedUICommand(
                "Open",
                "Open",
                typeof(FileCommand),
                new InputGestureCollection { new KeyGesture(Key.O, ModifierKeys.Alt) });
        }
        /// <summary>
        /// This method binding commands with application.
        /// </summary>
        /// <param name="hostWindow"></param>
        public static void BindCommands(Window hostWindow)
        {
            if (hostWindow == null)
                return;
            hostWindow.CommandBindings.Add(new CommandBinding(SendToMail, OnSendToMailCommandExecuted, OnSendToMailCommandCanExecute));
            hostWindow.CommandBindings.Add(new CommandBinding(SaveAll, OnSaveAllCommandExecuted, OnSaveAllCommandCanExecute));
            hostWindow.CommandBindings.Add(new CommandBinding(Open, OnOpenCommandExecuted, OnOpenCommandCanExecute));
            hostWindow.CommandBindings.Add(new CommandBinding(ApplicationCommands.Save, OnSaveCommandExecuted, OnSaveCommandCanExecute));
            hostWindow.CommandBindings.Add(new CommandBinding(ApplicationCommands.Help, OnHelpCommandExecuted, OnHelpCommandCanExecute));
            hostWindow.CommandBindings.Add(new CommandBinding(ApplicationCommands.Properties, OnPropertiesCommandExecuted, OnPropertiesCommandCanExecute));
            hostWindow.CommandBindings.Add(new CommandBinding(ApplicationCommands.New, OnNewCommandExecuted, OnNewCommandCanExecute));
        }
 
        #region SendToMail
        public static RoutedUICommand SendToMail { get; private set; }
        static void OnSendToMailCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            // Top window
            Window wind = sender as Window;
            if (wind == null)
                wind = GetTopWindow(sender as DependencyObject);
            // Element with focus
            IInputElement f = FocusManager.GetFocusedElement(wind);
            // Indicate wherther is TextBoxBase derived.
            TextBoxBase fce = f as TextBoxBase;
            if (fce != null)
            {
                try
                {
                    MessageBox.Show("SendToMail Excuted!");
                }
                catch (Exception ex)
                {
                    throw new InvalidOperationException(message: ex.Message);
                }
            }
        }
        static void OnSendToMailCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            // Default value.
            e.CanExecute = false;
            // Top window.
            Window wind = sender as Window;
            if (wind == null)
                wind = GetTopWindow(sender as DependencyObject);
            // element with focus.
            IInputElement f = FocusManager.GetFocusedElement(wind);
            // Indicate wherther is TextBoxBase derived.
            TextBoxBase fce = f as TextBoxBase;
 
            // If is.
            if (fce != null)
            {
                // Wherther is enabled.
                e.CanExecute = fce.IsEnabled;
            }
        }
        #endregion
        #region New
        // Versions provided by the. NET does not require field only method of execution and approval for implementation.
        static void OnNewCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
 
            MessageBox.Show("New Excuted!");
 
        }
        static void OnNewCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }
        #endregion
        #region Open
        public static RoutedUICommand Open { get; private set; }
        static void OnOpenCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
 
            MessageBox.Show("Open Excuted!");
 
        }
        private static void OnOpenCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }
        #endregion
        #region Save
        // Versions provided by the. NET does not require field only method of execution and approval for implementation.
        static void OnSaveCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("Save Excuted!");
        }
        static void OnSaveCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }
 
        #endregion
        #region SaveAll
        public static RoutedUICommand SaveAll { get; private set; }
        private static void OnSaveAllCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("SaveAll Excuted!");
        }
        private static void OnSaveAllCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }
        #endregion
        #region Properties
        private static void OnPropertiesCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("Properties Excuted!");
        }
        private static void OnPropertiesCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }
        #endregion
        #region Help
        // Versions provided by the. NET does not require field only method of execution and approval for implementation.
        private static void OnHelpCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("Help Excuted!");
        }
        private static void OnHelpCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }
        #endregion
        #region Helpers
        private static Window GetTopWindow(DependencyObject windowChild)
        {
            var obj = VisualTreeHelper.GetParent(windowChild);
            Window wind = obj as Window;
            if (wind == null)
            {
                wind = GetTopWindow(obj);
            }
            return wind as Window;
        }
        #endregion
    }
}

这是该类的第一个概念版本。在接下来的部分中,我们将重构它。为了在静态构造函数执行的初始化机制中使用字段,当库注册到应用程序时,并设置静态字段的属性(类似于 DependecyProperty)。NavShell 项目的 Bootstrapper 类调用将我们链接到适当命令字段的方法。

Bootstrapper 类中绑定 FileCommand 的部分

protected override DependencyObject CreateShell()
 {
     // Use the container to create an instance of the shell.
     ShellView shell = this.Container.GetExportedValue<ShellView>();
     // Commands set
     FileCommand.BindCommands(shell);
     // Register MainToolBar - this we created.
     // In Unity – this.Container.TryResolve<IRegionManager> 
     var regionManager = this.Container.GetExportedValue<IRegionManager>();
     regionManager.RegisterViewWithRegion(NameRegions.ToolBarsRegion, 
     () =>
       /* In Unity –> this.Container.TryResolve<MainToolBar> */ 
       this.Container.GetExportedValue<MainToolBar>()); 
    // Display the shell's root visual.
    shell.Show();
    return shell;
}

代码行

FileCommand.BindCommands(shell)
从容器中获取与窗口相关的命令。下一条语句是获取我们 MainToolBar 区域实例的管理器的一部分。在此之前,我们必须创建它。

让我们添加我们的 ToolBar

<ToolBar x:Class="NavShell.Views.MainToolBar"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:infra="clr-namespace:NavInfrastructure;assembly=NavInfrastructure"
         mc:Ignorable="d">
<!-- Main toolbar-->
    <Button x:Name="newButton"
            Command="New">        
        <Image Source="/NavInfrastructure;component/Images/new.png" />
    </Button>
    <Button x:Name="openButton"
            Command="{x:Static infra:FileCommand.Open}">        
        <Image Source="/NavInfrastructure;component/Images/open.png" />
    </Button>
    <Button x:Name="saveButton"
            Command="Save">        
        <Image Source="/NavInfrastructure;component/Images/disk.png" />
    </Button>
    <Button x:Name="saveAllButton"
            Command="{x:Static infra:FileCommand.SaveAll}">        
        <Image Source="/NavInfrastructure;component/Images/disk_saveAlls.png" />
    </Button>
    <Button x:Name="printButton"
            Command="Print">
        <Image Source="/NavInfrastructure;component/Images/printer.png" />
    </Button>
    <Button x:Name="previewPrint"
            Command="PrintPreview"
            CommandParameter="NavShell.Views.PrintPreview">        
        <Image Source="/NavInfrastructure;component/Images/preview_print.png" />
    </Button>
    <Button x:Name="sendToMailButton"
            Command="{x:Static infra:FileCommand.SendToMail}">        
        <Image Source="/NavInfrastructure;component/Images/mail_send.png"
               Width="16"
               Height="16" />
    </Button>
    <Separator />
    <Button x:Name="undoButton"
            Command="Undo">        
        <Image Source="/NavInfrastructure;component/Images/undo.png" />
    </Button>
    <Button x:Name="redoButton"
            Command="Redo">
         <Image Source="/NavInfrastructure;component/Images/redo.png" />
    </Button>
    <Separator />
    <Button x:Name="cutButton"
            Command="Cut">
       <Image Source="/NavInfrastructure;component/Images/page_cut.png" />
    </Button>
    <Button x:Name="copyButton"
            Command="Copy">
        <Image Source="/NavInfrastructure;component/Images/page_copy.png" />
    </Button>
    <Button x:Name="pasteButton"
            Command="Paste">
        <Image Source="/NavInfrastructure;component/Images/page_paste.png" />
    </Button>
    <Button x:Name="deleteButton"
            Command="EditingCommands.Delete">
        <Image Source="/NavInfrastructure;component/Images/page_delete.png" />
    </Button>
    <Separator />
    <Button x:Name="propertiesButton"
            Command="Properties">
        <Image Source="/NavInfrastructure;component/Images/tool_option.png" />
    </Button>
</ToolBar>

尚未实现特定行为。但是,SendToMail 方法显示了如何根据应用程序所在的上下文来识别命令是否应该执行以及如何执行。为了在 Silverlight Button 调用中使用,请将调用更改为

 prism: Click.Command = ""
此外,Silverlight 不识别
{x: Static ...}
.

首先,检查执行是否正在寻找继承自 Window 类的控件,如果这样做,则使用

GetTopWindow(DependencyObject object)
辅助类将找到这个类。根据此信息,检索具有焦点的控件。在这种情况下,检查焦点是否是基于 TextBoxBase 的控件。

运行应用程序。F5。“是,是,是”。

SendToMail 已禁用。好的。

我们打开 Document1。我们将焦点放在文档内,然后检查 SendToMail 是否工作。它有效。还有与系统剪贴板配合工作的控件。

这与 Prism 有什么关系?

上一节中介绍的方法与 Prism 的理念完全相反,Prism 强制模块订阅全局命令的组成(CompositeCommand)。当然, nothing preclude 解决上述假设,包括在 Prism 中。我提出的概念指定了哪些类型的控件包含特定的数据,以及如何处理它们,Prism 假定模块负责处理它。

会是个好例子。

将展示如何打印文档。创建 PrintPreviewView。

重构 FileCommand。在空间中,我们向类添加一个新成员。

/// <summary>
/// The list of possible control types to print content.
/// </summary>
public static Type[] ListTypesPrint { get; set; }

在构造函数中,我们添加了一个控件列表,允许用户打印。

ListTypesPrint = new Type[8];
ListTypesPrint.SetValue(typeof(FlowDocumentScrollViewer), 0);
ListTypesPrint.SetValue(typeof(RichTextBox), 1);
ListTypesPrint.SetValue(typeof(FixedDocument), 2);
ListTypesPrint.SetValue(typeof(FlowDocumentReader), 3);
ListTypesPrint.SetValue(typeof(FlowDocumentPageViewer), 4);

我创建了一个静态字典列表,作为可以处理我们新功能的控件。当然,这可以通过私有修饰符隐式完成,但添加新控件到不同的列表。最好在 Bootstrapper 类的方法 CreateShell() 中完成,就在应用程序窗口与 FileCommand 类连接之后。我同意,这可以以多种其他方式完成。

列表中没有 FlowDocument 本身,因为 RichTextBox 控件的核心包含 FlowDocument。此示例还向我们展示了内置的 Print 命令不适用于 RichTextBox 控件。内置的 Print 命令仅适用于包含 IDocumentPaginatorSource Document 类型属性的控件。在我们的示例中,我们定义了可以打印预览的控件模式。

创建 PrintPreview。

为此,我们需要向 NavShell 项目添加对 PresentationUI 资源库的引用,该库应在此地址:

C:\Windows\assembly\GAC_MSIL\PresentationUI\3.0.0.0__31bf3856ad364e35\PresentationUI.dll

在 NavShell 项目的 View 目录中,我们将添加一个名为 PrintPreview.xaml 的新控件类型。在 xaml 文件中,插入以下代码:

<Window x:Class="NavShell.Views.PrintPreview"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:doc="clr-namespace:System.Windows.Documents;assembly=PresentationUI"
        xmlns:c="clr-namespace:NavInfrastructure;assembly=NavInfrastructure"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:media="clr-namespace:System.Windows.Media;assembly=PresentationCore"
        Title="Print Preview"
        Height="500"
        Width="600">
    <Window.CommandBindings>
        <CommandBinding Command="Print"
                        CanExecute="Print_CanExecute"
                        Executed="Print_Executed" />
        <CommandBinding Command="Close"
                        CanExecute="Close_CanExecute"
                        Executed="Close_Executed" />
    </Window.CommandBindings>
    <Window.Resources>
        <Style TargetType="{x:Type DocumentViewer}">
            <Setter Property="Foreground"
                    Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}" />
            <Setter Property="Background"
                    Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" />
            <Setter Property="FocusVisualStyle"
                    Value="{x:Null}" />
            <Setter Property="ContextMenu"
                    Value="{DynamicResource {ComponentResourceKey ResourceId=PUIDocumentViewerContextMenu, TypeInTargetAssembly={x:Type doc:PresentationUIStyleResources}}}" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type DocumentViewer}">
                        <Border Focusable="False"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}">
                            <Grid Background="{TemplateBinding Background}"
                                  KeyboardNavigation.TabNavigation="Local">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto" />
                                    <RowDefinition Height="*" />
                                </Grid.RowDefinitions>
                                <ToolBar>
                                    <Button Command="Close">
                                        <StackPanel Orientation="Horizontal">
                                            <ContentPresenter Content="Close"
                                                              Margin="0,0,3,0" />
                                            <TextBlock  Text=""
                                                        VerticalAlignment="Center" />
                                        </StackPanel>
                                    </Button>
                                </ToolBar>
                                <ContentControl Style="{DynamicResource {ComponentResourceKey ResourceId=PUIDocumentViewerToolBarStyleKey, TypeInTargetAssembly={x:Type doc:PresentationUIStyleResources}}}"
                                                TabIndex="0"
                                                Focusable="{TemplateBinding Focusable}"
                                                Grid.Column="1"
                                                Grid.Row="0"
                                                Height="26" />

                                <ScrollViewer x:Name="PART_ContentHost"
                                              IsTabStop="true"
                                              TabIndex="1"
                                              Focusable="{TemplateBinding Focusable}"
                                              Grid.Column="0"
                                              Grid.Row="1"
                                              Grid.ColumnSpan="2"
                                              CanContentScroll="true"
                                              HorizontalScrollBarVisibility="Auto" />
                                <DockPanel Grid.Row="1"
                                           Grid.ColumnSpan="2">
                                    <FrameworkElement Width="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}"
                                                      DockPanel.Dock="Right" />
                                    <Rectangle VerticalAlignment="top"
                                               Height="10"
                                               Visibility="Visible">
                                        <Rectangle.Fill>
                                            <LinearGradientBrush EndPoint="0,1"
                                                                 StartPoint="0,0">
                                                <LinearGradientBrush.GradientStops>
                                                    <GradientStopCollection>
                                                        <GradientStop Color="#66000000"
                                                                      Offset="0" />
                                                        <GradientStop Color="Transparent"
                                                                      Offset="1" />
                                                    </GradientStopCollection>
                                                </LinearGradientBrush.GradientStops>
                                            </LinearGradientBrush>
                                        </Rectangle.Fill>
                                    </Rectangle>
                                </DockPanel>
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    <DockPanel>
        <Menu DockPanel.Dock="Top">
            <MenuItem Header="File">
                <MenuItem Command="Print"
                          Header="Print"
                          InputGestureText="Ctrl+P" />
                <Separator />
                <MenuItem Command="Close"
                          Header="Close" />
            </MenuItem>
        </Menu>
        <DocumentViewer x:Name="documentViewer"
                        Zoom="{Binding Zoom, Converter={x:Static c:DoubleToPrecentConverter.Default}}" />
    </DockPanel>
</window />

PrintPreview.xaml.cs

using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using NavInfrastructure;

namespace NavShell.Views
{
    /// <summary>
    /// Interaction logic for PrintPreview.xaml
    /// </summary>
    public partial class PrintPreview : Window
    {
        FrameworkContentElement _document;
        public PrintPreview()
        {
            InitializeComponent();
        }
        public PrintPreview(FrameworkContentElement doc)
            : this()
            _document = doc;
            IDocumentPaginatorSource iPaginator = _document as FixedDocument;
            if (iPaginator == null) iPaginator = PrinterDocument.ShowPrintPreview(_document as FlowDocument);

            this.documentViewer.Document = iPaginator;
        }
        #region Handled events
        private void Print_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            PrinterDocument.PrintDocument(this.DataContext as FrameworkContentElement);
        }
        private void Print_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }
        private void Close_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }
        private void Close_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            this.Close();
        }
        #endregion
    }
}

为了将文档转换为 FlowDocument,我使用了一个名为 PrinterDocument 的静态类,它实现了 IDocumentPaginatorSource。将 ReachFramework 引用添加到 NavInfrastructure 项目。在 NavInfrastructure 项目中创建一个新文件,名为 PrinterDocument。

using System;
using System.IO;
using System.IO.Packaging;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Xps.Packaging;
using System.Windows.Xps.Serialization;

namespace NavInfrastructure
{
    public static class PrinterDocument
    {
        public static IDocumentPaginatorSource ShowPrintPreview(FlowDocument document)
        {
            // We have to clone the FlowDocument before we use different pagination settings for the export.        
            if (document == null) throw new ArgumentNullException("document");
            FlowDocument _doc = document;
            _doc.ColumnWidth = double.PositiveInfinity;
            // Create a package for the XPS document
            MemoryStream packageStream = new MemoryStream();
            // Create a XPS document with the path "pack://temp.xps"
            Uri uriPath = new Uri("pack://temp.xps", UriKind.Absolute);
            // Test whether is use the packet 
            var package = PackageStore.GetPackage(uriPath);
            if (package != null)
            {
                PackageStore.RemovePackage(uriPath);
            }
            package = Package.Open(packageStream, FileMode.Create, FileAccess.ReadWrite);
            PackageStore.AddPackage(uriPath, package);
            var xpsDocument = new XpsDocument(package, CompressionOption.SuperFast, uriPath.OriginalString);
            // Serialize the XPS document
            XpsSerializationManager serializer = new XpsSerializationManager(new XpsPackagingPolicy(xpsDocument), false);
            serializer.SaveAsXaml(((IDocumentPaginatorSource)_doc).DocumentPaginator);
            // Get the fixed document sequence
            return xpsDocument.GetFixedDocumentSequence();
        }
    }
}

这些过程,我们将文档流转换为 Fix。但这也不是其细节的场所。

我们回到 NavInfrastructure 项目和 FileCommand 文件。在下一步中,我们需要考虑如何确定具有焦点的多功能视图窗口是否可以包含在打印过程的审查和打印中(打印预览服务的结果与我们将文档转换为正确的打印文档的方式相同)。检查方法应如下所示:

static void OnPrintPreviewCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
 {
     // Default value.
     e.CanExecute = false;
     // Top window.
     Window wind = sender as Window;
     if (wind == null)
         wind = GetTopWindow(sender as DependencyObject);
     // element with focus.
     IInputElement f = FocusManager.GetFocusedElement(wind);

     if (f != null)
     {
         // Whether is on the list.
         e.CanExecute = ListTypesPrint.Contains(f.GetType());
     }
 }

我认为这不需要评论,只需检查您的应用程序是否包含在打印类型列表(ListTypesPrint)中的控件焦点。

然而,该函数使用的服务有点复杂。

static void OnPrintPreviewCommandExecuted(object sender, ExecutedRoutedEventArgs e)
 {
     // Top window
     Window wind = sender as Window;
     if (wind == null)
         wind = GetTopWindow(sender as DependencyObject);
     // Element with focus
     IInputElement f = FocusManager.GetFocusedElement(wind);
     // Wherther is xxx.Document control on the list.
     // Used new datatypes dynamic.
     dynamic ctrl = f;
     var jest = ListTypesPrint.FirstOrDefault(f1 => f1 == f.GetType());
     if (jest != null)
     {
         FrameworkContentElement fce = ctrl.Document;
         if (fce != null)
         {
             try
             {
                var pp = Activator.CreateInstance(AppDomain.CurrentDomain,
                    Assembly.GetAssembly(sender.GetType()).FullName,
                    e.Parameter.ToString(),
                    false,
                    BindingFlags.Public | BindingFlags.Instance | BindingFlags.CreateInstance,
                    null,
                    new Object[] { fce },
                    null,
                    null);
                Window printWindow = (Window)pp.Unwrap();
                printWindow.ShowDialog();
                
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException(message: ex.Message);
            }
        }
    }
}

在初始阶段,应用程序窗口提供对焦点控件的洞察。我们再次检查它是否在列表中。接下来,使用语言的最新可能性(动态变量)从控件的文档属性中获取文档。验证获得的值。使用 Activator 监视模式创建服务,该服务作为参数从工具栏中的命令(CommandParameter = "NavShell.Views.PrintPreview"),即 e.Parameter.ToString() 或服务类名传递。现在只需启动服务窗口。在打印所有与 WPF 和所有 .NET 文档相关的内容之前,我们都进行了查找。这就是我们这里的全部重点。我们不依赖于模块及其视图的实现。但它很漂亮。

CompositeCommand 和 FileCommand

回到要求中包含的概念,并指出 Prism 模块 CompositeCommand 记录了命令。我将尝试提供 CloseAllDocuments 的示例。在 NavInfrastructure 项目中,我创建一个名为 GlobalCommands 的类。

using Microsoft.Practices.Prism.Commands;
namespace NavInfrastructure
{
   public static class GlobalCommand
    {
       public static CompositeCommand CloseAllDocumets = new CompositeCommand();
    }
}

在 ShellView.xml 文件中添加按钮。

<StackPanel Canvas.Top="75"
            Canvas.Left="15"
            Orientation="Horizontal"
            Width="Auto">
    <!-- Version for command in VM -->
    <Button Content="Close all view"
            prism:Click.Command="{x:Static infra:GlobalCommand.CloseAllDocumets}"
            Style="{DynamicResource MenuButtonStyle}"
            HorizontalContentAlignment="Stretch"
            VerticalContentAlignment="Stretch" /> 
    <Button Content="Exit"
            prism:Click.Command="{Binding ExitCommand}"
            Style="{DynamicResource MenuButtonStyle}"
            HorizontalContentAlignment="Stretch"
            VerticalContentAlignment="Stretch" />
</StackPanel>

将上一篇文章中的 ViewModelBase 类重构为如下形式:

using System.Linq;
using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.Regions;
using Microsoft.Practices.Prism.ViewModel;
using Microsoft.Practices.ServiceLocation;

namespace NavInfrastructure
{
    public abstract class ViewModelBase : NotificationObject
    {
        protected ViewModelBase()
        {
            GlobalCommand.CloseAllDocumets.RegisterCommand(KillingCommand);
        }
        #region Save behavior
        private bool _idDirty = false;
        /// <summary />
        /// Detecting changes in document.
        /// </summary />
        public bool IsDirty
        {
            get { return _idDirty; }
            set
            {
                if (_idDirty == value)
                {
                    _idDirty = value;
                    this.RaisePropertyChanged("IsDirty");
                }
            }
        }
        #endregion
        #region Killing behavior
        public virtual DelegateCommand KillingCommand
        {
            get { return _killingCommand ?? (_killingCommand = new DelegateCommand(KillView)); }
            set { _killingCommand = value; }
        }
        private DelegateCommand _killingCommand;

        protected bool cancel = false;

        public virtual void KillView()
        {
            // Arbitrary Container MEF or Unity.
            IRegionManager manager = ServiceLocator.Current.GetInstance<iregionmanager />();
            // Find current view
            var objectView = manager.Regions[NameRegions.MainRegion].Views.Where(f => ((System.Windows.FrameworkElement)f).DataContext == this).SingleOrDefault();
            if (this.IsDirty) {
                // Confirm close or cancel closing.
                cancel = true;// cancel closing
                // cancel = false // confirm close
            } 
            if (!cancel && objectView != null)
            {
                GlobalCommand.CloseAllDocumets.UnregisterCommand(KillingCommand);
                // Remove finding view.
                manager.Regions[NameRegions.MainRegion].Remove(objectView);
            }
        }
        #endregion
    }
}

主要变化是放弃了从视图传递属性的方式,这样只会依赖于自动检测。另一个变化是添加了一个受保护的构造函数,它会自动将我们的类捕获到 GlobalCommand.CloseAllDocuments 中。KillView 注册程序会从 CompositeCommand 中移除我们的类,移除对 ViewModelBase 的所有引用,以便 GC 可以移除对象。我们添加了一个功能,允许 IsDirty 查询类或文档是否已更改。众所周知的从上一篇文章的变化也未被错过,即 DocumentViewModel 和 CatalogViewModel。

using System;
using System.ComponentModel.Composition;
using System.Linq;
using Microsoft.Practices.Prism.Regions;
using NavInfrastructure;
using NavModule_One.Models;

namespace NavModule_One.ViewModels
{
    /// <summary>
    /// ViewModel class's Document.
    /// </summary>
    [Export]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public class DocumentViewModel : ViewModelBase, IConfirmNavigationRequest
    {
        #region Private Field
        [Import]
        CacheManager _cacheManager;
        #endregion

        #region .ctor

        public DocumentViewModel()
        { }

        #endregion

        #region Property CurrentItem
        /// <summary>
        /// Current base item.
        /// </summary>
        public EntityBase CurrentItem
        {
            get { return _currentItem; }
            private set
            {
                if (value != _currentItem)
                {
                    _currentItem = value;
                    this.RaisePropertyChanged("CurrentItem");
                }
            }
        }
        private EntityBase _currentItem;
        #endregion

        #region IConfirmNavigationRequest Members
        /// <summary>
        /// Implementation confirm.
        /// </summary>
        /// <param name="navigationContext"></param>
        /// <param name="continuationCallback"></param>
        public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
        {
                continuationCallback(cancel);
        }

        #endregion

        #region INavigationAware Members

        public bool IsNavigationTarget(NavigationContext navigationContext)
        {
            return CurrentItem.IDEntity.ToString() == navigationContext.Parameters["ParentId"];
        }

        public void OnNavigatedFrom(NavigationContext navigationContext)
        {
        }

        public void OnNavigatedTo(NavigationContext navigationContext)
        {
            // Verify _cacheManager
            if (_cacheManager == null) throw new CompositionException("Brak modułu CacheManager");
            var idEntity = int.Parse(navigationContext.Parameters["ParentId"]);
            this.CurrentItem = _cacheManager.CacheDocuments.Single(i => i.IDEntity == idEntity);
        }
        #endregion
    }
}

CatalogViewModel 类现在如下所示:

using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using System.Linq;
using Microsoft.Practices.Prism.Regions;
using NavInfrastructure;
using NavModule_One.Models;

namespace NavModule_One.ViewModels
{
    [Export]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public class CatalogViewModel : ViewModelBase, INavigationAware
    {
        #region Private Field
        // Assing Cache - devlop EnterpraceLibrary
        [Import]
        CacheManager _cacheManager;
        #endregion
        #region .ctor
        public CatalogViewModel()
        {
            // fire ctor in Base class.
            // and assing GlobalCommand as CompositeCommand.
        }
        #endregion
        #region Property CurrentItem
        /// <summary>
        /// Current base item.
        /// </summary>
        public EntityBase CurrentItem
        {
            get { return _currentItem; }
            private set
            {
                if (value != _currentItem)
                {
                    _currentItem = value;
                    this.RaisePropertyChanged("CurrentItem");
                }
            }
        }
        private EntityBase _currentItem;
        #endregion
        #region Property CatalogItems
        private ObservableCollection<EntityBase> _catalogItems;
        public ObservableCollection<EntityBase> CatalogItems // 
        {
            get { return _catalogItems ?? (_catalogItems = new ObservableCollection<EntityBase>()); }
            set
            {
                _catalogItems = value;
                RaisePropertyChanged("CatalogItems");
            }
        }
        #endregion
        #region INavigationAware Members
        public bool IsNavigationTarget(NavigationContext navigationContext)
        {
            // Only one view.
            return true;
        }
        public void OnNavigatedFrom(NavigationContext navigationContext)
        {
        }
        public void OnNavigatedTo(NavigationContext navigationContext)
        {
         // Verify _cacheManager
            if (_cacheManager == null) throw new CompositionException("Brak modułu CacheManager");
            var idEntity = int.Parse(navigationContext.Parameters["ParentId"]);
            this.CurrentItem = _cacheManager.CacheDocuments.Single(i => i.IDEntity == idEntity);
            PopulateViewModel();
        }
        #endregion
        #region Help method
        private void PopulateViewModel()
        {
            CatalogItems.Clear();
            foreach (var i in _cacheManager.CacheDocuments.Where(i => i.Parent != null && i.Parent.IDEntity == _currentItem.IDEntity))
            {
                CatalogItems.Add(i);
            }
        }
        #endregion
    }
}

模块内容和结构知识。

一个很好的例子是 Visual Studio 及其基础模块(解决方案中的项目)。概念是这样的:Solution 或 Root 只知道解决方案中包含的项目,而不知道它们的内容。这就是我们所说的。事实证明,ModuleCatalog.xaml 文件具有这样的知识,而应用程序的主调(Shell),在此情况下是“NavShell”。继续这条线,我们发现每个项目都关心自己的知识。它有自己的知识存储,并将其放入 XML 文件中。结合起来,我们继续分析每个文档并确定其命运,分配图标,下载属性等。下一篇文章将展示我如何关心关闭文档视图而不是实现 ViewModelBase 的独立模块以及未应用 MVVM 模式的模块。关于这个以及添加菜单、添加专用模块的工具栏、提供数据以及其他一些小事情,我将在下一篇文章中写。我相信即使一个人也学到了一些东西。

摘要

我试图分享我所知道的关于 Prism 的知识以及我如何使用它来解决这个模型的各种问题。

已知问题

与上一篇文章一样,我注意到导航中控件键有问题。原因是 Light 不显示为单个结构。这可以通过将资源移动到独立文件并将其链接到主应用程序来解决,这会导致代码产生很大的依赖性。此问题仅适用于 Module_One 解决方案使用的方法。Module_two 模块及其方法缺少键控件,但我提到的问题是关于根样式同化的。树仍然有正确的样式。这是由无法链接模块启动资源并将其分配给控件引起的。每个级别都根据主应用程序 Resources 中包含的样式自动进行。

很明显,这个应用程序还有很多不足之处,但正如波兰谚语所说,“千里之行,始于足下”。

此致 Andrzej Skutnik。

© . All rights reserved.