XAML 和 WPF 中的图形
XAML 和 WPF 中的图形
注意
本文最初发布于 我的网站。所有文字和代码示例都相同。但是,在我的网站上,您还可以看到 WPF 和 Silverlight 示例的实际效果。因此,在阅读 CodeProject 上的文章后,我邀请您也访问我的页面。
引言
在过去的几周里,我的同事 Karin Huber 和我一直在为软件架构师打基础。软件架构师专注于 COTS(现成商业软件)。不过,这并不是本文的主题(如果您想了解更多关于这家公司的情况,请访问我们的网站:此处 或 此处)。在本文中,我想在图形的上下文中描述 XAML、WPF 和 Silverlight 的功能。我将使用我们新公司的公司徽标作为本文的示例,因为其 XAML 实现使用了相当多的 XAML 图形功能。
遗憾的是,我无法在此文档中深入探讨。如果您对更深入的信息感兴趣,可以阅读 MSDN 库,或者(如果您能阅读德语)可以查看我们最新的关于 XAML 和 WPF 的书籍。

以下是我将在本文中讨论的内容
- 从 Inkscape 到 XAML - 如何与设计专业人士合作
- 形状和绘图对象 - 相似之处、区别以及何时使用
- 组合几何图形对象
- 变换对象 - 创建阴影效果
- WPF 和 Silverlight 之间的区别
从 Inkscape 到 XAML
如果您是程序员,那么在图形设计方面,您很可能需要与设计专业人士合作。根据我的经验,如果您在聘请设计专业人士之前就开始考虑内容、基本布局和设计原则,就能取得最佳效果。我们总是尝试勾勒出我们想要的东西的基本草图,然后将其交给我们的设计伙伴。我们发现,我们自己能实现的设计效果并不理想,仅仅是因为我们学习的是软件开发而不是设计。然而,如果您在请求设计工作时无法表达您的需求,那么效果也不是最好的。
- 您的软件开发团队中应该有一位成员了解图形设计的基本原则。他可以成为软件技术人员和设计师之间的“沟通桥梁”。
请继续关注我的博客;几天后,我将发布一些关于非设计师的设计原则的书籍推荐。 - 这些人使用的工具与我们软件开发人员使用的工具非常不同。在图形设计领域,我认识的几乎所有专业人士都使用 Adobe 的工具(例如,Adobe Illustrator)。您需要找到一个共同点来与他们交换工作。
我们先来关注第二点。如果您运气好,您的软件开发团队也可以负担得起 Adobe 许可证。网上有适用于 Adobe 工具的 XAML 导出器和转换器。尝试使用它们,看看它们是否能帮助您构建从开发到设计的桥梁。然而,这些工具非常强大,您需要花一些时间来学习如何使用它们。
另一个选择是使用微软的对应产品:Microsoft Expression Studio。这些工具的功能范围与我之前提到的 Adobe 工具类似。问题是,我认识的大多数设计专业人士目前都不使用它们。它们最大的优点是微软的设计工具原生支持“XAML”。它们的 XAML 导出功能非常好!但是,与 Adobe 工具一样,您需要一段时间才能熟悉 Expression。
如果您从事的是低预算项目,或者您想使用一个更容易使用的设计程序(因为它功能较少),您可以使用一个开源设计工具:Inkscape。我个人非常喜欢 Inkscape。Inkscape 的一个美妙之处在于它的文件格式:它使用 SVG(可缩放矢量图形)。您可以使用 Inkscape 绘制基本布局的创意,并将 SVG 文件交给您的设计专业人士。您可以确信他们能够处理 SVG;这种文件格式对他们来说并不陌生。
根据您的设计专业人士使用的工具,他们可以直接给您 SVG,或者直接导出 XAML。如果您收到 SVG,您很容易就可以将其转换为 XAML。几个月前,我在德国 MSDN 杂志上写了一篇关于如何手动将 SVG 路径转换为 XAML 路径的文章。在我的博客文章 XAML 中的世界 中,我用英语总结了内容。Jon Galloway 在他的博客中出色地描述了将 SVG 转换为 XAML 的方法。
即使您的设计师创建的是位图图像而不是矢量图形工具,Inkscape 也能提供帮助。该程序有一个内置的算法,用于矢量化位图。这很有用,但请准备好对矢量化过程的结果进行返工。
让我们来看看我们在创建新徽标 XAML 时是如何进行这个过程的。我们首先思考我们想要什么。公司名称已经确定(software architects)。我们开始定义关于我们希望徽标如何设计的一些基本指南。
- 我们不想使用复杂的剪贴画。徽标应使用有趣的字体显示公司名称。
- 公司名称相当长。因此,字体必须是紧凑的。否则,宽度和高度的比例会很奇怪。
- ...
基于这些设计理念,我们寻找了一种可以作为我们徽标基础的字体。经过一番研究,我们决定使用由国际字体公司创建的“ITC Franklin Gothic Book”的一个子类型。我们注意到 "software" 的最后三个字母和 "architects" 的前三个字母非常相似,因此我们决定利用这一点来实现图形效果。结果,我们向我们的设计专业人士提供了以下徽标草图(使用 Inkscape 构建的 SVG)以及我们的书面想法。

