Windows 窗体的动画






4.81/5 (50投票s)
一个组件,通过添加动画功能使 Windows Forms 控件更具动态性。
目录
引言
通常,窗体和控件的内容是相对静态的。所有元素都放置在特殊位置,具有特殊的外观,并且很少变化。即使它们发生变化,这通常也是即时变化,而不是流畅的过渡。框架已经提供了拥有炫酷动画所需的一切。只需将它们组合起来即可。这通常是为每个特殊情况完成的。这个组件提供了基础结构,可以轻松集成任何可想象类型的动画。它还试图解决 Visual Studio Designer 只有静态视图这一事实。
除了游乐场示例外,我还包含了一个可重用的组件 Gradients
。它足够复杂,可以形成另一篇文章,但它也展示了 Animations
组件如何用于创建封装动画的可重用控件。因此,这两个组件都包含在这篇文章中。
背景
要使用此组件,您应该对使用 Windows Forms 有一些经验。要完全理解它,建议您了解 Timer
类、设计器属性和直接位图操作的知识,但我会尽力解释实现的关键部分。
使用代码
要设置某些内容动画,首先要设计一个静态窗体。完成后,决定要设置动画的内容,只需将适当的 Animator
组件拖到窗体上即可。如果要为控件的边界设置动画,请将 ControlBoundsAnimator
拖到窗体上。现在,将其 Control
属性更改为要设置动画的特定控件。您将看到 StartBounds
和 EndBounds
属性如何切换到控件的当前值。然后,将 SynchronizationMode
属性更改为 Start
,并将控件移动到动画开始时应有的位置。之后,将 SynchronizationMode
属性更改为 End
,并将控件移动到动画应结束的位置。如果来回切换此属性,您会注意到控件如何在两个设置的位置之间跳转。完成后,您可以将其设置回 None
。剩下要做的就是启动动画。如果您希望动画在窗体加载时启动,只需在窗体的 Load
事件中为动画组件添加一个 Start()
调用即可。
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
_myBoundsAnimator.Start();
}
显示窗体,您将看到动画。使用 Intervall
和 StepSize
属性,您可以调整动画的速度(默认值相当快)。
示例
我付出了很多努力来提供下载中的优秀示例。它们涵盖了此组件的大部分功能。请记住,此组件可以轻松扩展以设置任何内容的动画。提供的动画器只是可能完成的工作的示例。
在我的另一篇文章中可以看到另一个使用 Animations 组件的示例:BarTender - Group your contents[^]。
架构
包含的两个组件太大了,无法在此处详述。因此,我将简要介绍每个包含的类。我已尽最大努力对其进行了充分的文档记录——下载中还包含一个编译好的帮助文件。因此,您需要自己探索所有可用的属性和调整。
组件动画
AnimatorBase
这是实现特殊属性动画的所有类的基类。它包含一个 Timer
,其 Tick
事件充当动画的心脏起搏器。它提供了用于控制动画(如 Start()
、Stop()
...)的属性,用于定义通用行为的属性,通知外部世界发生了什么的事件,以及轻松将多个动画器绑定的逻辑。该类本身是抽象的。因此,在任何事情发生之前都需要一些具体的实现。每个继承的动画器都必须重写 StartValue
、EndValue
和 CurrentValueInternal
属性,这些属性仅向基类告知要设置动画的属性的当前状态以及动画的开始和结束状态的信息。除此之外,还必须重写 GetValueForStep
函数。它接受一个 0% 到 100% 的值,并应返回 StartValue
和 EndValue
之间的插值。为了让实现者更容易,AnimatorBase
包含几个静态函数来帮助插值不同类型的值。
为了使您自己的组件与设计器更好地协同工作,还需要做一些额外的工作。只需查看提供的实现即可发现所有详细信息。
ControlBackColorAnimator
此 AnimatorBase
实现动画给定控件的背景颜色。
ControlForeColorAnimator
此 AnimatorBase
实现动画给定控件的前景色。
ControlBoundsAnimator
此 AnimatorBase
实现动画给定控件的边界。这可以是位置和/或大小。提供了几个属性来指定边界的哪个部分应被动画化,哪些部分是静态的。
FormOpacityAnimator
此 AnimatorBase
实现动画 Form
的不透明度。可用于淡入或淡出窗体。
TrackBarValueAnimator
此 AnimatorBase
实现动画 TrackBar
的 Value
。这在实践中可能不太有用。
组件渐变
GradientBorder
此控件是其他控件的容器,并添加一个逐渐透明到其边缘的边框。因此,越靠近边缘,看到的背景越多。它会调整其 DockPadding
以适应边框宽度。因此,您可以插入控件并将其停靠,它们将根据边框的宽度进行调整。边框及其内部的颜色通过 InnerColor
属性设置。
为了实现此效果,它重写了 OnPaint
函数。为了提高速度,它会在属性更改时将自身绘制到 Bitmap
中,然后仅绘制此位图。这种技术称为双缓冲。绘画本身是 unsafe
的,看起来相当混乱。它针对速度进行了极大的优化。感谢 Christian Graus 在此方面的帮助。如果您对此特殊部分感兴趣,请参阅他的文章系列 Image Processing for Dummies[^]。
GradientBorderLabel
此控件派生自 GradientBorder
,并通过扩展 OnPaint
添加文本。它还具有用于调整文本位置的属性。如果您想知道如何使用 StringFormat
类,可以查看其内部。
GradientBorderInnerColorAnimator
现在,我们来看 Animations
组件发挥作用的类。此类继承 AnimatorBase
,并动画化 GradientBorder
的 InnerColor
。请注意,由于 GradientBorderLabel
继承自 GradientBorder
,因此您也可以对其进行动画处理。
GradientBorderWidthAnimator
此类继承 AnimatorBase
并动画化 GradientBorder
的 BorderWidth
。请注意,由于 GradientBorderLabel
继承自 GradientBorder
,因此您也可以对其进行动画处理。
GradientBorderButton
此类继承 GradientBorderLabel
,并集成了 GradientBorderInnerColorAnimator
、GradientBorderWidthAnimator
和 ControlForeColorAnimator
。它们是必需的,因为此控件有两种状态——鼠标悬停时和不悬停时。控件在进入或离开鼠标时在这两种状态之间进行动画处理。它具有许多属性来影响此动画。
整个动画部分隐藏在类内部。因此,使用它相对容易,因为只需要设置一些属性。
设计器支持
我希望这些组件能获得良好的设计器支持。我认为关于最重要的事情的资料不多,所以我现在就来做。
Browsable
Browsable
属性可用于告诉设计器是否应显示特定属性。默认情况下,所有公共属性都列在属性窗口中。尽管不可更改,但这同样适用于 readonly
属性,我通常倾向于不显示它们。此外,有时某个属性在设计时可能没有实际用途。重写属性时,您还可以重写 Browsable
属性。例如,Control
有一个 Text
属性,在大多数继承类中在设计器中被隐藏,但例如 Label
类重写了它并使其可见。
[Browsable(false)]
public int MySampleProperty {
get { return _mySampleValue; }
set { _mySampleValue = value; }
}
描述
除了代码文档外,您还可以为设计器显示的任何属性定义描述。这可以通过使用 Description
属性来完成,该属性只包含一个简单的字符串,包含描述性文本。此文本将显示在属性窗口底部,针对选定的属性。
[Description("This is just a sample.")]
public int MySampleProperty {
get { return _mySampleValue; }
set { _mySampleValue = value; }
}
类别
属性窗口具有分类视图,其中属性分组在一起。通常,属性被放在 Others
组中。您可以使用 Category
属性定义属性的组织方式。使用一些默认名称,您的属性将与 .NET 的属性组合在一起。即使您没有英文版的 Visual Studio,也应该使用默认的英文类别名称,因为它们会自动本地化。在德语版 Visual Studio 中,类别名称 Behavior 将在属性窗口中显示为 Verhalten。
[Category("Appearance")]
public int MySampleProperty {
get { return _mySampleValue; }
set { _mySampleValue = value; }
}
默认值
您是否注意到通常所有属性值都正常显示,并且在您更改它们时变为粗体?当它们是粗体时,意味着它们不对应于默认值。这也影响写入设计器生成的代码部分的属性。您可以使用 DefaultValue
属性来影响此行为,该属性接受一个定义默认值的常量表达式。为了使其清晰,您应该始终提供一个来自常量字段的值,并且还要将其分配给字段声明或构造函数中的特定字段/元素。
private const int DEFAULT_MY_SAMPLE_PROPERTY = 0;
private int _mySampleValue = DEFAULT_MY_SAMPLE_PROPERTY;
[DefaultValue(DEFAULT_MY_SAMPLE_PROPERTY)]
public int MySampleProperty {
get { return _mySampleValue; }
set { _mySampleValue = value; }
}
ShouldSerialize
DefaultValue
属性的一个问题是您需要提供一个常量表达式。这适用于所有基本类型。但是,在更复杂的情况下可以怎么做?为此,您只需实现一个名为 ShouldSerializePropName
的函数。它不应接受任何参数,并返回一个布尔值。设计器通过反射搜索这些属性,并在它们存在时调用它们。
public Color MySampleProperty {
get { return _mySampleValue; }
set { _mySampleValue = value; }
protected virtual bool ShouldSerializeMySampleProperty() {
return base.Parent != null && MySampleProperty.Equals(Color.Empty);
}
如您所见,任何逻辑都可以实现,就像在这种情况下一样,为了序列化 MySampleProperty
,会检查设置的值以及另一个属性的内容。
类型转换器
TypeConverter
属性定义了在设计器中如何编辑值。框架内置了数十种,您也可以指定自己的。
[TypeConverter(typeof(OpacityConverter))]
public int MySampleProperty {
get { return _mySampleValue; }
set { _mySampleValue = value; }
}
此示例将显示 0 到 1 的值,在属性窗口中显示为 0% 到 100%。
RefreshProperties
如果一个属性影响另一个属性的内容,属性窗口通常会感到困惑,并且不会显示正确的值。通过使用 RefreshProperties
属性,您可以在特定属性的值更改时使其完全刷新。
public int MySampleProperty1 {
get { return _mySampleValue1; }
set { _mySampleValue1 = value;
_mySampleValue2 = value * 2; }
[RefreshProperties(RefreshProperties.Repaint)]
public int MySampleProperty2 {
get { return _mySampleValue2; }
set { _mySampleValue2 = value;
_mySampleValue1 = value / 2; }
}
如果您有两个像这样的属性,在更改 MySampleProperty1
时,您将看不到对 MySampleProperty2
的任何更改。但是由于 RefreshProprties
属性,在更改 MySampleProperty2
时,您会看到 MySampleProperty1
。
设计器
Designer
属性用于确定如何设计特定类。当需要这样做的一个常见示例是,当您自己的 UserControl
也应作为容器控件运行时。
[Designer("System.Windows.Forms.Design.ParentControlDesigner,
System.Design", typeof(IDesigner))]
public class GradientBorder : System.Windows.Forms.UserControl
DesignMode
这一个不直接影响设计器,但在某些情况下,您可能需要在代码中检查它当前是否在设计模式下运行。它是 Component
类的 protected
属性,因此可以直接从任何组件(包括所有控件)访问。
待办事项
- 一些额外的
AnimatorBase
实现应该很有用。 - 目前,在 .NET 2.0 框架下编译时,动画不会流畅运行。这是由于
System.Windows.Forms.Timer
类的更改。 - 您喜欢的任何内容 :)。请随时发布请求。
历史
- 2006 年 3 月 2 日 - 版本 1.0
- 初始发布。
- 2006 年 5 月 13 日 - 版本 1.1
- 将
LoopAnimation
属性更改为LoopMode
,现在支持三种不同的状态。 - 添加了一个名为
DummyAnimator
的新动画器,它本身不执行任何动画,但可以作为其他动画器的父级。 - 进行了几处小的更正和调整。
- 将