UML软件工程组织

在 Longhorn 道路上跨出新的一步
转自:www.microsoft.com 作者:Chris Sells
摘要:Chris Sells 在基于 Longhorn 生成其 Solitaire 应用程序的后一部分时,探讨了 Avalon 的五个主要元素系列。下载 LonghornSolitaire5.msi 示例文件

请回忆一下在上一篇文章中,我正在身陷于构建“Longhorn”的 Solitaire(纸牌)实现。当我们最后结束时,lhsol 示例应用程序令人感到非常无聊 — 它仅仅包含一个空的主窗口。然而,我们确实明白了基本的“Avalon”Plumbing(管道配置)如何工作,以及如何使用 XAML 来声明主窗口和应用程序类。XAML 的声明性特性使我们可以专注于我们的目标,而让工具和基础结构决定如何让我们达到目标。

代码和 XAML 的拆分形式如下所示:

<!-- MyApp.xaml -->
<Application
  xmlns=http://schemas.microsoft.com/2003/xaml
  xmlns:def="Definition"
  def:Class="LonghornSolitaire.MyApp"
  def:CodeBehind="MyApp.xaml.cs"
  StartingUp="AppStartingUp">
</Application>
// MyApp.xaml.cs
using MSAvalon.Windows;
namespace LonghornSolitaire {
  public partial class MyApp : Application {
    void AppStartingUp(object sender, StartingUpCancelEventArgs e) {
      Window mainWindow = new MainWindow();
    }
  }
}
<!-- Window1.xaml -->
<Window  
    xmlns="http://schemas.microsoft.com/2003/xaml"
    xmlns:def="Definition"
    def:Class="LonghornSolitaire.MainWindow"
    def:CodeBehind="Window1.xaml.cs"
    Text="Solitaire"
    Visible="True">
</Window>
// Window1.xaml.cs
using System;
using MSAvalon.Windows;
namespace LonghornSolitaire {
  public partial class MainWindow : Window {
  }
}

我们使用在 Longhorn 上运行的 Visual Studio 将这四个文件捆绑在一起,以创建项目和解决方案。我们还享受了使用 MSBuild 从命令行(最近它对于我已经变得非常重要,稍后我会加以介绍)生成该项目的自由。

在这一部分中,我们会了解如何生成某种类似于 Solitaire 的东西,如图 1 所示

foghorn04142004-fig01.gif

图 1. 某种类似于 Solitaire 的东西

Avalon 元素

在我们进一步讨论如何生成 lhsol 之前,我们需要了解更多关于 Avalon 中提供的生成块的信息。Avalon 具有五个元素系列,如图 2 所示。

foghorn04142004-fig02

图 2. Avalon 的五个元素系列

请注意图 2 中部的线,它将 Presentation Core 与 Presentation Framework 拆分开。请回忆一下在上一部分中,这些也是 Avalon .NET 程序集、PresentationCore.dll 和 PresentationFramework.dll 的名称。Presentation Core 表示 Avalon 的最低级别,它提供了一组服务(如,布局、输入、焦点、事件处理和媒体等),在它们的基础上可以生成较高级别的 Presentation Framework。因为 Presentation Core 和 Framework 之间的拆分很清楚,所以如果您断定较高级别的结构不符合您的需要,Avalon 允许您深入到较低级别并利用 Core 服务,而无须从头开始。另一方面,您很可能将大部分时间花费在 Framework 中的类上,因为它们将 Core 中提供的服务组合在精密的小型代码块中,而这些代码块可能完成了您希望完成的工作。

控件

Avalon Presentation Framework 中的第一个元素系列 — 控件系列,提供了将由容器承载的、可重用的 UI 块区和行为。例如,MSAvalon.Windows.Controls 命名空间容纳了长期以来广受欢迎的控件(如 ButtonListBoxTextBox)以及一些即将流行的控件(如 AudioVideoCanvas)。在这一阶段,您可能开始想念 ToolBarTree 等控件,但请您放心,它们不久就会问世。

这一控件元素系列正是我开始生成 lhsol 的起点。我的想法是将主要的新 UI 概念 — 纸牌堆,绑定到一个控件,然后在 lhsol 主窗口上的所有位置使用该控件,这些位置包括平局牌堆、垫牌堆、四个花色相同的牌堆和七个颜色交替变化的牌堆。我考虑使用一堆知道如何以适合于 Solitaire 的方式显示自身的纸牌来充当大量自定义的 UI。

在 Avalon 中生成自定义控件的方式与在 Windows 窗体中完全相同。您可以选取与您所期望的工作方式几乎完全相同的控件,然后从该控件进行派生。因为我希望 PileOfCards 控件只充当具有可变数量的 Card 控件的容器,所以我选择让我的控件成为 Canvas 控件的子类,生成它的目的是为了包含和排列 Card 子控件:

// PileOfCards.xaml.cs
namespace CardControls {
  public partial class PileOfCards : Canvas {... }
}

注意关键字 Partial。尽管我可以将 Pilefards 逻辑完全放到 # 文件中,但我选择了将它拆分到 # 和 ML 两个文件中。

<Canvas
  xmlns="http://schemas.microsoft.com/2003/xaml"
  xmlns:def="Definition"
  def:Class="CardControls.PileOfCards"
  def:CodeBehind="PileOfCards.xaml.cs">
    <Border
      BorderBrush="red"
      BorderThickness="2"
      Width="100%"
      Height="100%">
      <ListBox ID="cardList" />
    </Border>
</Canvas>

该 XAML 定义了自定义类的一个实例,该类派生于 Canvas,它映射到 PileOfCards.xaml.cs 中的 PileOfCards 类。同时,请注意我使用了一个红色的边框和一个列表框,以便容纳堆中的纸牌序列。这最终将由 Card 子控件取代,但是该列表框是一个很有用的助手,可以在我进行开发时显示牌堆的内容。

PDC Visual Studio 中的自定义控件

如果您愿意做我做过的工作,以便在 Visual Studio 的 PDC 版本中创建自定义控件,您必须确实 希望做这件事情。例如,尽管添加类或 XAML 文件会很容易,如果您需要图 3 中排列的那些美妙的 XAML 和代码隐藏文件,您将必须添加图 4 中所示的某个 Avalon 项目项模板。您具体选择哪一个模板并不重要,但请确保使用您喜欢文件名,因为更改文件名会搞乱与该版本中的代码隐藏文件之间的关联。

foghorn04142004-fig03

图 3. 正确关联的 XAML 和代码隐藏文件

foghorn04142004-fig04

图 4. Avalon 项目项模板

但是,使 XAML 和代码隐藏文件协同工作非常容易。难点在于将它们放在包含 XAML 文件中的什么位置以及如何引用它们。对于“将它们放在什么位置”的问题,必须将当前要在 XAML 文件中引用的自定义控件放在单独的项目中。问题在于 XAML 从已编译的程序集中获取类型信息,因此您将必须在包含项目中添加对自定义控件项目的引用。在该情况下,这意味着将 PileOfCards 控件放在一个项目中,然后从 lhsol 项目引用该项目。

以此说明为前提,XAML 处理器在处理 XAML 时需要了解自定义控件。要为 XAML 处理器提供该信息,您需要在引用您的自定义控件的每个 XAML 文件的顶部,放置一条类似于以下内容的 XML 处理指令:

<?Mapping XmlNamespace="XN" ClrNamespace="CN" Assembly="A" ?>

从右边开始向左观察,Mapping PI(XML 知识界为 XML 处理指令所起的名称)的 Assembly 属性引用包含自定义类型的 .NET 程序集。ClrNamespace 引用包含自定义控件的程序集内部的命名空间。XmlNamespace 引用的内容供您在 XML 中使用,以便定义用来创建控件实例的命名空间前缀。最后一点有一些复杂,因为您不能将 XmlNamespace 直接用作 XML 命名空间前缀,而必须将其映射为 XML 命名空间前缀,如下所示:

<!-- Window1.xaml -->
<?Mapping XmlNamespace="XN" ClrNamespace="CN" Assembly="A" ?>
<Window xmlns:xn="XN" ... >
  <xn:MyClass ... />
</Window>

因此,在该示例中,Window1.xaml 文件中的 xn:MyClass 元素映射到 A 程序集的 CN 命名空间中的类 MyClass,如图 5 所示。

foghorn04142004-fig05

图 5. XAML <Mapping> 指令的 .NET 程序集和类映射

有关更具体的示例,请回忆一下 CardControls 命名空间中的 PileOfCards 声明:

namespace CardControls {
  public partial class PileOfCards : Canvas {... }
}

因为在程序集中声明的类 PileOfCards 也称为 CardControls,我们可以按如下方式在 XAML 中创建它的实例:

<!-- Window1.xaml -->
<?Mapping
  XmlNamespace="CardControls"
  ClrNamespace="CardControls" Assembly="CardControls" ?>
<Window xmlns:cc="CardControls" ... >
  <cc:PileOfCards ID="drawPile" />
  ...
</Window>

最后一个难点在于,当您在 XAML 中引用控件时,Visual Studio 的 PDC 版本的生成过程存在一个问题。该问题是,一旦将自定义控件程序集引入 Visual Studio 过程,将不会释放它,这意味着任何包含该自定义控件项目的解决方案都无法重新生成,并且文件将被锁定。我绕过该问题的方法是使用 Visual Studio 来管理我的解决方案和项目,然后每当我进行更改以便将解决方案和项目文件刷新到磁盘时,都要关闭该解决方案。当磁盘上的解决方案和项目文件满足您的要求时,您可以使用 MSBuild 来生成该解决方案,如下所示:

C:\> msbuild.exe /p:Configuration=Debug /q LonghornSolitaire.sln

让 Visual Studio 与 MSBuild 共享生成系统的优点在于,即使 Visual Studio 正在使解决方案和项目文件保持打开状态,MSBuild 仍然能够读取它们以便生成。在此情况下,我们要生成 LonghornSolitaire 解决方案的调试配置,包括一个自定义控件项目和一个应用程序本身的项目。因为我发现在这样做之后,需要如此频繁地运行输出,所以我生成了一个小批处理文件:

@rem br.cmd: build and run
del ui\bin\Debug\LonghornSolitaire.exe
msbuild.exe /p:Configuration=Debug /q LonghornSolitaire.sln
ui\bin\Debug\LonghornSolitaire.exe

该批处理文件的第一行删除了输出,以便在生成过程失败时,在批处理文件的最后一行没有要运行的输出。msbuild.exe 的 /q 开关用于关闭除错误生成进程以外的所有进程。有关 MSBuild 的奇妙之处的详细信息,请参阅 Christophe Nasarre 的有关 MSBuild 的工作方式和扩展方式的系列文章。

一方面,可以使用 Visual Studio 来编辑文本以及创建和管理解决方案和项目文件,另一方面,又可以使用 MSBuild 来执行生成过程而不必锁定文件,我就是靠它们来完成开发工作的(还记得我说过 MSBuild 将变得很重要吗?)当然,随着工具的进步,一切都将变得更加 简单。

面板

Avalon 中的下一个大型元素系列是面板系列。面板是一个具有特定方式排列所含元素的容器。Avalon 内置了五种主要面板:

FlowPanel:该面板排列从左至右依次呈现(默认情况下)的子控件,并将无法适合某“行”的控件换行至下一行。请将这想象成在页面上依次呈现的文本,但将其一般化为任何控件组合。图 6 显示了正在起作用的 FlowPanel 示例。

foghorn04142004-fig06

图 6. 以两种不同宽度依次呈现矩形的 FlowPanel

DockingPanel:该面板将控件停靠在任何边缘或者填充剩余的空间,就像 Windows 窗体中的 Docking 属性一样。有关示例,请参见图 7。

foghorn04142004-fig07

图 7. 以两种尺寸停靠子控件的 DockPanel

GridPanel:GridPanel 是一个用于在简单网格中布置控件的简单表格,其中的行和列有以各种单位(像素、英寸、百分比等)指定的宽度,如图 8 所示。

foghorn04142004-fig08

图 8. 排列文本和文本框控件的 GridPanel

Table:Table 与 GridPanel 类似,但要丰富得多,它像 Microsoft Word 中的表格一样具有所有种类的格式设置选项。

Canvas:Canvas 是一个用于从任一边缘对子控件进行绝对定位的控件。请将其想象为令人兴奋的典型窗体或对话框,如图 9 所示。

foghorn04142004-fig09

图 9. 按照到边缘的固定距离排列控件的 Canvas

请回忆一下,PileOfCards 控件派生于 Canvas,因为我们将根据纸牌在牌堆中的顺序、它们的翻动状态以及其他方面(例如,纸牌是在同样花色的牌堆中,还是在一般的牌堆中),在控件内部按绝对位置来定位 Card 控件。

同样,GridPanel 恰好是我们在图 1 中所示主窗口周围的 13 个定标点排列 PileOfCard 控件所需的控件。以下为在 GridPanel 内部放置 PileOfCard 控件的 XAML:

GridPanel
  Columns="7" Width="100%" Height="100%" Background="DarkGreen">
  <GridPanel.ColumnStyles>
    <ColumnStyle Width="14.2857%" />
    <ColumnStyle Width="14.2857%" />
    <ColumnStyle Width="14.2857%" />
    <ColumnStyle Width="14.2857%" />
    <ColumnStyle Width="14.2857%" />
    <ColumnStyle Width="14.2857%" />
    <ColumnStyle Width="14.2857%" />
  </GridPanel.ColumnStyles>
  <GridPanel.RowStyles>
    <RowStyle Height="20%" />
    <RowStyle Height="80%" />
  </GridPanel.RowStyles>
  <!-- top row -->
  <cc:PileOfCards ID="drawPile" />
  <cc:PileOfCards ID="discardPile" />
  <Border/> <!-- spacer -->
  <cc:PileOfCards ID="suitPile1" />
  <cc:PileOfCards ID="suitPile2" />
  <cc:PileOfCards ID="suitPile3" />
  <cc:PileOfCards ID="suitPile4" />
  <!-- bottom row -->
  <cc:PileOfCards ID="cardPile1" />
  <cc:PileOfCards ID="cardPile2" />
  <cc:PileOfCards ID="cardPile3" />
  <cc:PileOfCards ID="cardPile4" />
  <cc:PileOfCards ID="cardPile5" />
  <cc:PileOfCards ID="cardPile6" />
  <cc:PileOfCards ID="cardPile7" />
</GridPanel>

您将注意到,GridPanel 的 ColumnStyles 元素定义了七个列,每个列占用了窗口总宽度(或至少其宽度的 99.9999%)的相等百分比。同样,RowStyles 定义了两个行,一行用于上面那行牌堆(该牌堆占用了窗口的 20%),一行用于下面那行牌堆(该牌堆占用了窗口的 80%)。一旦定义了列数,不特定于 GridPanel 的子元素就会变为单元格,并自动按列和行排列。每个子控件(其中一个除外)都是 PileOfCards 控件的实例,并使用我在前面讨论的 XML 命名空间映射。唯一的非 PileOfCards 控件是一个空的 Border 元素,它的作用仅仅是占据垫牌堆和第一个同一花色牌堆之间的空间。

在图 1 中,您还可以注意到另外一个细节。纸牌是以非常原始的文本形式呈现,正面朝下的纸牌显示在方括号中(有太多的纸牌绘制质量低劣)。您每次运行该应用程序时,纸牌都将具有不同的排列,就像它们被新的玩家洗过一样。该逻辑来自示例中包含的 Solitaire 引擎,该引擎由 James Kovacs 根据 Christine Morine's shared source Windows Forms implementation of classic Solitaire 重建。使用 James 的引擎所需的安装代码可在所含源代码的 MyApp.xaml.cs、Window1.xaml.cs 和 PileOfCards.xaml.cs 文件中找到。

Decorators、Shapes 和 Content Elements

我将在以后的文章中概述 Avalon 的其他三个元素系列:Decorators、Shapes 和 Content Elements。简而言之,Decorators 影响单个子控件的呈现方式。变换 Decorator 旋转、扭曲其子控件,调整其子控件的大小,以及以其他方式变换其子控件,以实现某些效果(有时为动画效果)。您已看到的边框 Decorator 用于在红色边框中将 PileOfCards 列表框控件换行。以上为目前的两个主要 Decorators 元素,将来会有更多此类元素。

Shapes 是当您在 GDI 或 GDI+ 中绘图时所使用的图元,并包括矩形、多边形、线、椭圆等。在本系列文章中的上一篇内提及的 XAML 牌面,被定义为一系列特定种类 Canvas(称为 FixedPage)的 Shapes。有关 Avalon 中的 Shapes 的完整讨论(包括它们在新的 Avalon 绘图模型中的使用),请参阅 Ian Griffiths 撰写的 Introducing the New Avalon Graphics Model。

Content Elements 是 Section、Heading、Paragraph、Line Break、Page Break、Bold、Italics 等一些您通常会在内容中看到的元素。Avalon 正是通过这些元素将应用程序和内容联系起来,并且对二者使用相同的模型、置标和结构。有关 Avalon 中 Content 元素的完整概述,请参阅 Dino Esposito 即将发布的“Documents Do Matter:Serve Them Nicely and Effectively with Avalon's Document Services”一文。

 

 

版权所有:UML软件工程组织