我们提供给设计专业人士的基本想法

在我看来,这是一个很好的例子,说明了设计专业人士能带来多大的不同。在我们的例子中,设计师采纳了我们的想法并进行了一些改进。他以一种略有不同的方式组合了这两个词,使效果更加有趣。

作为第二步,他将字体转换为路径,并更改了某些字母,以创建网格,将徽标分解成不同的水平部分。

此外,他还创建了一个增强版的徽标,包括渐变填充和反射效果。

过了一会儿,我们在 Inkscape(SVG 格式)中得到了矢量图形的徽标。

从那时起,我们就回到了我们原来的职业——编码。在 SVG 文件中,我们已经准备好了徽标的所有路径表达式,可以直接复制出来。我们可以在 XAML 中直接使用它们。这是 SVG 中路径表达式的一个例子。

正如您所见,路径是使用一种小型语言定义的,其中指定了所有点、线和曲线。这种小型语言在 XAML 和 SVG 之间非常非常相似。通常,您可以直接将 SVG 中的路径复制到 XAML 中,而无需进行任何更改。您可以在 W3C 上找到有关 SVG 中路径规范的详细信息,在微软的 MSDN 库 中可以找到有关 XAML 中路径的详细信息。
形状和绘图对象
下一步是在 XAML 中实现徽标。在 XAML 中,您有两种方式可以指定图形对象:您可以使用派生自 Shape(例如 Rectangle、Ellipse 等)的类,或者使用 Drawing 对象(派生自 Drawing 的类)。我们书籍“XAML und WPF Programmierhandbuch”中的以下 UML 类图显示了 WPF 中与 2D 图形相关的类的继承树。

图 7.10,第 442 页
Shapes 和 Drawing Objects 的主要区别在于:
- Shapes 是 FrameworkElement。因此,您可以像使用其他控件(例如
Button
、TextBox
等)一样在用户界面中使用它们。它们也派生自 Visual。因此,它们知道如何渲染自身。 - Drawing Objects 不是 FrameworkElement!它们是 Freezable(派生自 Freezable)。因此,它们可以被设置为只读状态。通过这种方式,您可以显著提高性能。但是,处于只读状态的 Freezable 不能通过动画或数据绑定进行修改。这导致一个结论:只有当 Freezable 代表静态图形且在运行时不发生变化时,才应“冻结”它们。
- Drawing Objects 不是 Visual!要显示 Drawing Objects,您需要一个 FrameworkElement 辅助对象。下图显示了这种关系。

