支持设计器的 ImageList 动画控件 (ImAC)






4.20/5 (3投票s)
一篇关于编写带设计器支持的ImageList动画控件的文章。
引言
在我之前的文章中,我解释了如何编写一个带设计器支持的 SignalBar 控件。本文将介绍如何在 .NET CF 中编写一个简单的 ImageList
动画控件 (ImAC)。CodeProject 上还有一个用 .NET CF 编写的 动画控件,允许您对 .GIF 文件进行动画处理。我想创建的控件略有不同。它可以动画处理 .NET CF 支持的几乎任何图像(.bmp、.gif、.png、.jpg),并允许用户在设计时创建自定义动画。这是可能的,因为 ImAC 带有设计器支持功能。
背景
不幸的是,在 .NET CF 中编写自定义控件不像在完整的 .NET Framework 中那样直接。它没有提供“设计器支持” (DS)!幸运的是,有一些文章可以帮助您为 .NET CF 添加 DS。其中一篇由 Jim Wilson 撰写:《为 .NET Compact Framework DateTimePicker 控件添加设计器支持》。因此,我强烈建议您阅读他的文章,作为理解本文的前提。因此,在本文中,我将更专注于编写实际的控件本身。但是,源代码将包含完整的 DS 并可供使用。
使用代码
您可以下载上面提供的演示 ZIP 文件。它包含两个程序集,运行时和设计时。将运行时程序集 (HT.CustomControl.AnimationCtrl.dll) 提取到 \Program Files\Microsoft Visual Studio .NET 2003\CompactFrameworkSDK\v1.0.5000\Windows CE\,如果不存在,请创建一个名为 Designer 的子文件夹,然后将设计时程序集 (HT.CustomControl.AnimationCtrl.Design.dll) 复制过来。接下来,将其添加到您的 VS.NET 工具箱中(视图 -> 工具箱 或 Ctrl+Alt+X)。您可以通过右键单击并选择“添加选项卡”来创建自己的控件选项卡。然后,通过选择“添加/删除项…”菜单项到新创建的选项卡中,添加控件程序集。然后,浏览并找到 {Installation Directory}\Designer\ 文件夹中的 HT.CustomControl.AnimationCtrl.Design.dll 程序集,单击“确定”,然后您应该能够看到并将控件拖放到您的窗体上。
代码设计
解决方案中唯一重要的文件是运行时 AnimationControl 项目下的 AnimationCtrl.cs。此文件包含控件使用的所有字段、属性和方法。文件以继承自 Control
类的 AnimationCtrl
类声明开始。
public class AnimationCtrl : System.Windows.Forms.Control
{
...
}
在类声明之后的下一个块中,指定了定义类所需的字段。
...
#region fields
protected Bitmap m_bmp; // offscreen bitmap
protected Graphics m_graphics;
protected int iloop;
protected int nLoop;
protected bool bStarted;
protected int ci; // index that point to current image in imglist
protected Timer tmrAnm = null; // timer controlling animation
private ImageList imglist = null;
#endregion
...
在字段声明之后,定义了以下属性。请注意,每个属性都有一个前导部分。前导部分定义了将属性公开到 Visual Studio .NET 2003 (VSNET) 属性窗口所需的属性。
#region properties
#if NETCFDESIGNTIME
[System.ComponentModel.Browsable(true)]
[System.ComponentModel.Category("Behavior")]
[System.ComponentModel.DefaultValue(-1)]
[System.ComponentModel.Description("Determines the number" +
" of loops, -1 will loop forever")]
#endif
public int Loop // number of looping
{
get
{
return nLoop;
}
set
{
nLoop = value;
}
}
#if NETCFDESIGNTIME
[System.ComponentModel.Browsable(true)]
[System.ComponentModel.Category("Behavior")]
[System.ComponentModel.DefaultValue(1000)]
[System.ComponentModel.Description("The animation" +
" interval in miliseconds")]
#endif
public int Interval // in miliseconds
{
get
{
return tmrAnm.Interval;
}
set
{
tmrAnm.Interval = value;
}
}
#if NETCFDESIGNTIME
[System.ComponentModel.Browsable(true)]
[System.ComponentModel.Category("Appearance")]
[System.ComponentModel.DefaultValue(null)]
[System.ComponentModel.Description("The list of images to be animated")]
#endif
public ImageList ImageList
{
get
{
return imglist;
}
set
{
imglist = value;
}
}
#endregion
以下是 AnimationCtrl
在 VS.NET 的属性窗口中的快照。我们应该看到上面定义的所有已公开属性。注意 System.ComponentModel.Description
属性的值如何显示在属性窗口的底部。
图 1:AnimationCtrl 属性窗口快照
要以编程方式操作控件,您可以调用 Start()
和 Stop()
方法。当您启动动画并停止然后再次启动时,动画应该从停止时的最后一帧恢复。但是,在其他情况下,您可以停止动画,当您再次启动它时,它会从头开始。后者操作可以通过调用 Reset()
方法轻松实现。以下描述了这三个方法。
public void Reset()
{
Stop();
ci = 0;
}
public void Start()
{
if(!bStarted)
{
bStarted = true;
tmrAnm.Enabled = true;
}
}
public void Stop()
{
bStarted = false;
tmrAnm.Enabled = false;
}
由于控件将由之前声明的定时器对象 tmrAnm
驱动,因此每次定时器生成的 tick 事件发生时,它都会要求控件用 ImageList
imagelist
中的下一张图像重绘自身。
在知道定时器 tick 事件发生时将调用哪个处理程序之前,您应该在初始化控件时将处理程序 (tmrAnm_Tick()
) 绑定到定时器对象 (tmr_Anm
)。完成后,处理程序可以调用 Invalidate()
函数来生成 OnPaint
消息。您可能需要查阅 MSDN 库以了解更多关于 Invalidate()
、Refresh()
和 Update()
的信息。当控件收到 OnPaint
消息时,默认情况下会调用 OnPaint
函数。
/// Timer
/// Binding the timer.Tick event to the handler
tmrAnm = new Timer();
tmrAnm.Interval = 1000;
tmrAnm.Tick += new EventHandler(tmrAnm_Tick);
private void tmrAnm_Tick(object sender, EventArgs e)
{
Invalidate();
}
控件的逻辑位于 OnPaint
函数中。为了良好的设计实践,我们可以编写一个函数来分离逻辑和 UI。但是,在本例中,我不会这样做,因为函数调用会产生开销。虽然在实际情况中可能差别不大,但对于快速重绘,这可能会给动画带来一些延迟。
OnPaint
函数将遍历 ImageList
中的图像,从“ci
”指向的图像开始。通常,“ci
”初始化为零。然后它会检查是无限循环直到调用 Stop
方法,还是循环 nLoop
次。OnPaint
调用以将内存位图 m_bmp
绘制到屏幕结束。
#region overridden methods
protected override void OnPaint(PaintEventArgs e)
{
// draw to memory bitmap
CreateMemoryBitmap();
// init background
m_graphics.Clear(this.BackColor);
if(imglist.Images.Count > 0)
{
Bitmap bmp = new Bitmap(imglist.Images[ci]);
m_graphics.DrawImage(bmp, 0, 0);
if(bStarted)
{
if(nLoop == -1)
{
ci = (ci + 1) % imglist.Images.Count;
}
else
{
if(iloop >= nLoop)
{
Stop();
iloop = 0;
}
else
{
iloop++;
}
ci = (ci + 1) % imglist.Images.Count;
}
}
}
// blit memory bitmap to screen
e.Graphics.DrawImage(m_bmp, 0, 0);
}
protected override void OnPaintBackground(PaintEventArgs e)
{
// don't pass to base since we paint everything, avoid flashing
}
最后要做的是,控件应通过调用 Invalidate()
函数来重绘自身,每当它被调整大小时。因此,我们需要覆盖 OnResize()
函数,如下所示。
protected override void OnResize(EventArgs e)
{
base.OnResize (e);
this.Invalidate();
}
#endregion
最后,控件的初始化如下所示。
#region initialization
// default constructor
public AnimationCtrl()
{
InitializeComponents();
}
private void InitializeComponents()
{
///
/// Image List
/// List of images to be animated
imglist = new ImageList();
/// Timer
/// Binding the timer.Tick event to the handler
tmrAnm = new Timer();
tmrAnm.Interval = 1000;
tmrAnm.Tick += new EventHandler(tmrAnm_Tick);
///
/// ci
///
ci = 0;
///
/// flag
///
bStarted = false;
nLoop = -1;
iloop = 0;
}
结论
编写自定义控件是一次激动人心的体验,您可以根据自己的意愿自定义控件的行为。通常,子类化一个控件非常简单。然而,在 .NET CF 中,由于不能自动获得设计器支持,所以有点麻烦。感谢那些撰写了关于如何在 .NET CF 中为自定义控件编写添加设计器支持的文章的人。这使得我在开发 .NET CF 应用程序时生活变得更加轻松。
历史
- 版本 1.0 -ImageList动画控件 (ImAC) 的初始发布。