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

WPF 中的动画“AlarmBar”自定义控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.13/5 (6投票s)

2008年7月22日

CPOL

6分钟阅读

viewsIcon

64273

downloadIcon

1511

WPF 关于控件自定义的发布资源几乎全部集中在编辑 ControlTemplates 的本地副本上,而实现和交互实际的 Custom Control 库则需要使用一套不同的技术和引用语法。

引言

许多应用程序都包含某种“消息栏”来以美观且不显眼的方式向用户显示错误、警告、状态或其他类别的消息。虽然在 WPF 中实现基本功能(只需将 `TextBlock` 拉伸到窗口顶部)非常简单,但在生产环境中,您可能需要创建可在项目之间共享、具有微妙动画效果、支持简单 XAML 实例化语法的... ...

附加项目实现了一个“`AlarmBar`”自定义控件,支持“`Glass`”(玻璃)和“`Etched`”(蚀刻)视觉样式以及多种动画选项。消息会缓慢淡入,保持可见几秒钟,然后淡出(在实际应用中,支持的细节将单独写入某个日志)。

NoAlarm.png

ErrorAlarm.png

WarningAlarm.png

添加 `AlarmBar` 并自定义其外观和行为,只需几行 XAML,您就可以定义应用程序所需的任意数量的 `Alarm` 类别及其关联的自定义颜色方案。尝试将 `Button` 或其他无效的子元素添加到集合中,将触发即时(设计时)错误通知。

ClientXaml.png

要显示 `Alarm`,只需指定 `Alarm` id 和消息文本

alarmBar.Display( “Error”, “Containment breach imminent. Run for your lives!” )

WPF 自定义控件项目结构

