1第10章GDI编程3-动画动画是利用人的视觉滞留缺陷(25ms~400ms)和心理认可来动态生成系列相关画面以产生运动视觉的技术。位图动画是将预先制作好的一系列表示连续画面的位图,按一定的时间间隔一幅接一幅地连续显示,从而产生动画效果。因为绘制动画所需的图形,以及拍摄和处理图片,需要美术、摄影、数字图像处理、动画设计等知识,我们这里不讲。本书只介绍如何显示已有的位图(序列)以产生动画效果,以及如何动态绘制不同的简单图形以产生二维图形动画等。用GDI编程实现动画,一般需要用到计时器(Timer)操作,通常在计时器响应函数OnTimer中(而不要使用OnDraw)绘图来实现动画。10.1固定位图动画本节介绍利用一系列的位图资源,在同一个屏幕位置,接连显示位图序列,以达到动画的效果的具体方法。为此,可在交互绘图程序中添加一个如图10-1所示的位图动画对话框,并添加对应的对话框类CDukeDlg。也可以创建一个基于对话框的独立的MFC应用程序。图10-1位图动画对话框资源当然还需添加相应的“位图动画”菜单项(ID_DUKE)和(为视图类添加)对应的菜单响应函数,并在该函数中创建对话框类的对象,打开对话框来运行动画:#includeDukeDlg.hvoidCDrawView::OnDuke(){CDukeDlgdlg;dlg.DoModal();2}10.1.1准备位图、加入位图资源系列公爵(Duke)BMP文件T1.BMP~T10.BMP(见图10-2),来自Java吉祥物的GIF动画文件,可存放在项目的res子目录的Duke子目录中(该位图资源已经打包成Duke.rar文件后,放到了系里的网站和我的个人网页上)。T1.BMPT2.BMPT3.BMPT4.BMPT5.BMPT6.BMPT7.BMPT8.BMPT9.BMPT10.BMP图10-2Duke位图文件用VC的资源编辑器依次加入位图文件:在左边的项目工作区中选“资源视图”页,展开项目的资源列表,在“Bitmap”表项(若无此项,则可直接在项目资源项)上单击鼠标右键,在弹出的浮动菜单中选“添加资源”菜单项,在打开的“添加资源”对话框中,选中左边“资源类型”栏中的“Bitmap”表项,单击右边的“导入”按钮,在弹出的“导入”文件对话框中,定位Duke目录,选中所有Ti.BMP后按“打开”钮,则会自动加入ID为IDB_BITMAPi的位图资源。为了以后循环编程的方便,必须保证是从T1.BMP到T10.BMP顺序依次加入。为了确认,可打开头文件Resource.h查看,若其中的常量IDB_BITMAPi的定义数值不连续,可作一些手工修改使其连续,例如:#defineIDB_BITMAP1131#defineIDB_BITMAP2132#defineIDB_BITMAP3133#defineIDB_BITMAP4134#defineIDB_BITMAP5135#defineIDB_BITMAP61363#defineIDB_BITMAP7137#defineIDB_BITMAP8138#defineIDB_BITMAP9139#defineIDB_BITMAP1014010.1.2添加控件、创建对话框类为对话框资源添加图片控件:打开对话框资源,在控件工具箱中选图片控件(PictureControl)工具,在对话框的适当位置添加图片控件,设置其“ID”属性值为“IDC_ANI”,修改“Type”属性为(在其下拉式列表框中选中)“Bitmap”,再在“Image”属性的下拉式列表框中选中“IDB_BITMAP1”位图资源,则该位图绘显示在图片控件中。为了控制动画的播放,需要添加一个即可表示开始动画又可表示停止动画的按钮(初始标题为“开始动画”),可设置其ID为“IDC_ANI_START_STOP”。为了让用户选择动画的速度,可以添加静态文本提示框“每秒帧数:”和文本编辑框(IDC_N),在后面的10.1.6小节中还会添加滑块控件(SliderControl,IDC_SLIDER_N)。创建该对话框资源所对应的对话框类CDukeDlg。10.1.3添加类变量、装入与删除位图在对话框类的定义(头文件)中添加若干类变量:CBitmap*m_pBmp[10];//位图指针数组BITMAPm_bs;//位图结构变量boolm_bStarted;//判别动画是否启动(初始化为false)intm_nCurFrame,//当前帧号(初值为0)m_nFramesPerSecond;//每秒帧数(初值为10)为CDukeDlg类添加(重写型)消息响应函数OnInitDialog,在该函数中(也可以在构造函数中)创建位图对象并装入位图资源,然后获取位图结构(其中的位图宽和高用于BitBlt函数),并初始化其他类变量,最后设置编辑控件的初值(粗体部分为新加的):BOOLCDukeDlg::OnInitDialog(){CDialog::OnInitDialog();4//TODO:在此添加额外的初始化for(inti=0;i10;i++){//装入位图资源m_pBmp[i]=newCBitmap;m_pBmp[i]-LoadBitmap(IDB_BITMAP1+i);}m_pBmp[0]-GetBitmap(&m_bs);//获取位图结构m_bStarted=false;//设置已开始动画为假m_nCurFrame=0;//设置初始的当前帧为0m_nFramesPerSecond=10;//设置初始动画速度为每秒10帧SetDlgItemInt(IDC_N,m_nFramesPerSecond);//设置编辑框初值returnTRUE;//returnTRUEunlessyousetthefocustoacontrol//异常:OCX属性页应返回FALSE}其中,语句m_pBmp[i]-LoadBitmap(IDB_BITMAP1+i);中的IDB_BITMAP1+i用到了Duke位图资源ID的连续性。为了避免内存泄漏,还需要为对话框类添加析构函数,并在该函数中删除位图对象:CDukeDlg::~CDukeDlg(){for(inti=0;i10;i++)deletem_pBmp[i];}10.1.4启动/停止动画(设置/删除计时器)为按钮IDC_ANI_START_STOP添加单击消息响应函数:voidCDukeDlg::OnBnClickedAniStartstop(){//TODO:在此添加控件通知处理程序代码if(m_bStarted){//已开始动画m_bStarted=false;//设置已开始动画为假KillTimer(1);//取消计时器5//设置按钮文本为“开始动画”SetDlgItemText(IDC_ANI_START_STOP,L开始动画);}else{//未开始动画m_bStarted=true;//设置已开始动画为真m_nCurFrame=0;////设置当前帧为0//获取编辑框中的帧速值m_nFramesPerSecond=GetDlgItemInt(IDC_N);if(m_nFramesPerSecond=0)//限制最小帧速值为1m_nFramesPerSecond=1;elseif(m_nFramesPerSecond100)//最大帧速值为100m_nFramesPerSecond=100;//用调整后帧速值重新设置编辑框的内容SetDlgItemInt(IDC_N,m_nFramesPerSecond);//利用帧速值来设置计时器SetTimer(1,(UINT)(1000.0/m_nFramesPerSecond+0.5),NULL);//设置按钮文本为“停止动画”SetDlgItemText(IDC_ANI_START_STOP,L停止动画);}}其中:m_bStarted为布尔型类变量,用于判断动画是否已经开始播放。在构造函数中初始化为假,在用户按了开始/停止动画按钮后取反。m_nCurFrame为整数型类变量,用于记录当前所要显示的位图序号,初始化为0。m_nFramesPerSecond也为整数型类变量,用于记录当前的每秒帧数(帧速值,范围可设为1~100)。SetDlgItemText函数,用于动态修改按钮上的文本。SetTimer用于设置计时器,它是CWnd的成员函数(所以也可在其派生的视图类和对话框类中使用),其函数原型为:UINTSetTimer(UINTnIDEvent,UINTnElapse,6void(CALLBACKEXPORT*lpfnTimer)(HWND,UINT,UINT,DWORD));nIDEvent为此计时器的编号。因为一个应用程序可以设置多个计时器,为了在响应时区分它们,必须各有一个编号。简单程序的计时器一般只有一个,所以取nIDEvent=1即可。nElapse为间隔时间,单位为毫秒(1/1000秒)。可用表达式(UINT)(1000.0/m_nFramesPerSecond+0.5),从每秒帧数计算出间隔时间的毫秒数。lpfnTimer为应用程序提供的处理WM_TIMER消息的回调函数,一般取为NULL,这时WM_TIMER消息由CWnd派生类的对应消息响应函数来处理。KillTimer用于删除计时器,它也是CWnd的成员函数,其函数原型为:BOOLKillTimer(intnIDEvent);在设置了计时器后,系统会按指定的时间间隔发送WM_TIMER消息给应用程序窗口,程序员可在计时器消息响应函数OnTimer中作需要的处理,在本程序中是显示当前位图。10.1.5绘制动画(响应计时器消息)为CDukeDlg类的WM_TIMER消息添加响应函数:voidCDukeDlg::OnTimer(UINTnIDEvent){//TODO:在此添加消息处理程序代码和/或调用默认值CDC*pDC=GetDlgItem(IDC_ANI)-GetDC();//获取图片框DCCDCdc;//定义内存DCdc.CreateCompatibleDC(pDC);//创建兼容DCdc.SelectObject(m_pBmp[m_nCurFrame]);//选入当前帧位图pDC-BitBlt(0,0,m_bs.bmWidth,m_bs.bmHeight,&dc,0,0,SRCCOPY);//显示当前帧位图m_nCurFrame++;//当前帧加一(设置下一次要显示的位图序号)m_nCurFrame%=10;//当前帧余10(避免超出位图数组,实现循环)CDialog::OnTimer(nIDEvent);}结果如图10-3所示。7图10-3Duke动画10.1.6滑块控件的使用为了使用户能够利用鼠标快速修改每秒帧数的值,我们在对话框中添加了一个滑块控件(SliderControl)(可将其ID值设为“IDC_SLIDER_N”)。对应的MFC类为CSliderCtrl,它是直接从CWnd派生的类:CObject→CCmdTarget→CWnd→CSliderCtrl。可以在对话框类中定义一个CSliderCtrl的类指针变量:CSliderCtrl*m_pSlider;然后在对话框类的初始化函数中获得滑块控件对象的指针,并设置其取值范围为1~100,再设置其滑块的当前位置(注意:粗体代码必需位于SetDlgItemInt之前):BOOLCDukeDlg::OnInitDialog(){CDialog::OnInitDialog();//TODO:在此添加额外的初始化……m_pSlider=(CSliderCtrl*)GetDlgItem(IDC_SLIDER_N);m_pSlider-SetRange(1,100);m_pSlider-SetPos(m_nFramesPerSecond);SetDlgItemInt(IDC_N,m_nFramesPerSecond);……}为了能在用户移动滑