图 7.27,第 467 页
乍一看,Geometries(派生自 Geometry
的对象)似乎与 Shapes 相似。实际上它们非常不同。Geometries 不代表可以直接显示在屏幕上的图形对象。它们仅指定对象的形状。
在我们的例子中,使用 Drawing Objects 实现徽标是有意义的,因为它是一个相对静态的图形对象。以下是徽标基本形状的 XAML 代码。请注意 PresentationOptions
的 namespace
声明。您需要此声明才能冻结 Freezables。
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:PresentationOptions=
"http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="PresentationOptions" >
我们将徽标放入页面的资源集合中。因此,徽标可以轻松地提取到一个单独的 XAML 文件中(例如,App.xaml 或其他文件)。
<Page.Resources>
在这里,您可以看到两个图形对象(“software”和“architects”)的声明。请注意,我们使用了冻结的 Drawing 对象。您还应该看看我们如何使用 GeometryGroup
组合多个 PathGeometry
对象。稍后我将详细介绍这个类。
<!-- ****** SOFTWARE ******************************************* -->
<!-- Geometry for the word "software" -->
<GeometryGroup x:Key="LogoSoftware"
PresentationOptions:Freeze="True" >
<PathGeometry Figures="M 0.31163807,145.75739 ... z" />
<PathGeometry Figures="M 70.074606,115.57876 ... z" />
<PathGeometry Figures="M 173.53291,0.40830421 ... z" />
<PathGeometry Figures="M 307.03223,184.21003 ... z" />
<PathGeometry Figures="M 391.22711,96.183574 ... z" />
<PathGeometry Figures="M 426.25331,184.22683 ... z" />
<PathGeometry Figures="M 501.63047,145.55673 ... z" />
</GeometryGroup>
<!-- ****** ARCHITECTS **************************************** -->
<!-- Geometry for the word "architects" -->
<GeometryGroup x:Key="LogoArchitects"
PresentationOptions:Freeze="True" >
<PathGeometry Figures="M 391.29841,156.18357 ... z" />
<PathGeometry Figures="M 426.35415,242.80498 ... z" />
<PathGeometry Figures="M 590.3878,242.94013 ... z" />
<PathGeometry Figures="M 625.36802,242.94013 ... z" />
<PathGeometry Figures="M 682.10338,226.72431 ... z" />
<PathGeometry Figures="M 735.83215,206.36345 ... z" />
<PathGeometry Figures="M 502.24129,206.22951 ... z" />
<PathGeometry Figures="M 805.67431,206.22951 ... z" />
<PathGeometry Figures="M 869.59918,226.66181 ... z" />
<PathGeometry Figures="M 873.62206,206.17249 ... z" />
</GeometryGroup>
在这里,我们定义了徽标中使用的渐变填充。
<!-- ****** BRUSHES ******************************************* -->
<!-- Brush for the word "software" -->
<LinearGradientBrush x:Key="SoftwareBrush" StartPoint="0,1"
EndPoint="0,0" PresentationOptions:Freeze="True">
<GradientStop Color="#76ba52" Offset="0.0" />
<GradientStop Color="#c0dd89" Offset="1.0" />
</LinearGradientBrush>
<!-- Brush for the word "architects" -->
<LinearGradientBrush x:Key="ArchitectsBrush" StartPoint="0,1"
EndPoint="0,0" PresentationOptions:Freeze="True">
<GradientStop Color="#264da6" Offset="0.0" />
<GradientStop Color="#15306c" Offset="1.0" />
</LinearGradientBrush>
正如我们之前所说,Drawing Objects 需要一个 FrameworkElement 辅助对象才能在屏幕上显示它们。在我们的例子中,Image
类被用于此目的。Image
需要一个 ImageSource
的子类作为图像的源。因此,我们在资源字典中提供了一个 DrawingImage
对象(DrawingImage
派生自 ImageSource
)。
<!-- ****** LOGO ********************************************** -->
<DrawingImage x:Key="SoftwareArchitectsLogo"
PresentationOptions:Freeze="True" >
<DrawingImage.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="{StaticResource SoftwareBrush}"
Geometry="{StaticResource LogoSoftware}" />
<GeometryDrawing Brush="{StaticResource ArchitectsBrush}"
Geometry="{StaticResource LogoArchitects}" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Page.Resources>
只需三行代码即可在任何 WPF 窗口或页面中显示徽标。其他所有内容都已在资源中定义。
<Canvas>
<Image Source="{StaticResource SoftwareArchitectsLogo}" />
</Canvas>
</Page>
在这里,您可以在 XAMLPad 中看到使用上述代码实现的徽标。XAMLPad 是一个小型工具,您可以使用它来原型化您的 XAML 代码。它包含在免费的 Windows SDK 中。

组合几何图形对象
在 WPF 中,您有两种方法可以组合 Geometries。您可以在集合中将它们分组(如上面的代码所示),在这种情况下,您使用 GeometryGroup
类。请注意,GeometryGroup
实际上只是一个集合,仅此而已。如果您想通过组合两个 Geometry 来创建一个全新的 Geometry
(例如,求交集、并集等),则必须使用 CombinedGeometry
。
在我们的例子中,我们组合了两个 Geometries 来创建“software”一词的实色底部。目标不是为徽标的这个区域创建单独的路径。相反,我们希望基于先前声明的路径,并排除灰色矩形(见下图)。
<Page.Resources>
[...]
请注意,我们如何使用 StaticResource
标记扩展来引用现有的“software”一词的 Geometry
。
<!-- Geometry for the dark area at the bottom of the letters
of the word "software" -->
<CombinedGeometry x:Key="LogoSoftwareBottomShape"
GeometryCombineMode="Exclude"
Geometry1="{StaticResource LogoSoftware}"
PresentationOptions:Freeze="True">
<CombinedGeometry.Geometry2>
<RectangleGeometry Rect="0,0,519,145" />
</CombinedGeometry.Geometry2>
</CombinedGeometry>
[...]
<!-- Brush for the dark area at the bottom of the letters
of the word "software" -->
<SolidColorBrush x:Key="SoftwareBottomShapeBrush"
Color="#76BA52" PresentationOptions:Freeze="True" />
[...]
<!-- ****** LOGO ********************************************** -->
<DrawingImage x:Key="SoftwareArchitectsLogo"
PresentationOptions:Freeze="True" >
<DrawingImage.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="{StaticResource SoftwareBrush}"
Geometry="{StaticResource LogoSoftware}" />
<GeometryDrawing
Brush="{StaticResource SoftwareBottomShapeBrush}"
Geometry="{StaticResource LogoSoftwareBottomShape}" />
<GeometryDrawing Brush="{StaticResource ArchitectsBrush}"
Geometry="{StaticResource LogoArchitects}" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Page.Resources>
变换对象
完成徽标所缺少的最后一块是两个单词的反射效果。该效果可以通过在 Y 轴上翻转它们来创建。对于这种情况,WPF 提供了 Transform
类。该库包含 Transform
的各种子类,您可以使用它们来创建不同的效果。下图概述了可供您使用的内容。

图 7.39,第 484 页
在我们的例子中,我们不仅使用了一个 Transform
对象。此外,我们还使用了 CombinedGeometry 来截断镜像的单词。如果我们不这样做,横向跟随徽标的对象将与徽标保持奇怪的距离。
<Page.Resources>
[...]
<!-- Geometry for the mirror-effect of the word "software" -->
<CombinedGeometry x:Key="LogoSoftwareMirror"
GeometryCombineMode="Exclude"
PresentationOptions:Freeze="True">
<CombinedGeometry.Geometry1>
<GeometryGroup>
<PathGeometry Figures="M 0.31163807,145.75739 ... z" />
<PathGeometry Figures="M 70.074606,115.57876 ... z" />
<PathGeometry Figures="M 173.53291,0.40830421 ... z" />
<PathGeometry Figures="M 307.03223,184.21003 ... z" />
<PathGeometry Figures="M 391.22711,96.183574 ... z" />
<PathGeometry Figures="M 426.25331,184.22683 ... z" />
<PathGeometry Figures="M 501.63047,145.55673 ... z" />
</GeometryGroup>
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<RectangleGeometry Rect="0,0,519,145" />
</CombinedGeometry.Geometry2>
您可以通过将 Transform
的适当子类分配给要更改的对象的 Transform
属性来应用变换。
<CombinedGeometry.Transform>
<TransformGroup>
<ScaleTransform CenterY="184" ScaleY="-1" />
</TransformGroup>
</CombinedGeometry.Transform>
</CombinedGeometry>
[...]
<!-- Geometry for the mirror-effect of the word "architects" -->
<CombinedGeometry x:Key="LogoArchitectsMirror"
GeometryCombineMode="Exclude"
PresentationOptions:Freeze="True">
<CombinedGeometry.Geometry1>
<GeometryGroup PresentationOptions:Freeze="True" >
<PathGeometry .../>
[...]
</GeometryGroup>
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<RectangleGeometry Rect="330,0,604,206" />
</CombinedGeometry.Geometry2>
<CombinedGeometry.Transform>
<TransformGroup>
<ScaleTransform CenterY="243" ScaleY="-1" />
</TransformGroup>
</CombinedGeometry.Transform>
</CombinedGeometry>
[...]
<!-- Brush for the mirror-effect of the word "software" -->
<LinearGradientBrush x:Key="SoftwareMirrorBrush" StartPoint="0,1"
EndPoint="0,0" PresentationOptions:Freeze="True">
<GradientStop Color="#0076ba52" Offset="0.4" />
<GradientStop Color="#60c0dd89" Offset="1.0" />
</LinearGradientBrush>
<!-- Brush for the mirror-effect of the word "architects" -->
<LinearGradientBrush x:Key="ArchitectsMirrorBrush" StartPoint="0,1"
EndPoint="0,0" PresentationOptions:Freeze="True">
<GradientStop Color="#00264da6" Offset="0" />
<GradientStop Color="#3015306c" Offset="1.0" />
</LinearGradientBrush>
[...]
<!-- ****** LOGO ********************************************** -->
<DrawingImage x:Key="SoftwareArchitectsLogo"
PresentationOptions:Freeze="True" >
<DrawingImage.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="{StaticResource SoftwareBrush}"
Geometry="{StaticResource LogoSoftware}" />
<GeometryDrawing Brush="{StaticResource SoftwareMirrorBrush}"
Geometry="{StaticResource LogoSoftwareMirror}" />
<GeometryDrawing
Brush="{StaticResource SoftwareBottomShapeBrush}"
Geometry="{StaticResource LogoSoftwareBottomShape}" />
<GeometryDrawing Brush="{StaticResource ArchitectsBrush}"
Geometry="{StaticResource LogoArchitects}" />
<GeometryDrawing Brush="{StaticResource ArchitectsMirrorBrush}"
Geometry="{StaticResource LogoArchitectsMirror}" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Page.Resources>
这是在 XAMLPad 中具有所有效果的完成徽标。

