当我们在 Windows XP 下拷贝文件、删除文件、搜索文件等操作时,屏幕都会显示一个附带动画的操作进度对话框。从Win32编程时代一路走过来的朋友或者现在使用MFC进行开发的朋友对如何实现它想必都不会陌生。但是,我们现在已经在.Net C#平台下展开了全面的开发,那如何让C#程序也能实现显示一段Windows XP系统自带的动画资源呢?嘿嘿,.Net框架可不是白菜,通过使用PInvoke调用,我们照样实现具备这个功能的自定义控件。
我们的版本:

Windows的版本:

通过反复探索,我们发现要实现这个自定义控件必须掌握以下几项技术:
- Win32动态链接库在.Net平台下的调用;
- 绘制并显示系统默认动画不同于在.Net控件表面去捕获OnPaint事件,而要通过使用Win32 API利用句柄(handle)和消息循环来来通知Windows来播放动画;
- 对于自定义控件必须重载CreateParams方法,以便对控件的外观和行为实施更高级的控制;
- 自定义.Net控件必须从Windows类SysAnimate32派生,然后通过使用控件的句柄通过Win32 API进行绘制;
首先创建一个默认的Windows Form项目,我们先试验下Form类重载CreateParams方法并让其派生自Windows类SysAnimate32。在Form1.cs中copy如下内容:
protected override CreateParams CreateParams
{
get
{
// 派生该控件的 Windows 类的名称,用于显示系统自带动画资源
//请查询 MSDN ms-help://MS.MSDNQTR.v90.chs/dv_vcide/html/f9cd9cea-45a6-4349-8358-e5efbcdcff76.htm
CreateParams parms = base.CreateParams;
parms.ClassName = "SysAnimate32";
parms.Style |= ACS_CENTER;
parms.Style |= ACS_AUTOPLAY;
parms.Style |= ACS_TRANSPARENT;
return parms;
}
}
试运行结果如何?Form1的窗体行为和默认有了巨大区别:无法实施窗体标题栏拖动、无法关闭、窗体无法获得焦点等等。这些特点其实都是SysAnimate32类的特点,所以我们不能轻易的对Form实施操作,我们必须创建一个用户控件并对其外观和行为加以修改!
在解决方案中新建一个Windows控件库取名,这样的好处是日后可以在任意项目中重用。将上述代码拷贝到UserControl1.cs中。
Windows自带动画资源全部保存在Shell32.Dll中,我们只要返回动态链接库中的指定资源号,并通过SendMessage向Windows发送消息,告诉Windows我将在哪个表面(控件handle)以什么方式播放哪个资源动画即可。
public UserControl1()
{
InitializeComponent();
TabStop = false;
SetStyle(ControlStyles.Selectable, false);
SetStyle(ControlStyles.UserPaint, false);
SetStyle(ControlStyles.ResizeRedraw, true);
if (m_hShellModule == 0)
m_hShellModule = LoadLibraryEx("shell32.dll", 0, 2);
SendMessage(new HandleRef(this, Handle), ACM_OPENW, m_hShellModule, 150);
SendMessage(new HandleRef(this, Handle), ACM_PLAY, -1, MakeLong(Math.Min(0, 0xffff), Math.Min(-1, 0xffff)));
}
上述代码表明在控件创建后即刻在自身表面播放动画, 本文演示打开Shell32.dll中第162号位置的AVI资源,即删除文件的动画,我们可以通过使用资源查看和编辑软件来更深入的查看倒底哪个动画对应哪个资源位置。

然后,通过Windows消息循环发送消息,告诉Windows如何播放该动画。
控件到这里基本完成,但是细心地网友会发现,这个控件在设计时是无法拖动的!这是为什么呢?原来,我们已经绕过了.Net托管机制,子类化了自定义控件,这里要展开说一下,我们所看到的一切窗体或可视控件,无论它是在运行时还是设计时,其实从Windows平台来讲它们都派生自WNDCLASS类,具体控件可能又派生自BUTTON或者CHECK等等,这个类完全不同于.Net下的类。只不过.Net框架为我们封装了所有与Win32底层操作的方法和类。也就说我们在.Net下写程序时会调用庞大的.Net类库的方法和类,而.Net类库又和底层Win32API进行交互,最终完成和Windows的交互,譬如显示另一个窗体或改变窗体样式等。
回到本文,我们既然已经子类化了自定义控件,那么我们必须对未经处理的Windows消息进行捕获和处理。所以,必须重载WndProc方法:
protected override void WndProc(ref Message m)
{
//必须对点击测试的消息进行处理,否则无法在窗机设计时拖动自定义控件!
if (m.Msg == WM_NCHITTEST)
{
Point point = base.PointToClient(new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 0x10));
if (base.ClientRectangle.Contains(point))
m.Result = new IntPtr(HTCLIENT);
else
m.Result = new IntPtr(HTBORDER);
}
else
base.WndProc(ref m);
}
上述操作其实在MFC和早期Win32开发平台下是比较常见的方法,但是在托管代码环境以及.Net垃圾回收机制下这样做很多东西都必须自己来处理,比如: 1、必须手动处理特定Windows消息,本文中就是指窗体拖动消息,先测试是否在用户区域中,若是则将消息映射为HTCLIENT否则为HTBORDER; 2、必须手动实施内存的申请和释放,因为上述代码不受CLR管理,所以,所有内存的请求都必须手动释放。
protected override void Dispose(bool disposing)
{
if (m_hShellModule != 0)
{
FreeLibrary(m_hShellModule);
m_hShellModule = 0;
}
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
本文示例程序在 Windows XP SP3 + Visual Studio 2008 SP1 下编译调试通过。