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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.20/5 (3投票s)

2005年7月7日

CPOL

5分钟阅读

viewsIcon

43760

downloadIcon

561

一篇关于编写带设计器支持的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 属性的值如何显示在属性窗口的底部。

Figure 1: Snapshot of AnimationCtrl property window

图 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) 的初始发布。
© . All rights reserved.