WPF 和 Silverlight 之间的区别
Silverlight 是微软对 Adobe 的 Flash 的回应。与 WPF 类似,Silverlight 也使用 XAML 来指定表示用户界面元素的“对象树”。如果您熟悉 WPF 的 XAML,那么开始学习 Silverlight 将不会困难。不幸的是,Silverlight 的 XAML 并不提供您在 WPF 中习惯的所有功能(我们这里讨论的是 Silverlight 1.0,这是当前已发布的版本)。
我不想给出所有差异的完整列表,因为您可以在微软的 Silverlight 开发中心 的 MSDN 库中详细阅读。我只想列出那些在我们为 Silverlight 应用程序实现徽标时影响到我们的差异。与 WPF 的 XAML 相反,Silverlight **不**...
- ...像 WPF 那样支持资源。Silverlight 资源仅包含用于动画的
Storyboard
对象。 - ...了解 Freezables。
- ...支持
PathGeometry
中的路径表达式的迷你语言。它仅在Path
中受支持。 - ...了解像
{StaticResource ...}
或{Binding ...}
这样的标记扩展。您需要使用 JavaScript 自己实现相应的功能。
以下是 Silverlight 中一个简化版徽标实现的源代码。您可以看到 Silverlight 版本与 WPF 版本非常相似。
请注意,Silverlight 版本中没有与冻结 Freezables 相关的引用。Silverlight 不了解 Freezables。
<Canvas
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" >
<Path x:Name="SoftwarePath"
Data="M 0.31163807,145.75739 ... z">
<Path.Fill>
<LinearGradientBrush StartPoint="0,1" EndPoint="0,0">
<GradientStop Color="#76ba52" Offset="0.0" />
<GradientStop Color="#c0dd89" Offset="1.0" />
</LinearGradientBrush>
</Path.Fill>
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="0.25" ScaleY="0.25" />
<TranslateTransform x:Name="SoftwareTranslateAnimation" />
</TransformGroup>
</Path.RenderTransform>
出于演示目的,我们在这里添加了一个小型动画。我们使用了 DoubleAnimation
对象来让徽标飞入并淡入。
<Path.Triggers>
<EventTrigger RoutedEvent="Path.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="SoftwareTranslateAnimation"
Storyboard.TargetProperty="Y"
From="-50" To="0" Duration="0:0:0.5" />
<DoubleAnimation
Storyboard.TargetName="SoftwarePath"
Storyboard.TargetProperty="Opacity"
From="0" To="1" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Path.Triggers>
</Path>
<Path x:Name="ArchitectsPath"
Data="M 391.29841,156.18357 ... z">
<Path.Fill>
<LinearGradientBrush StartPoint="0,1" EndPoint="0,0">
<GradientStop Color="#264da6" Offset="0.0" />
<GradientStop Color="#15306c" Offset="1.0" />
</LinearGradientBrush>
</Path.Fill>
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="0.25" ScaleY="0.25" />
<TranslateTransform x:Name="ArchitectsTranslateAnimation" />
</TransformGroup>
</Path.RenderTransform>
<Path.Triggers>
<EventTrigger RoutedEvent="Path.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="ArchitectsTranslateAnimation"
Storyboard.TargetProperty="Y"
From="50" To="0" Duration="0:0:0.5" />
<DoubleAnimation
Storyboard.TargetName="ArchitectsPath"
Storyboard.TargetProperty="Opacity"
From="0" To="1" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Path.Triggers>
</Path>
</Canvas>
摘要
WPF 和 XAML 是开发下一代 Windows 应用程序的绝佳工具。与 HTML 不同,您不必将要在 WPF 应用程序中使用的所有图像都转换为位图图像。只需让您的设计专业人士提供矢量格式的图像,您就会发现将其转换为 XAML 非常容易。
历史
- 2008 年 1 月 6 日:初次发布