windows编程技术10GDI编程3动画Word格式.docx
《windows编程技术10GDI编程3动画Word格式.docx》由会员分享,可在线阅读,更多相关《windows编程技术10GDI编程3动画Word格式.docx(24页珍藏版)》请在冰豆网上搜索。
#defineIDB_BITMAP4134
#defineIDB_BITMAP5135
#defineIDB_BITMAP6136
#defineIDB_BITMAP7137
#defineIDB_BITMAP8138
#defineIDB_BITMAP9139
#defineIDB_BITMAP10140
10.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();
//TODO:
在此添加额外的初始化
for(inti=0;
i<
10;
i++){//装入位图资源
m_pBmp[i]=newCBitmap;
m_pBmp[i]->
LoadBitmap(IDB_BITMAP1+i);
}
m_pBmp[0]->
GetBitmap(&
m_bs);
//获取位图结构
m_bStarted=false;
//设置已开始动画为假
m_nCurFrame=0;
//设置初始的当前帧为0
m_nFramesPerSecond=10;
//设置初始动画速度为每秒10帧
SetDlgItemInt(IDC_N,m_nFramesPerSecond);
//设置编辑框初值
returnTRUE;
//returnTRUEunlessyousetthefocustoacontrol
//异常:
OCX属性页应返回FALSE
其中,语句m_pBmp[i]->
中的IDB_BITMAP1+i用到了Duke位图资源ID的连续性。
为了避免内存泄漏,还需要为对话框类添加析构函数,并在该函数中删除位图对象:
CDukeDlg:
~CDukeDlg()
i++)deletem_pBmp[i];
10.1.4启动/停止动画(设置/删除计时器)
为按钮IDC_ANI_START_STOP添加单击消息响应函数:
voidCDukeDlg:
OnBnClickedAniStartstop(){
在此添加控件通知处理程序代码
if(m_bStarted){//已开始动画
m_bStarted=false;
KillTimer
(1);
//取消计时器
//设置按钮文本为“开始动画”
SetDlgItemText(IDC_ANI_START_STOP,L"
开始动画"
);
else{//未开始动画
m_bStarted=true;
//设置已开始动画为真
m_nCurFrame=0;
////设置当前帧为0
//获取编辑框中的帧速值
m_nFramesPerSecond=GetDlgItemInt(IDC_N);
if(m_nFramesPerSecond<
=0)//限制最小帧速值为1
m_nFramesPerSecond=1;
elseif(m_nFramesPerSecond>
100)//最大帧速值为100
m_nFramesPerSecond=100;
//用调整后帧速值重新设置编辑框的内容
SetDlgItemInt(IDC_N,m_nFramesPerSecond);
//利用帧速值来设置计时器
SetTimer(1,(UINT)(1000.0/m_nFramesPerSecond+0.5),NULL);
//设置按钮文本为“停止动画”
停止动画"
其中:
●m_bStarted为布尔型类变量,用于判断动画是否已经开始播放。
在构造函数中初始化为假,在用户按了开始/停止动画按钮后取反。
●m_nCurFrame为整数型类变量,用于记录当前所要显示的位图序号,初始化为0。
●m_nFramesPerSecond也为整数型类变量,用于记录当前的每秒帧数(帧速值,范围可设为1~100)。
●SetDlgItemText函数,用于动态修改按钮上的文本。
●SetTimer用于设置计时器,它是CWnd的成员函数(所以也可在其派生的视图类和对话框类中使用),其函数原型为:
UINTSetTimer(UINTnIDEvent,UINTnElapse,
void(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消息添加响应函数:
OnTimer(UINTnIDEvent){
在此添加消息处理程序代码和/或调用默认值
CDC*pDC=GetDlgItem(IDC_ANI)->
GetDC();
//获取图片框DC
CDCdc;
//定义内存DC
dc.CreateCompatibleDC(pDC);
//创建兼容DC
dc.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(避免超出位图数组,实现循环)
OnTimer(nIDEvent);
结果如图10-3所示。
图10-3Duke动画
10.1.6滑块控件的使用
为了使用户能够利用鼠标快速修改每秒帧数的值,我们在对话框中添加了一个滑块控件(SliderControl)(可将其ID值设为“IDC_SLIDER_N”)。
对应的MFC类为CSliderCtrl,它是直接从CWnd派生的类:
CObject→CCmdTarget→CWnd→CSliderCtrl。
可以在对话框类中定义一个CSliderCtrl的类指针变量:
CSliderCtrl*m_pSlider;
然后在对话框类的初始化函数中获得滑块控件对象的指针,并设置其取值范围为1~100,再设置其滑块的当前位置(注意:
粗体代码必需位于SetDlgItemInt之前):
OnInitDialog(){
……
m_pSlider=(CSliderCtrl*)GetDlgItem(IDC_SLIDER_N);
m_pSlider->
SetRange(1,100);
SetPos(m_nFramesPerSecond);
为了能在用户移动滑块时,动态修改编辑控件中的值,需要为对话框类添加水平滚动消息(WM_HSCROLL)的响应函数OnHScroll:
OnHScroll(UINTnSBCode,UINTnPos,
CScrollBar*pScrollBar){
SetDlgItemInt(IDC_N,m_pSlider->
GetPos());
OnHScroll(nSBCode,nPos,pScrollBar);
同样,为了在用户修改编辑控件中的值时,动态改变滑块的位置,还需要为对话框类添加编辑控件的EN_CHANGE消息响应函数:
OnEnChangeN(){
SetPos(GetDlgItemInt(IDC_N));
注意,如果滑块初始化的代码位于SetDlgItemInt之后,则会造成此语句中的m_pSlider指针无效。
10.1.7CImageList类
上面的位图动画,也可以使用MFC的图像列表类CImageList来实现,代码会更简单一些。
CImageList类是CObject的直接派生类:
CObject→CImageList
1.成员函数
CImageList类的常用成员函数有:
●构造函数:
CImageList();
●创建函数:
BOOLCreate(intcx,intcy,UINTnFlags,intnInitial,intnGrow);
●添加函数:
intAdd(CBitmap*pbmImage,COLORREFcrMask);
●绘制函数:
BOOLDraw(CDC*pDC,intnImage,POINTpt,UINTnStyle);
2.例子
可在动画对话框类中添加如下代码:
//类变量(头文件)
intm_nCurFrame,m_nFramesPerSecond;
CImageListimgList;
//初始化(OnInitDialog函数)
BITMAPbs;
CBitmapbmp;
bmp.LoadBitmap(IDB_BITMAP1);
bmp.GetBitmap(&
bs);
imgList.Create(bs.bmWidth,bs.bmHeight,ILC_COLOR8,10,0);
imgList.Add(&
bmp,RGB(0,0,0));
for(inti=1;
i++){
bmp.DeleteObject();
bmp.LoadBitmap(IDB_BITMAP1+i);
imgList.Add(&
bmp.DeleteObject();
//绘图(OnTimer函数)
imgList.Draw(pDC,m_nCurFrame,CPoint(0,0),ILD_NORMAL);
//OnBnClickedAniStartStop、OnHScroll和OnEnChangeN函数同前
10.1.8过程框图
下面分别给出使用位图数组和CImageList类来实现固定位图动画的主要过程框图。
1.使用位图数组
图10-4是使用位图数组实现固定位图动画的主要过程框图。
图10-4使用位图数组实现固定位图动画的主要步骤
2.使用图像列表
图10-5是使用CImageList类实现固定位图动画的主要过程框图。
图10-5使用CImageList类实现固定位图动画的主要步骤
10.2图形动画
在前面(参见8.5.3小节)利用鼠标进行交互绘图时,我们就已经实现了简单的图形动画——动态画直线、矩形或椭圆等,用户可通过鼠标对绘图位置坐标和图形大小进行交互式选择。
具体做法是,用灰色点线笔在同一个位置异或画两次一样的图形——第一次画图,第二次擦除。
快速不断地在不同的地方画擦,就达到了动画的效果。
即图形动画的原理就是边擦边画。
如果要画的不再是简单的线状图形,而是复杂的面状图,则异或画图方法就不再有效。
因为异或会大大改变窗口中原有图形的色彩,这是用户所不能容忍的。
可用的解决方法是,擦除(或保存)要绘图的区域,然后再绘制新图形(并恢复原区域的图形)。
具体的实现方法有两种——直接绘图和缓冲绘图。
10.2.1直接绘图
利用直接绘图方法,来产生动画的原理很简单,但是会存在讨厌的闪烁现象。
1.原理
通过不断地擦除(要绘图的区域)和绘制(新图形)动态图形而产生动画效果。
可以使用CDC类的画填充矩形的函数(使用白色刷):
voidFillRect(LPCRECTlpRect,CBrush*pBrush);
来擦除指定矩形区域。
例如:
pDC->
FillRect(&
rect,newCBrush(RGB(255,255,255)));
然后,再在该矩形区域内绘制新图形。
pDC->
SelectObject(&
pen);
//选入画边框的笔
brush);
//选入画填充色的刷
Ellipse(&
rect);
//绘制填充椭圆
下面是一个在白色背景上动态画伸缩填充椭圆的例子,需要创建一个传统单文档MFC应用程序。
主要代码片段如下:
1)在视图类中定义若干类变量:
boolshrink;
//用于判断伸缩
intr,w,h,//当前椭圆的短轴半径和宽高
R,W,H,//最大椭圆的短轴半径和宽高
xc,yc;
//椭圆的中心坐标
CPenpen;
//绘制椭圆边框的笔(与刷同色)
CBrushbrush,whiteBrush;
//绘制椭圆内部的刷和删除原椭圆的白刷
2)在视图类的构造函数中,设置初值、构造笔和刷:
shrink=true;
//初始为缩小
COLORREFgreenCol=RGB(0,150,0),//定义绿色
whiteCol=RGB(255,255,255);
//定义白色
pen.CreatePen(0,0,greenCol);
//实心单像素宽的绿色笔
brush.CreateSolidBrush(greenCol);
//实心绿色刷
whiteBrush.CreateSolidBrush(whiteCol);
//实心白色刷
3)在某个菜单项的事件处理函数中计算并设置初值、启动计时器:
CRectrect;
GetClientRect(&
//获取当前客户区矩形
W=rect.Width();
H=rect.Height();
r=R=min(W,H)/2;
//初始为最大椭圆
w=W/2;
h=H/2;
xc=W/2;
yc=H/2;
SetTimer(1,10,NULL);
//可设置不同的时间间隔,或者让用户来设置
4)在计时器的消息响应函数OnTimer中,擦除并绘制椭圆,调整半径:
CDC*pDC=GetDC();
//擦除
if(shrink){//对膨胀不需要擦除
CRectrect(xc-w,yc-h,xc+w,yc+h);
FillRect(rect,&
whiteBrush);
//调整半径
if(shrink){
w--;
h--;
r--;
//缩小1像素
if(r==0)shrink=false;
//转换成放大
}else{
w++;
h++;
r++;
//放大1像素
if(r==R)shrink=true;
//转换成缩小
Ellipse(xc-w,yc-h,xc+w,yc+h);
ReleaseDC(pDC);
运行结果如图10-6所示。
图10-6伸缩填充椭圆
运行该程序后会发现,存在明显的闪烁现象,这主要是由收缩时的擦除操作所造成的。
解决办法是,采用内存DC进行缓冲绘图。
10.2.2缓冲绘图
在前面资源位图动画的绘制过程中,我们已经采用了缓冲(buffering)方法来显示位图:
BitBlt(0,0,bs.bmWidth,bs.bmHeight,&
d