[转]在 Longhorn 道路上跨出新的一步

本文围绕基于Longhorn生成Solitaire应用程序展开,探讨了Avalon的五个主要元素系列。介绍了控件系列,如将纸牌堆绑定到控件;阐述了在PDC Visual Studio中创建自定义控件的方法及问题解决;还提及面板系列,如用GridPanel排列控件。后续文章将继续探讨其他元素系列。

Chris Sells
Microsoft Corporation

2004年 4月 12 日

摘要:Chris Sells 在基于 Longhorn 生成其 Solitaire 应用程序的后一部分时,探讨了 Avalon 的五个主要元素系列。(13 页打印页)

*

下载 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

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

foghorn04142004-fig08

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

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

CanvasCanvas 是一个用于从任一边缘对子控件进行绝对定位的控件。请将其想象为令人兴奋的典型窗体或对话框,如图 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”一文。

致谢

感谢 Rob Relyea 在 Building Applications with Controls and Dialogs 中发表的有关 PDC 的论述。通过这篇文章,我了解了 Avalon 的五个元素系列,并了解了不同种类布局的画面的来源。他还在 Avalon newsgroup 中发布了有关如何在 XAML 中解决自定义控件使用问题的窍门。

同时,还要感谢 Mike Weinhardt 为我制作了图 2 和图 5。我喜欢使用屏幕快照,但他为我绘制了如此吸引人的图画,我实在无法拒绝。

最后,我要感谢 Avalon 工作组的 Dmitry Titov 和 Oleg Ovetchkine,是他们帮助了我使我的布局能够正常工作;同时还要感谢 James Kovacs 和 Christine Morine,我在我的代码中窃用了他们在 Sol 引擎方面所做的工作。

我们所处的位置

至此,我们已经探讨了 Avalon 的五个主要元素系列中的两个,它们是我们开始创建适合于生成可伸缩 Solitaire 应用程序的布局所需要的。我们已经使用控件和面板将我们的 UI 拆分为两个项目:一个项目用于自定义 PileOfCards 控件(解决当前工具集中的一些“问题”),一个项目用于应用程序本身(使用 GridPanel 来排列自定义控件的实例)。在下一篇文章中,我们将得到在上一篇文章中产生的 XAML 牌面,方法是将原始的 Solitaire 位图转换为 FixedPage XAML,并在由 PileOfCards 控件排列成组的 Card 控件中使用它们,同时还要在图片中添加 Shapes 元素,并很可能添加 Decorators 元素。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值