请回忆一下在上一篇文章中,我正在身陷于构建“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 所示
图 1. 某种类似于
Solitaire 的东西
Avalon 元素
在我们进一步讨论如何生成 lhsol
之前,我们需要了解更多关于 Avalon
中提供的生成块的信息。Avalon
具有五个元素系列,如图 2 所示。
图 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
命名空间容纳了长期以来广受欢迎的控件(如
Button、ListBox 和 TextBox)以及一些即将流行的控件(如
Audio、Video 和 Canvas)。在这一阶段,您可能开始想念
ToolBar 和 Tree
等控件,但请您放心,它们不久就会问世。
这一控件元素系列正是我开始生成 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
项目项模板。您具体选择哪一个模板并不重要,但请确保使用您喜欢文件名,因为更改文件名会搞乱与该版本中的代码隐藏文件之间的关联。
图 3. 正确关联的 XAML
和代码隐藏文件
图 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 所示。
图 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 示例。
图 6.
以两种不同宽度依次呈现矩形的
FlowPanel
|
• |
DockingPanel:该面板将控件停靠在任何边缘或者填充剩余的空间,就像
Windows 窗体中的 Docking
属性一样。有关示例,请参见图 7。
图 7.
以两种尺寸停靠子控件的 DockPanel
|
• |
GridPanel:GridPanel
是一个用于在简单网格中布置控件的简单表格,其中的行和列有以各种单位(像素、英寸、百分比等)指定的宽度,如图
8 所示。
图 8.
排列文本和文本框控件的 GridPanel
|
• |
Table:Table 与 GridPanel
类似,但要丰富得多,它像 Microsoft Word
中的表格一样具有所有种类的格式设置选项。
|
• |
Canvas:Canvas
是一个用于从任一边缘对子控件进行绝对定位的控件。请将其想象为令人兴奋的典型窗体或对话框,如图
9 所示。
图 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”一文。
|