让你的VC也支持手势----【手势扒手】制作过程 下 - gangyilovevc的专栏 ...
来源:百度文库 编辑:神马文学网 时间:2024/04/20 22:43:17
让你的VC也支持手势----【手势扒手】制作过程 下 接上篇,好,我们继续,在剩下的文字中,(还是有点长,就不在分了)我们重点剖析下插件主要部分的设计思想和实现,对其做个简单总结。前面说过,插件采用VC的add-in实现,他是一个基于ATL/COM的组件应用程序,如果不了解ATL/com的也无所谓(了解当然更好啦。。),因为涉及到他们东西很少,它最重要的地方就是提供了一个框架壳子,而且此框架也不必深究,都交给IDE来处理,我们更多还是用MFC,这个应该熟悉把,最起码也应该会用把,如果不会。。。。。,厄,那也能看懂,因为我们重点说思想,不涉及具体的代码,当然示例性代码还是有的,嘿嘿。新建一工程,命名为MouseGesture,向导选择DevStudio Add-in Wizard,单击OK,来到add-in向导第一步,我们插件也提供一个工具栏,可以保持默认,单击完成,这时具有扩展IDE开发环境的插件已经建好了,编译成功后,通过 Tools—Customize---Andin and Macro Files标签页将我们的插件添加道IDE中,成功后应该在IDE中显示一个新的工具栏,后续工作我们都以此工程为基础实现我们自己的插件。现在就让我们一步步把程序核心关键地方一一讲解,当你看完时,也许你会这么想,“就这么简单啊,我也来做一个。。。”开始啦~~~~~~!1. 既然是手势,鼠标相关的信息我们得知道把,这里我们通过鼠标钩子来实现,分别在插件初始化和卸载时实现钩子的挂钩及脱钩。代码简单的连注释都省了。如下。 //挂钩CDSAddIn::OnConnection(){ …………. g_hook = SetWindowsHookEx(WH_MOUSE,&MouseProc,NULL,GetCurrentThreadId());}//脱钩CDSAddIn::OnDisconnection(){ ………………….. UnhookWindowsHookEx(g_hook);}2. 鼠标的消息我们已经可以拿到了,先让我们把鼠标消息转换成屏幕上的线把,只要完成这一步手势看着就有点样子。程序中我们实现了一个类CMouseEx,由他来实现所有功能,先看下他的声明,后面用的到。class CMouseEx { ………………..//程序本身提供的功能 void CloseCur(); //关闭当前窗口, //加速键相关 //WM_COMMND相关 ……………………}关于用鼠标划线的功能,想必大家都不陌生,无非是记住鼠标当前位置(我们已经下钩了),然后MoveTo、LinetTo之类的,这些就不在说了,这里着重要说的是在那个DC上作画?DC有没有裁剪?由于我们程序是一个辅助的划线工具,不可能让线画到整个IDE环境,比方说菜单、工具栏、类视图等这些地方是不允许划线的,要不太乱了,而且平时我们大多也在代码编辑地方待的时间最长,所以这里应该是理想手势执行区。所以呢, DC我们采用屏幕DC,屏幕DC对我们来说已经够用了,操作范围以代码编辑区为界限。决定了使用屏幕DC,和操作DC范围,看看如何得到他把,得到屏幕DC应该很简单的,如下。if (!m_pWindowDC){m_pWindowDC = new CWindowDC(CWnd::FromHandle(GetDesktopWindow()));} 以后操作都针对此DC,由于此DC可以操作到整个屏幕,刚才我们也说过应该将范围限制在代码编辑区中,所以呢给此DC加个裁剪,这个怎么做呢?代码编辑区其实也是一个窗口,具体窗口的属性大家可以通过spy++看看,由此知道他其实是一个类名为MDIClient的窗口,已打开的文件是他的子窗口。那现在就很明显了,得到这个MDIClient窗口句柄,在得到他的RECT,然后在以此RECT对DC做裁剪,这样就把操作限制在代码编辑区了。 MDIClient窗口同时有是IDE的一个子窗口,显然我们要先得到IDE的窗口句柄,然后遍历其子窗口,在查找类名为MDIClient的窗口即可。好,继续看代码。//寻找IDE 句柄 原理就是IDE的父窗口为NULL HWND hWnd, hDesktopWnd; hWnd = ::GetActiveWindow(); hDesktopWnd = ::GetDesktopWindow(); while (hWnd && hWnd != hDesktopWnd) { m_hDevStudioWnd = hWnd; hWnd = ::GetParent(hWnd); } CWnd *pDevStudioWnd = CWnd::FromHandle(m_hDevStudioWnd); //找到IDE句柄了在根据类名查找MDIClient子窗口 char szClassName[256]; m_hMDIWnd = pDevStudioWnd->GetTopWindow()->m_hWnd; ::GetClassName(m_hMDIWnd, (LPTSTR)szClassName, sizeof(szClassName)); while (strcmp(szClassName, "MDIClient") != 0) { m_hMDIWnd = ::GetNextWindow(m_hMDIWnd, GW_HWNDNEXT); ::GetClassName(m_hMDIWnd, (LPTSTR)szClassName, sizeof(szClassName)); } //给DC加裁剪CRgn rgn;rgn.CreateRectRgnIndirect(&rcClient); m_pWindowDC->SelectClipRgn(&rgn);到这里时,DC和裁剪都有了,在加上划线功能,恩,有点手势味道了,我们划线采用Polyline方式,因为我们手势还有tip显示功能,如果用Move\Line画线的话,Tip窗口可能将我们划的线给擦掉,简单期间我们直接使用Polyline方式,让他每次都重新画一边。3. 鼠标消息、划线功能都有了,现在应该说手势命令识别了,怎样识别手势呢?起初我也想复杂了,当时是想到了文字的识别?那肯定是很复杂的,但其实那些我们用不到,我们用到的只是记录鼠标移动过的轨迹,并辨别它,比如说RD、LD之类的,(U -上,D-下,L-左,R-右)核心代码也是从网上摘录的,代码很简单也很经典,直接看代码吧。//生成手势命令BOOL CMouseEx::MakeMouseGestureCommnd( CPoint pt ){ int x = pt.x - m_ptGenerateCur.x; int y = pt.y - m_ptGenerateCur.y; int dist = x*x+y*y; if(dist>64) { if(x>abs(y) && x>0) {// R } else if(abs(x)>abs(y) && x<0) {// L } else if(y>abs(x) && y>0) {//D } else if(abs(y)>abs(x) && y<0) {// U } else return FALSE; return TRUE; } else return FALSE; } 没骗你把,很简单不是。4. 现在应该到了具体的功能了,程序支持三种手势功能分别是本身实现的、通过快捷键的、发送WM_COMMND的,下面一一介绍。a) 程序本身的功能在这个模块可以实现一些自己特有功能(总不能老拿别人的把),或者对某个功能扩充,好处吗?自然很明显,举个简单例子,对话框应用程序中,往往是在OnInitDialog中对一些控件变量初始化,然后在OnOK中把其值的在拿回来,类似如下这样。CInfo m_info;//举例 一个类,属性对应dlg每个控件OnInitDialog(){//edit 控件 m_EditName = m_info.m_csName; m_EditAge = m_info.m_csAge; m_EditSex = m_info.m_csSex; …… //假设有很多edit控件需要赋值 }OnOK(){//点击确定后,要拿到最新值,怎么做呢?反正我是这样做的,上面代码拷贝过来,一个一个在修改,也就是修改下等号位置,但往往这些赋值操作很多,让我改的很痛苦。。。。m_info.m_csName = m_EditName;m_info.m_csAge= m_ EditAge;m_info.m_ csSex = m_EditSex ;………//很多}如果我将这个手工操作改为用代码来实现,并且通过手势来触发,以后在遇到此类问题,我首先选中一段代码,然后鼠标一画便实现反向赋值,很爽不是吗?恩,这个是挺好,原有的一个一个改确实很烦,但。。。这个功能我却没有实现,到不是复杂,关键是类似这样问题太多了。。。。现在插件本身的功能,没有一个是自己实现的,都只使用了接口IApplication本身的几个方法,实现了几个功能?主要还是‘偷‘现有功能。呵呵。b) 快捷键之前我的想法是,可以把所有的快捷键都转换成手势,都可以用手势来触发,但那毕竟只是一个想法而已,在真正实现的时候,发现了一个让人极度不爽的事情,那就是对快捷键有个特殊要求,必须带ALT 否则不起作用,触发快捷键我采用keybd_event/ SendInput方式,但发现如果不带ALT的话,就相应应不了,原因还不太确定?所以不便多说,如果有知道的朋友,请告诉我。不过那并不代表我们就没有办法了,可以把我们频繁用到的功能(主要针对IDE),重新给他指定一个带ALT的快捷键,在 Tools—Customize---Keyboard中设置,然后将此快捷键添加到配置中,我么可以一样用。呵呵,条条大路通罗马。c) WM_COMMND如果不愿意重新设置快捷键、或者某个功能在Keyboard没有、在或者需要‘偷‘某插件功能以及其他?那怎么办?这是就用到了发送消息方法了, 理论上所有基于发送WM_COMMND实现的功能我们都能够‘偷’过来,因为本身就是发送了一个WM_COMMND消息而已。WM_COMMND详细介绍请参见MSDN,这里简单说明一下我们的使用原理,基于菜单、加速键的功能都是通过发送WM_COMMND来实现的,HIWORD(wParam) 1 来自己菜单 0 来自加速键LOWORD(wParam) 控件标示ID。其实这个功能实现起来是最简单的,就是发送一个WM_COMMND消息,但他功能也是最强的,等等。。。。。控件标示ID怎么得到,单击一个菜单项,或者一个加速键我怎么知道他的标示ID,你如果能想到这点,证明你是一个心思缜密的人,没想到的证明你是一个做大事的人。嘿嘿。。。。不错,我们还没有得到这个ID?没有这个ID我们的消息也没有办法发啊?怎么拿呢?应该有更好的办法把,反正我没用,也没具体想,我只是拿出了SPY++,监视IDE消息,因为没有用的消息太多,为了不影响输出,我只过滤了WM_COMMND消息,然后在IDE中操作一下我将要‘偷’的功能,完毕后,我在看看SPY++输出什么,然后将对应的信息,输入到程序配置中 ,仅次而已。恩,是的,虽然麻烦但他的确可以工作,而且平时我也不会在修改它,真可谓一劳永逸。。。。。。5. 具体功能总是对应具体实现,如果明白了上述功能的原理,那么看下面的结构应该是很简单的。让我们看看上述功能如何用程序来描述。 //0 软件功能 1 扒 快捷键 2 扒 菜单命令typedef enum {Commnd_Self=0x00,Commnd_ShorCut,Commnd_Menu} CommndType;typedef struct { CommndType type; char* lpCommndData; //数据 CString csMouseGesture; //鼠标手势 LRUD CString csDesc; //描述 用于tip}MouseCommnd;不错,一个结构而已,相当于上述功能快照。也许唯一点要说明的就是lpCommndData了。不同手势类型代表不同数据(也许用个union比较好,做的时候,是想到那就做到那,有的地方也许欠考虑。。。),在最后执行命令时,根据type类型,做不同处理,如下。BOOL CMouseEx::Excute(){ MouseCommnd* pCommnd;switch (pCommnd->type){ case Commnd_Self: //程序本身的功能case Commnd_ShorCut //处理加速键case Commnd_Menu: //发送WM_COMMND}}下面对每个不同类型的lpCommndData数据格式做个简要分析。a. 类型为Commnd_Self的,代表程序本身实现的功能,说白了就是实现一函数来完成某个具体逻辑, 因为提供功能已经事先定好,所以我们在lpCommndData身上存放函数地址,这样在执行时,我们可以在将其转化为函数调用,之后这块逻辑就不用在修改了,以后在增加功能,只要存放正确函数地址即可,为此用四个字节,存放函数地址。怎样确定一个函数地址呢?而且还是类成员函数,比方说就前面提到过的CloseCur这个成员方法,这个成员地址怎么得到呢?其实地址在编译时就已经确定,可以通过类的偏移来得到地址,但那样如果类结构改了,就有可能出错,为此这里我们根据堆栈平衡原则来得到函数地址。实现一函数,参数为函数指针,将成员函数传过去,这时堆栈里面就存放该函数地址了,我们得到他就可以了。描述有可能不清,直接来代码把,正所谓‘源码之下,了无秘密’,相信大家能看懂的。。。//得先有一个成员函数指针把typedef void (CMouseEx::*pSelffn)(); //如下是填充一个程序本身功能的代码。使用时像这样MouseCommnd *pCommnd = new MouseCommnd;pCommnd->type = Commnd_Self;pCommnd->lpCommndData = new char[4];//得到函数地址DWORD dAddress = GetFunAddress(&CMouseEx::CloseCur); *(DWORD*)pCommnd->lpCommndData = dAddress;pCommnd->csMouseGesture = "DR";pCommnd->csDesc = "关闭当前窗口";//、、、、、、可以增加更多功能 //采用内联汇编方式,得到成员函数的地址DWORD CMouseEx::GetFunAddress( pSelffn p ){DWORD dAddress; __asm { mov eax,[ebp+08h] mov DWORD ptr[dAddress],eax } return dAddress;}在函数调用时,无非也就是先PUSH 参数、push返回地址,、push ebp ,其中[EBP+8]为最右边参数,这里就一个参数,也就是说这个值,既函数地址。(以VC6以及stdcall 调用约定来解析)这样我们可以得到所有函数地址,在调用时就方便了。如下接上面BOOL CMouseEx::Excute() 函数case Commnd_Self: { //我们只保存函数地址,所以得自己实现函数调用,模拟CALL指令 DWORD *dwThis = (DWORD*)this; __asm { mov ebx,dword ptr [pCommnd] mov edx,[ebx].lpCommndData mov eax,dword ptr [edx] ;得到函数地址 mov ecx, dword ptr [dwThis] ;模拟this指针 call eax ;调用函数,无参 }}应该挺简单的把。。。。VC中如果想看反汇编代码,ALT+8能够满足你,那多麻烦,用手势多好。这样做的好处时,不管你以后在增加多少功能,只要初始化函数地址正确,它就能够正常工作,也就是说你要的工作是初始化pCommnd这个结构,并实现函数的具体逻辑,仅此而已,其他的你用管。这一块是不需要修改的。当然除了BUG。b. 加速键lpCommndData 在加速键类型中,存放hotkey的值,大小也为四个字节,高位放wModifiers,低位放wVirtualKeyCode。上面已经说过,快捷键要起效,就必须带ALT,其实触发快捷键那就简单了keybd_event 完全可以胜任,具体实现就不用在举例了把。无非也就如下这样keybd_event(wVirtualKeyCode,0,0,0); //模拟键按下keybd_event(wVirtualKeyCode,0,KEYEVENTF_KEYUP,0);//弹起c. WM_COMMNDlpCommndData 在COMMND中,用于存放消息,大小也为四个字节,高位放加速键、菜单项的标示ID,低位放Notification Cod,既加速键为1,菜单为0。至于发消息,那就更简单了,只要信息正确,如下OK。USHORT nCode,nFlag;nCode = *( USHORT *)pCommnd->lpCommndData;nFlag = *( USHORT *)(pCommnd->lpCommndData + 2);// 偏移消息号//向IDE 发消息。PostMessage(m_hDevStudioWnd,WM_COMMAND,MAKEWPARAM(nCode,nFlag),0);6. 至此对程序核心部分的设计思想及实现我做了一些简要分析,我希望我表达的够清楚,以不至使你既浪费了时间,有没有搞清楚怎么回事?如果真的那样,请不要怀疑你的能力,那是我的问题,怪我没有事情讲清楚。我们分析了程序主要部分,当然这个小程序还涉及到一些其他知识点,麻雀虽小,但也五脏俱全,比方说Ø 为了限制输入手势命令的EDIT只能接受几个特定的字母,我使用了子类化Ø 移动tip窗口,并对其贴图。Ø 增加手势命令的BUTTON ,我使用了重绘了。Ø 以及其他小地方。但这样的一些东西,一不是我们分析目标,二也没有必要说这些东西,毕竟我们主要着重点是在手势扒手。 看下我的测试环境。
最后在说说源代码的事,代码在XP_SP2、VC_SP6下编译并通过简单测试,其实我也想过把代码放出来,但由于最初没有定具体功能,在做的时候,是想道那就做到那,感觉好的就加上了,代码太。。。,实在不好意思拿出来误导大家,不过核心思想我们已经讲过了,你也完全可以重新实现一个,随便一写也比这个好。 好了,终于完了,就到这里把,祝大家愉快~~~~~~
插件下载地址:http://download.csdn.net/source/2867221 本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/gangyilovevc/archive/2010/11/29/6042014.aspx
最后在说说源代码的事,代码在XP_SP2、VC_SP6下编译并通过简单测试,其实我也想过把代码放出来,但由于最初没有定具体功能,在做的时候,是想道那就做到那,感觉好的就加上了,代码太。。。,实在不好意思拿出来误导大家,不过核心思想我们已经讲过了,你也完全可以重新实现一个,随便一写也比这个好。 好了,终于完了,就到这里把,祝大家愉快~~~~~~
插件下载地址:http://download.csdn.net/source/2867221 本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/gangyilovevc/archive/2010/11/29/6042014.aspx
让你的VC也支持手势----【手势扒手】制作过程 下 - gangyilovevc的专栏 ...
让你的VC也支持手势----【手势扒手】制作过程 上 - gangyilovevc的专栏 ...
让你的VC也支持手势----【手势扒手】制作过程(全)
教你学会 “我爱你” 的手势
教你学会 “我” 的手势
.教你学会 “我爱你” 的手势.............
教你学会 “我爱你” 的手势...........................
教你学会 “我爱你” 的手势.
教你学会 “我爱你” 的手势
教你学会 “我爱你” 的手势
交警的手势
中国的数字手势
佛像的各种手势
关于手势的笑话
佛的各种手势
识破说谎的八大手势
交警手势的记忆技巧
"V"字手势的来历
教你测睾丸健康程度的"OK"手势
你想要的手势,我都有【图】
从拿酒杯的手势看性格
手势,美女们的招牌动作
生活常用18种手势的含义
生活常用18种手势的含义