鼠标操作的封装和例子
我看了一些鼠标操作的例子,但是我觉得对现有的鼠标封装还不够满意,我希望能更容易的把鼠标操作封装到一些程序中,比如一些小游戏,使用起来就像在VC下面添加事件处理函数那样方便。因此我做了这样的尝试:下面是这样的代码,参考了一些书本上的例子以及这个论坛的其他人发的代码,我在这些基础上改写成这样的一个头文件:
程序代码:
/* * 封装鼠标操作 * Author: hoodlum1980 * Date: 2008.09.04 * Email: jinfd@* 说明: */ #ifndef __MOUSE_H_INCLUDED_ #define __MOUSE_H_INCLUDED_ #include <dos.h> #include <graphics.h> #include <time.h> #include <math.h> #include <stdlib.h> /*鼠标事件处理*/ enum MOUSE_EVENT_CODE { NOEVENT=0, MOUSEMOVE=10, LBUTTONDOWN=11, LBUTTONUP=12, LBUTTONDBLCLK=23, RBUTTONDOWN=24, RBUTTONUP=25, RBUTTONDBLCLK=26, }; /*鼠标按键*/ enum MOUSE_BUTTON { LEFTBUTTON=0, RIGHTBUTTON=1, MIDDLEBUTTON=2, }; /*光标形状*/ enum CURSOR_TYPE { ARROW=0,/*箭头*/ IBEAM=1,/*I型*/ }; #define DoubleClickTime 0.2 /*鼠标双击的最大时间间隔(每秒产生18.2次clock)*/ /*定义函数指针*/ typedef void (*MOUSE_EVENT_HANDLER)(int x, int y); /*定义事件处理函数链的节点*/ typedef struct _tagCHAIN_NODE { MOUSE_EVENT_HANDLER handler; struct _tagCHAIN_NODE *next; } CHAIN_NODE; /*鼠标样子,注意0为黑色,15为白色!12为不绘制的颜色!*/ /*含有两个形状的鼠标,0为箭头状,1为I状。*/ int cursors[2][20][12]= { /* ARROW CURSOR */ { 12,12,12,12,12,12,12,12,12,12,12,12, 12, 0,12,12,12,12,12,12,12,12,12,12, 12, 0, 0,12,12,12,12,12,12,12,12,12, 12, 0,15, 0,12,12,12,12,12,12,12,12, 12, 0,15,15, 0,12,12,12,12,12,12,12, 12, 0,15,15,15, 0,12,12,12,12,12,12, 12, 0,15,15,15,15, 0,12,12,12,12,12, 12, 0,15,15,15,15,15, 0,12,12,12,12, 12, 0,15,15,15,15,15,15, 0,12,12,12, 12, 0,15,15,15,15,15,15,15, 0,12,12, 12, 0,15,15,15,15,15,15,15,15, 0,12, 12, 0,15,15,15,15,15, 0, 0, 0, 0, 0, 12, 0,15,15, 0,15,15, 0,12,12,12,12, 12, 0,15, 0,12, 0,15,15, 0,12,12,12, 12, 0, 0,12,12, 0,15,15, 0,12,12,12, 12, 0,12,12,12,12, 0,15,15, 0,12,12, 12,12,12,12,12,12, 0,15,15, 0,12,12, 12,12,12,12,12,12,12, 0,15,15, 0,12, 12,12,12,12,12,12,12, 0,15,15, 0,12, 12,12,12,12,12,12,12,12, 0, 0,12,12 }, /* IBEAM CURSOR */ { 12,12,12,12,12,12,12,12,12,12,12,12, 12,12,12,12,12,12,12,12,12,12,12,12, 0, 0, 0, 0, 0,12,12,12,12,12,12,12, 12,12, 0,12,12,12,12,12,12,12,12,12, 12,12, 0,12,12,12,12,12,12,12,12,12, 12,12, 0,12,12,12,12,12,12,12,12,12, 12,12, 0,12,12,12,12,12,12,12,12,12, 12,12, 0,12,12,12,12,12,12,12,12,12, 12,12, 0,12,12,12,12,12,12,12,12,12, 12,12, 0,12,12,12,12,12,12,12,12,12, 12,12, 0,12,12,12,12,12,12,12,12,12, 12,12, 0,12,12,12,12,12,12,12,12,12, 12,12, 0,12,12,12,12,12,12,12,12,12, 12,12, 0,12,12,12,12,12,12,12,12,12, 12,12, 0,12,12,12,12,12,12,12,12,12, 12,12, 0,12,12,12,12,12,12,12,12,12, 12,12, 0,12,12,12,12,12,12,12,12,12, 0, 0, 0, 0, 0,12,12,12,12,12,12,12, 12,12,12,12,12,12,12,12,12,12,12,12, 12,12,12,12,12,12,12,12,12,12,12,12 } }; /*全局变量,缓存鼠标信息*/ int mousePosX=-100; int mousePosY=-100; int mouseButton=0;/*最近按键*/ clock_t LastMouseEventTime;/*上一次鼠标时间的时间*/ clock_t CurMouseEventTime;/*当前鼠标事件的时间*/ union REGS regs; /*指向临时保存的图案*/ char *img_buf=NULL; /*当前光标形状*/ int CursorType=ARROW; /*事件处理函数链*/ CHAIN_NODE *CH_MouseMove=NULL; CHAIN_NODE *CH_LButtonDown=NULL; CHAIN_NODE *CH_LButtonUp=NULL; CHAIN_NODE *CH_LButtonDblClk=NULL; CHAIN_NODE *CH_RButtonUp=NULL; CHAIN_NODE *CH_RButtonDown=NULL; CHAIN_NODE *CH_RButtonDblClk=NULL; /*函数列表*/ CHAIN_NODE** GetChainByEvent(int event); void AddHandler(MOUSE_EVENT_HANDLER handler, int event);/*重要函数,挂接事件处理函数*/ void RemoveHandler(MOUSE_EVENT_HANDLER handler, int event);/*移除一个事件处理函数*/ void CallChainHandlers(CHAIN_NODE *chain, int x, int y);/*依次调用调用链上的所有处理函数*/ void set_mouse_bounds(int minx,int miny,int maxx,int maxy);/*设置鼠标活动区域*/ int mouse_init(void);/*初始化*/ void get_cursor_pos(int *pX, int *pY);/*重要函数,把这个函数加入主程序循环体*/ void erase_mouse(int x, int y);/*在绘制屏幕前请关闭鼠标,绘制完毕后打开鼠标!*/ void draw_mouse(int x, int y); void set_cursor(int cursorType);/*设置光标类型*/ void DoMouseEvents(); /*重要函数,把这个函数加入主程序循环体*/ /*根据事件,返回相应的Chain的指针*/ CHAIN_NODE** GetChainByEvent(int event) { CHAIN_NODE** pChain; switch(event) { case MOUSEMOVE: pChain=&CH_MouseMove; break; case LBUTTONDOWN: pChain=&CH_LButtonDown; break; case LBUTTONUP: pChain=&CH_LButtonUp; break; case LBUTTONDBLCLK: pChain=&CH_LButtonDblClk; break; case RBUTTONDOWN: pChain=&CH_RButtonDown; break; case RBUTTONUP: pChain=&CH_RButtonUp; break; case RBUTTONDBLCLK: pChain=&CH_RButtonDblClk; break; default: pChain=NULL; break; } return pChain; } /*增加事件处理函数*/ void AddHandler(MOUSE_EVENT_HANDLER handler, int event) { CHAIN_NODE *pNode, **pChain; if(handler==NULL) return; /*新申请一个节点*/ pNode=(CHAIN_NODE*)malloc(sizeof(CHAIN_NODE)); if(pNode==NULL) return; pNode->handler=handler;/*设置函数指针!*/ /*根据event判断要插入哪一个链表*/ pChain=GetChainByEvent(event); /*把新节点插入到链表头部位置*/ /* NewNode -- OldHead -- ... -- ... -- null */ if(pChain!=NULL) { pNode->next=*pChain; *pChain=pNode; } } /*移除一个Handler! 请注意移除首节点时要特殊处理!*/ void RemoveHandler(MOUSE_EVENT_HANDLER handler, int event) { CHAIN_NODE *pNode=NULL, *pParent=NULL, **pChain=NULL; if(handler==NULL) return; /*根据event判断要插入哪一个链表*/ pChain=GetChainByEvent(event); /*把新节点插入到链表头部位置*/ /* NewNode -- OldHead -- ... -- ... -- null */ if(pChain==NULL || *pChain==NULL) return; /*搜索该节点!注意,需要临时存储当前节点的父节点!*/ for(pNode=*pChain; (pNode!=NULL) && (pNode->handler != handler); pParent=pNode, pNode=pNode->next); /*如果要移除的是链表的头部节点,则需要特殊处理*/ if(pNode==*pChain && pNode!=NULL) { *pChain=pNode->next; free(pNode); return; } if(pNode!=NULL && pNode->handler==handler) { if(pParent!=NULL) pParent->next = pNode->next; /*自己的父节点 连接 自己的子节点,从而自己脱链!*/ pNode->next=NULL; pNode->handler=NULL; free(pNode);/*释放内存*/ pNode=NULL; /*重要!不能省略!*/ } } /*依次调用某个链表的所有节点*/ void CallChainHandlers(CHAIN_NODE *chain, int x, int y) { CHAIN_NODE *pNode; for(pNode=chain; pNode!=NULL; pNode=pNode->next) (*(pNode->handler))(x,y); } /*设定鼠标活动范围*/ void set_mouse_bounds(int minx,int miny,int maxx,int maxy) { regs.x.ax=0x07; /*7号0x33中断:设置水平位置最大值*/ regs.x.cx=minx; regs.x.dx=maxx; int86(0x33,®s,®s); regs.x.ax=0x08;/*8号0x33中断:设置垂直位置最大值*/ regs.x.cx=miny; regs.x.dx=maxy; int86(0x33,®s,®s); } /*初始化鼠标*/ int mouse_init(void) { regs.x.ax=0x00; int86(0x33,®s,®s); if(regs.x.ax==0) return 0; /*set_mouse_bounds(0,0,639,479);*/ return 1; } /*攻取鼠标坐标*/ void get_cursor_pos(int *pX, int *pY) { regs.x.ax=0x03; int86(0x33,®s,®s); *pX=regs.x.cx; *pY=regs.x.dx; } /*清除鼠标移动痕迹*/ void erase_mouse(int x, int y) { if(img_buf==NULL) return; putimage(x, y, img_buf, 0); free(img_buf); img_buf=NULL; } /*绘制鼠标,会自动擦除旧的位置的光标,并更新mousePosX和mousePoxY!*/ void draw_mouse(int x, int y) { int i,j,tx,ty; /*get_cursor_pos(&tx,&ty); if(tx==mousePosX && ty==mousePosY) return; erase_mouse(); mousePosX=tx; mousePosY=ty; */ tx = x+11>639? 639:x+11; ty = y+19>479? 479:y+19; img_buf=(char *)malloc(imagesize(x, y, tx, ty)); getimage(x, y,tx,ty,img_buf); if(x+11>639) tx=12-(x+11)%639; else tx=12; if(y+19>479) ty=20-(y+19)%479; else ty=20; /*12相当于透明色!*/ for(i=0;i<ty;i++) for(j=0;j<tx;j++) if(cursors[CursorType][i][j]!=12) putpixel(x+j, y+i,cursors[CursorType][i][j]); } /*改变鼠标样子,kind=0:箭头状光标,kind=1:I状光标*/ void set_cursor(int cursorType) { CursorType=cursorType; } /*重要函数,处理鼠标事件!*/ void DoMouseEvents() { int event=0; int oldMouseX,oldMouseY; CHAIN_NODE **pChain; oldMouseX = mousePosX,oldMouseY = mousePosY; /*保存当前鼠标的位置*/ _AX=0x03; /*读取鼠标按钮状态*/ geninterrupt(0x33); /*产生33号鼠标软中断*/ if((_BX&1) && !(_BX&2)) /*鼠标左键被按下同时右键没有按下*/ event=LBUTTONDOWN; if((_BX&2) && !(_BX&1)) /*鼠标右键被按下同时左键没有按下*/ event=RBUTTONDOWN; /*鼠标左键和右键同时被按下*/ if(_BX&1 && _BX&2) event=103; _AX=0x06; /*读取鼠标按钮释放信息*/ _BX=0x00; /*指定读取鼠标左键的释放信息*/ geninterrupt(0x33); /*产生33号鼠标软中断*/ if(_BX==1) /*如果鼠标左键的释放数为1*/ event=LBUTTONDOWN; /*产生一个单击左键信息*/ _AX=0x06; /*读取鼠标按钮释放信息*/ _BX=0x01; /*指定读取鼠标右键的释放信息*/ geninterrupt(0x33); /*产生33号鼠标软中断*/ if(_BX==1) /*如果鼠标左键的释放次数为1*/ event=RBUTTONDOWN; /*产生一个单击右键信息*/ /*GetMouseXY();*/ /*获得当前鼠标位置,并把数据赋给MouseX,MouseY*/ get_cursor_pos(&mousePosX, &mousePosY); /*鼠标双击的判断*/ if(event==103) /*如果是同时按下鼠标的左键*/ mouseButton=0; /*上一次的按键是既不是鼠标左键也不是鼠标右键*/ else if(event==RBUTTONDOWN) /*如果是按下鼠标的右键*/ { if(mouseButton == RIGHTBUTTON) /*如果上一次也是按下鼠标的右键*/ { CurMouseEventTime=clock();/*获得现在的程序运行时间*/ /*判断上一次按下鼠标的右键到这次按下鼠标右键的时间间隔是不是小于最大鼠标双击时间间隔*/ if(((CurMouseEventTime-LastMouseEventTime)/CLK_TCK)<=DoubleClickTime) { if(fabs(oldMouseX-mousePosX)<=16 && fabs(oldMouseY-mousePosY)<=16) event=RBUTTONDBLCLK; /*鼠标消息为右键双击*/ } LastMouseEventTime=CurMouseEventTime; } else { mouseButton=RIGHTBUTTON; /*作为下一次的按键判断时的上一次按键状态*/ CurMouseEventTime=clock(); /*作为下一次的按键判断时的上一次按键时间*/ LastMouseEventTime=CurMouseEventTime; } } else if(event==LBUTTONDOWN) { if(mouseButton==LEFTBUTTON) { CurMouseEventTime=clock();/*获得现在的程序运行时间*/ /*判断上一次按下鼠标的左键到这次按下鼠标左键的时间间隔是不是小于最大鼠标双击时间间隔*/ if(((CurMouseEventTime-LastMouseEventTime)/CLK_TCK)<=DoubleClickTime) { if(fabs(oldMouseX-mousePosX)<=16 && fabs(oldMouseY-mousePosY)<=16) event=LBUTTONDBLCLK; /*鼠标消息为左键双击*/ } LastMouseEventTime=CurMouseEventTime; } else { mouseButton=LEFTBUTTON; /*作为下一次的按键判断时的上一次按键状态*/ CurMouseEventTime=clock(); /*作为下一次的按键判断时的上一次按键时间*/ LastMouseEventTime=CurMouseEventTime; } } /*event;*/ /*返回鼠标按键消息:0--没有按键,1--单击右键, 2--单击左键,3--同时按下左键和右键, 4--拖曳左键,5--拖曳右键,6--双击右键,7--双击左键*/ if(oldMouseX!=mousePosX || oldMouseY!=mousePosY) { /*实时显示*/ erase_mouse(oldMouseX, oldMouseY); draw_mouse(mousePosX, mousePosY); /*处理鼠标移动消息*/ CallChainHandlers(CH_MouseMove, mousePosX, mousePosY); } /*调用处理函数链表*/ pChain=GetChainByEvent(event); CallChainHandlers(*pChain, mousePosX, mousePosY); } #endif /* end of #ifndef __MOUSE_H_INCLUDED_ */
首先,可以肯定的是,这是我心急情况下写出来的结果,基本上可以达到我预期的想法。
但我对这个结果依然很不满意。因为要使用它,依然需要了解一些细节,比如说要更新桌面上的图形,必须先关闭鼠标,绘制完成后再打开鼠标。(这是因为绘图时可能造成缓存的image变为Invalid。)
显然,现在封装的依然不够好,我希望能在更多时间里认真检查现在的逻辑,对它继续完善。
它使用起来类似这样:
#include <...\mouse.h> 我们把它引进来。
我们做一些初始化的工作:
mouse_init();
如果没有安装会返回0.
set_mouse_bounds(0,0,640,480); 设置鼠标活动范围
然后我们写一个事件处理函数:
void OnLButtonDown(int x, int y)
{
erase_mouse(x,y); //注意如果要绘制屏幕这里必须临时关闭鼠标!!!
//在下面这里更新屏幕等操作
。。。。
draw_mouse(x,y); //绘制完成再显示鼠标。。。。。
}
我们在程序的主循环之前,把我们的事件处理函数插入到事件处理链表的头部中,就像在VC中那样类似:
AddHandler(OnLButtonDown, LBUTTONDOWN);
第一个参数是我们的事件处理函数的指针,第二个参数是事件的类型。
然后我们在我们的程序主循环(也就是游戏中的那个死循环)中加一句调用,比如说:
while(!kbhit())
{
DoMouseEvents();//这一句会处理鼠标的事件,会引发对我们自己添加的事件处理函数的调用
//接着我们做一些帧更新的处理,和其他主循环逻辑。
}
这样即可以了。
下面是引入鼠标操作的连连看2的代码和编译文件,把里面的文件夹解压到C盘即可(这是因为图片资源的路径在程序中是硬编码)。
在这个游戏中,主要通过单击鼠标左键来操作,如果单击鼠标右键,将在屏幕上绘制一个白色圆(作为示范)。
(刚发帖子时,RemoveHandler代码中有一个BUG,导致删除处理函数后再调用这链表会使程序崩溃,我已经修正并更新。)
[[it] 本帖最后由 hoodlum1980 于 2008-9-11 11:06 编辑 [/it]]