WPF 关于控件自定义的发布资源几乎全部集中在编辑 ControlTemplates 的本地副本上,而实现和交互实际的 Custom Control 库则需要使用一套不同的技术和引用语法。如果您是第一次接触 WPF Custom Controls,最好创建一个空的 WPF 客户端项目并按以下步骤操作,这样会更容易理解项目代码。请注意,一个通用的 Custom Control 将在 *Themes*/*generic.xaml* 中生成,您一定不要重命名或移动该文件。

  • 使用 *Add\New Project\WPF Custom Control Library* 生成默认控件和模板。

  • 从您的客户端项目添加一个对控件项目的引用,并重新生成解决方案。

  • 在您的客户端窗口 XAML 文件顶部添加一行以导入控件命名空间

    xmlns:custom="clr-namespace:ControlProjectName;
    		assembly=ControlProjectAssemblyName" 
  • 在 WPF 窗口中添加控件实例,设置其背景颜色,然后运行应用程序

    <custom:CustomControl1 Background="Red" Height="20" VerticalAlignment="Top" /> 

验证设计时 XAML 条目

要显示消息,`AlarmBar` 会引用其集合中某个 `Alarm` 对象的相关信息。构建此类控件时,您面临的第一个决定是选择一个要继承的基类,以便通过 XAML 支持向控件添加 `Alarm` 对象(并且*仅* `Alarm` 对象)。继承自 `ItemsControl` 将允许通过标记添加集合成员,但您无法在设计时验证添加(您可以通过手动迭代成员并检查其类型在运行时捕获错误,但这并不是一个令人满意的解决方法)。通过继承自 `ContentControl` 并指定 `Alarm` 作为支持的集合类型,当用户尝试向我们的集合添加无效的子元素时,会自动在设计时生成错误。请注意,尽管 `AlarmBar` 包含一个 `Alarm` 列表,但我们可能不想使用 `ListBox` 作为基类,因为我们实际上从未显示一个列表(技术上,我们显示与单个“活动”列表项相关的属性)。

AlarmBarContentProperty.png

偏好 XAML 而非过程代码

实现我们的控件几乎没有实际代码。我们只需定义包含绑定到几个 `DependencyProperties` 的显示元素的模板和样式。显示 `Alarm` 所需的过程代码包括将“`ActiveAlarm`”属性设置为 ID 与显示请求中 ID 匹配的集合成员,设置该 `Alarm` 对象的消息文本,以及启动 Storyboard 动画。

构成 `AlarmBar` 默认模板的关键组件是

  • `TextBlock`,反映当前的 `Alarm` 颜色和实际的消息文本,并裁剪以符合控件的圆角半径设置。
  • 可选的次要动画,例如动画“脉冲”,它将与“淡入/淡出”显示序列并行运行。
  • 可选的 `static` 覆盖(目前仅用于创建“`Glass`”效果)。

MainTemplate.png

次要动画和 `static` 覆盖元素的显示和行为是在单独的样式和模板中定义的,这些样式和模板根据“`VisualStyle`”(Visual Style)和“`AnimationStyle`”(Animation Style)`enum` 值“插入”到 `AlarmBar` 中(这些值将在下一节中进行内部转换)。

隐藏客户端的 ComponentResourceKeys

有两种方法(忽略反射等技术)供客户端代码引用外部程序集中的 WPF 逻辑资源。对于简单的资源 DLL,我们可以指定远程 XAML 文件的路径...

<ResourceDictionary Source="/SomeAssembly;component/SomeResourceDictionary.xaml" />

...并使用与本地相同的语法引用其资源。

MyProperty="{StaticResource SomeResourceIdString}"

自定义控件通常依赖于在特定 CLASS 上下文中标识的资源,使用 `ComponentResourceKeys`,必须从客户端使用以下语法变体之一引用

MyProperty="{DynamicResource {ComponentResourceKey TypeInTargetAssembly=
		{x:Type custom:SomeClass}, ResourceId= SomeResourceIdString }}" 
MyProperty="{DynamicResource {ComponentResourceKey {x:Type custom:SomeClass}, 
		SomeResourceIdString }}"
MyProperty="{StaticResource {x:Static custom:SomeClass.PropertyToExposeSomeResource}}"

这要求客户端了解控件库内部定义的特定 `ComponentResourceKey string`,这并不总是可取的。我们的控件通过向客户端公开由 Intellisense 友好的 `enum` 值支持的属性,并在设置底层 `DependencyProperties` 之前将这些 `enum` 转换为 `ComponentResourceKeys` 来避免这种情况。为了消除额外的步骤,我们使用相同的 `string` 来定义 `enum` 值及其关联的 `ComponentResourceKey`。

杂项

XAML 文件组织

实际的 Custom Control Library 最终会包含许多不同的控件,将它们所有相关的标记都转储到一个 `generic.xaml` 文件中将导致无法维护的混乱。在我们的项目中,`generic.xaml` 会从第二个 XAML 文件中提取信息(目前有一个已知的 WPF 错误,在引用 *Themes* 文件夹内的同级 XAML 文件时需要指定完整路径)。我们还将颜色定义(即使只在一个地方使用)提取到一个单独的文件中。颜色在开发过程中往往会频繁更改,通过扫描越来越多的非平凡标记来挑选颜色值会极其繁琐。

动画 Storyboards

Storyboards 在启动的那一刻就被“冻结”了,导致尝试将动画的“To”或“From”属性绑定到动态更新的值失败,这就是为什么我们被迫使用 `static SystemParameters.FullPrimaryScreenWidth` 值作为我们“Blip”动画中的 `TranslateTransform` 目标。另一个选项可能是响应窗口大小调整事件显式重新加载动画。

启动动画的预期架构方法是让 `AlarmBar` 引发一个“`AlarmChanged`”`RoutedEvent`,该事件在模板中通过 `EventTrigger` 处理。虽然这在运行*单个*动画时效果很好,但在我们的情况下(有时必须在两个不同模板的上下文中并行运行两个动画),我无法使其正常工作。

Andy L. 的其他项目

历史

  • 2008 年 7 月 22 日:初始发布
© . All rights reserved.