| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 1622 人关注过本帖
标题:鼠标操作的封装和例子
取消只看楼主 加入收藏
hoodlum1980
Rank: 2
来 自:浙江大学
等 级:论坛游民
威 望:2
帖 子:289
专家分:23
注 册:2008-2-24
收藏
 问题点数:0 回复次数:3 
鼠标操作的封装和例子
我看了一些鼠标操作的例子,但是我觉得对现有的鼠标封装还不够满意,我希望能更容易的把鼠标操作封装到一些程序中,比如一些小游戏,使用起来就像在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,&regs,&regs);
    regs.x.ax=0x08;/*8号0x33中断:设置垂直位置最大值*/
    regs.x.cx=miny;
    regs.x.dx=maxy;
    int86(0x33,&regs,&regs);
}

/*初始化鼠标*/
int mouse_init(void)
{
    regs.x.ax=0x00;
    int86(0x33,&regs,&regs);
    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,&regs,&regs);
     *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]]

连连看_jfd.rar (99.22 KB) 连连看

搜索更多相关主题的帖子: 鼠标 例子 封装 
2008-09-05 10:55
hoodlum1980
Rank: 2
来 自:浙江大学
等 级:论坛游民
威 望:2
帖 子:289
专家分:23
注 册:2008-2-24
收藏
得分:0 
[bo][un]StarWing83[/un] 在 2008-9-5 14:41 的发言:[/bo]

大哥,你把函数和数据写在头文件里面???

就算是VC,也是需要一个__declspec(selectany)的,你这样,如果有两个C文件a.c和b.c都包含了这个头文件,而后链接在一起,就会出链接错误。

如果确实想这么做,用VC ...


当然会考虑这种问题,.h文件中有预编译指令的。你可以试验一下,同时inluce它多次,比如
#include <mouse.h>
#include <mouse.h>
看看是不是会有编译错误。我如果连这都不知道,我还写什么头文件啊。

我得特别说明一下,这是专门给用tc的场合写的。如果用vc,当然不需要这么麻烦。同时也引用我回答一些人的疑问,既然用tc写大程序这么麻烦,而且难度远大于用vc,那为什么还规定必须用tc来做c语言的大程序呢。我个人觉得,当你用c语言做过大程序,将会对你的基本功有极大的考验和增强。这是非常重要的。写这些小游戏当然不是终极目的,windows下的程序要比这更好更多。

[[it] 本帖最后由 hoodlum1980 于 2008-9-5 18:18 编辑 [/it]]
2008-09-05 18:11
hoodlum1980
Rank: 2
来 自:浙江大学
等 级:论坛游民
威 望:2
帖 子:289
专家分:23
注 册:2008-2-24
收藏
得分:0 
[bo][un]StarWing83[/un] 在 2008-9-5 14:41 的发言:[/bo]

大哥,你把函数和数据写在头文件里面???

就算是VC,也是需要一个__declspec(selectany)的,你这样,如果有两个C文件a.c和b.c都包含了这个头文件,而后链接在一起,就会出链接错误。

如果确实想这么做,用VC ...


如果链接成多个obj,在链接会出现问题。那你在VC里面自己可以把它拆分为头文件和源码文件,不过你include头文件的时候,如果存在多个对这个模块的引用,仍有可能需要指定某些变量和函数为extern的。
2008-09-05 18:32
hoodlum1980
Rank: 2
来 自:浙江大学
等 级:论坛游民
威 望:2
帖 子:289
专家分:23
注 册:2008-2-24
收藏
得分:0 
[bo][un]StarWing83[/un] 在 2008-9-5 14:41 的发言:[/bo]

大哥,你把函数和数据写在头文件里面???

就算是VC,也是需要一个__declspec(selectany)的,你这样,如果有两个C文件a.c和b.c都包含了这个头文件,而后链接在一起,就会出链接错误。

如果确实想这么做,用VC ...


不好意思,昨天没有休息好,心情有些焦躁。你说的是有道理的。不过我不知道在tc下面如果我要提供一些函数该怎么提供一个dll。可能tc也不支持动态链接库的方式,而且tc2.0里面它也不支持类。

另外__declspec是一个微软的规范,所以可能只适用于微软的IDE。

昨天我又发现进度条的更新绘制是不正确的,调试了下发现原因仅仅是这样的一个非常不起眼的代码,导致int类型溢出,
left = BoardX + (int)(BoardWidth*percent*CellSize*0.01);
这里,BoardWidth=10,percent=100,CellSize=48
前三个数字乘法的结果是48000,用16进制表示是0xBB80,显然已经超出了有符号整数(__int16)的正数表达范围,导致结果变成了负数。。。
修正这个bug也很简单,只要把*0.01提前就可以了。(附件和程序已经更新,更新了进度条的绘制方式,现在不会闪烁)


/*计算进度条的新的右边界, 注意必须先乘以0.01,否则int会溢出!!!*/
left = BoardX + (int)(BoardWidth*0.01*percent*CellSize);

[[it] 本帖最后由 hoodlum1980 于 2008-9-6 10:20 编辑 [/it]]
2008-09-06 10:19
快速回复:鼠标操作的封装和例子
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.043707 second(s), 9 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved