织梦CMS - 轻松建站从此开始!

罗索

Windows Mobile 触摸屏(Touch Panel)截获

落鹤生 发布于 2010-07-01 21:54 点击:次 
为了做全屏手写功能,需要把鼠标的事件全部截获过来,研究了一个星期左右,发现有三种方法可以实现。而且对每种方法已经写了测试代码。根据三种方法效果的好坏排序。
TAG:

为了做全屏手写功能,需要把鼠标的事件全部截获过来,研究了一个星期左右,发现有三种方法可以实现。而且对每种方法已经写了测试代码。根据三种方法效果的好坏排序:

1. 用英文手写识别(TRNSCRBR)Touch的拦截代码,这种方法实现是上上策,这个是微软为手写专门在Touch 驱动中加的。

2. 自己写一个伪Touch驱动,让GWES加载这个Touch驱动,在你的Touch驱动中再调用原来的驱动。

3. 用QASetWindowsJournalHook来获取键盘和鼠标事件, 从QA打头可以看出这个API用来QA来用的,它是用来记录键盘和鼠标的事件,并不能截获,我试了很久,最后找出一种比较BT的方法,就是在收到WM_LBUTTON的时候,用SetWindowsPos设置一个窗体到TOPMOST,这样其他的窗体就收不到WM_LBUTTON的消息。但是Mouse消息还是能够收到。所以才把这种方法定到最后的方案。

上面说了这三种方法,想必大家想知道是如何实现的吧。我们就一个个的来,先说第一个。

1. 利用微软手写识别驱动

由于项目比较紧,这种方法我目前还没有完全试验成功,但我可以这个方法肯定是可行的,只要给点时间肯定是可以的。不说废话了,还是把我知道的东东介绍给大家吧。

先来介绍一下Windows CE Touch Panel的驱动。微软的Touch驱动是一个本地驱动,非流驱动。分为MDD层和PDD层。对于PDD层我们不需要了解,它导出了如下的函数供MDD调用(DDIS接口)。

DdsiTouchPanelAttach

DdsiTouchPanelDetach

DdsiTouchPanelDisable

DdsiTouchPanelEnable

DdsiTouchPanelGetDeviceCaps

DdsiTouchPanelGetPoint

DdsiTouchPanelPowerHandler

微软把与驱动无关的代码放到了MDD层中,这部分代码都开源的,我们可以直接研究它的代码,看我们如何来实现。由于微软不提供相应的截获鼠标的文档,所以我只能看驱动代码,从中得到方法。 微软的MDD层由几个独立的模块来协同完成。

Touch Panel校准算法:源码在PUBLIC\COMMON\OAK\DRIVERS\TCH_CAL下,这部分跟我们没有多大关系,就不细介绍了。
Touch Panel校准UI:源码在PUBLIC\COMMON\OAK\DRIVERS\CALIBRUI
Touch Panel DDI接口:C:\WM605\PUBLIC\COMMON\OAK\DRIVERS\TOUCH,这个目录下又分了三个子目录

1. TCHMAIN:实现DDI接口。

TouchPanelGetDeviceCaps

TouchPanelEnable

TouchPanelDisable

TouchPanelSetMode

TouchPanelReadCalibrationPoint

TouchPanelReadCalibrationAbort

TouchPanelSetCalibration

TouchPanelCalibrateAPoint

TouchPanelPowerHandler

这里面最重要的一段代码就在TouchPanelEnable中,只所以重要,是跟我们实现Touch截获有关。看一下这个函数里的代码

  1. extern PFN_TOUCH_PANEL_CALLBACK v_pfnCgrPointCallback; 
  2. extern PFN_TOUCH_PANEL_CALLBACK v_pfnCgrCallback; 
  3. ... 
  4. BOOL TouchPanelEnable(PFN_TOUCH_PANEL_CALLBACK    pfnCallback) 
  5. ... 
  6.   v_pfnCgrPointCallback = pfnCallback; 
  7.     if (v_pfnCgrCallback != NULL) 
  8.     v_pfnPointCallback = v_pfnCgrCallback; 
  9.     else 
  10.         v_pfnPointCallback = pfnCallback; 
  11. ... 

说明下PFN_TOUCH_PANEL_CALLBACK ,每次Touch得到一个点后都会调用它指向的函数。v_pfnCgrPointCallback用来指向TouchPanelEnable原始的函数入口、而v_pfnPointCallback用来指向我们自己的入口。当我们把v_pfnCgrCallback指向自己写的函数时。

2. BASIC:没有微软手写驱动的时,这里面的代码就只有DLLMain的功能

3. TRNSCRBR:要想把TRNSCRBR编译到驱动中,需要在BSP的设置加WCESHELLFE_MODULES_TRANSCRIBER 或SHELLW_MODULES_CGRTOUCH。

最终导出如下函数:

TouchReset

TouchRegisterWindow

TouchUnregisterWindow

TouchSetValue

TouchGetValue

TouchCreateEvent

TouchGetFocusWnd

TouchGetLastTouchFocusWnd

TouchGetQueuePtr

下面详细来讲下关于TRNSCRBR截获Touch Panle的方法。先看一下它导出的函数.

void TouchReset(BOOL bSetAllValuesToDefault): TRNSCRBR定义了一些配置,通过这个函数来Reset到默认。

BOOL TouchRegisterWindow(HWND hClientWnd); 注册Wnd到Touch驱动,Touch把键盘消息发送给这个窗口了。

void TouchUnregisterWindow(HWND hClientWnd); 取消注册,Touch把消息发送给系统

void TouchSetValue(DWORD dwName, DWORD dwValue); 设置配置

LRESULT TouchGetValue(DWORD dwName, DWORD dwValue); 得到有个配置

void TouchCreateEvent(int iX, int iY); 发送Mouse 消息给系统

LPVOID TouchGetQueuePtr(); 从队列中读取一个POINT。

HWND TouchGetFocusWnd(); 

HWND TouchGetLastTouchFocusWnd();

所以你建立一个自己的窗体,然后设置窗体的属性。在Touch驱动中,它会根据你窗体设置的属性来发送哪些mouse消息给你。

  1. // set flags  
  2. _iClientFlags = (int)GetWindowLong(_hClientWnd, 0); 
  3. //just adjust 
  4. if((_iClientFlags&TABLET_TEST_FIRST_POINT)!=0 && 
  5. !SendMessage(_hClientWnd, WM_PEGREC_FIRSTPOINT, 0, MAKELPARAM(X, Y))) 
  6. _iClientFlags = TABLET_ALL_TO_SYSTEM; 
  7. … 

很是奇怪,一般的窗体是不能SetWindowLong(hWnd,0,XXX)。只有对于对话框才有设置这个属性。#define DWL_MSGRESULT   0。但是对话框有一个问题,当你SendMessage给对话框时,返回值总为空。所以上面的SendMessage(_hClientWnd, WM_PEGREC_FIRSTPOINT, 0, MAKELPARAM(X, Y)返回为FALSE。这样导致把_iClientFlags位置为TABLET_ALL_TO_SYSTEM,这样所有的消息都发给了系统。还有即使是用对话框,调用SetWindowLong(hDlg, 0, 0x2FD); 但是GetWindowLong(_hClientWnd,0),返回始终为空。

下面是SetWindowLong需要设置的内容(pegc_def.h 内)。

  1. // TCHSTUB strokes dispatch modes (set in window longs) 
  2. #define TABLET_SILENT                0x0000 
  3. #define TABLET_ALL_TO_CLIENT         0x0001 
  4. #define TABLET_ALL_TO_SYSTEM         0x0002 
  5. #define TABLET_CLICK_TO_SYSTEM       0x0004 
  6. #define TABLET_STARTDELAY_TO_SYSTEM  0x0008 
  7. #define TABLET_INTERDELAY_TO_SYSTEM  0x0010 
  8. #define TABLET_SEND_RELEASE_MSG      0x0020 
  9. #define TABLET_SEND                  0x0040 
  10. #define TABLET_NEED_PERMITION        0x0080 
  11. #define TABLET_SAVE_FOCUS_WND        0x0100 
  12. #define TABLET_TEST_FIRST_POINT      0x0200 

我现在调用TouchRegisterWindow后,要么只截获Touch Down的消息,mouse和up都没有截获,或者是把系统的所以消息都屏蔽了。哪位有兴趣可以研究一下。在模拟器上是可以试的。模拟器的PDD代码目录在PLATFORM\DEVICEEMULATOR\SRC\DRIVERS\TOUCH下,需要把MDD的代码合成在一起,通过打印信息就可以看出效果了。这个方法没有实现,还讲了这么多的废话,哈哈。

我把Touch的MDD层代码放到了TouchMDD中了。

2. 编写伪Touch驱动。

这个方法跟上面的方法其实是一个方法,只是微软来写和我们自己的区别。微软写的太复杂了,搞得我没有搞定上面的方法。可能是微软为了更好的扩展和其他应用吧。但是我们只给自己的应用用的话,那就很简单了。看代码吧

  1. BOOL APIENTRY DllMain( HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
  2. {
  3. switch ( ul_reason_for_call ) 
  4. case DLL_PROCESS_ATTACH: 
  5. DisableThreadLibraryCalls((HMODULE) hModule); 
  6. g_hInstTouch = LoadLibrary(TOUCH_DLL); 
  7. break
  8. case DLL_PROCESS_DETACH: 
  9. if(NULL != g_hInstTouch) 
  10. FreeLibrary(g_hInstTouch); 
  11. break
  12.     return TRUE; 

在DllMain中,把Touch.dll Load进来。我们只要看TouchPanelEnable和自己的一个callback函数。

  1. // 原始Callback 
  2. PFN_TOUCH_PANEL_CALLBACK pfnOrgTouchPanelCallback = NULL; 
  3. HWND g_sipWnd = NULL; 
  4. HWND g_hwWnd = NULL; 
  5. #define  HW_CLASSNAME  L"CeSipEng" 
  6. INT xSaved = 0; 
  7. INT ySaved = 0; 
  8. int  iMinX = 4; 
  9. int  iMinY = 4; 
  10. BOOL hwTouchPanelCallback( 
  11. TOUCH_PANEL_SAMPLE_FLAGS Flags, 
  12. INT X, 
  13. INT Y 
  14. if(NULL == g_sipWnd) 
  15. g_sipWnd = FindWindow(L"SipWndClass", NULL); 
  16. g_hwWnd = GetWindow(g_sipWnd, GW_CHILD); 
  17. if(IsWindowVisible(g_hwWnd)) 
  18. TCHAR szClassName[32]; 
  19. GetClassName(g_hwWnd, szClassName, 32); 
  20. if(wcsicmp(szClassName, HW_CLASSNAME) ==  0) 
  21. // down 
  22. if(Flags == (TouchSampleDownFlag |
  23.  TouchSampleIsCalibratedFlag | TouchSampleValidFlag)) 
  24. SendMessage(g_hwWnd, WM_LBUTTONDOWN, 0, MAKELPARAM(X,Y)); 
  25. // mouse  
  26. else if(Flags == (TouchSampleDownFlag |
  27.  TouchSamplePreviousDownFlag | TouchSampleIsCalibratedFlag |  
  28. TouchSampleValidFlag) && 
  29. xSaved - X > iMinX || X - xSaved > iMinX && 
  30. ySaved - Y > iMinY || Y - ySaved > iMinY) 
  31. SendMessage(g_hwWnd, WM_MOUSEMOVE, 0, MAKELPARAM(X,Y)); 
  32. // up 
  33. else if(Flags ==(TouchSampleIsCalibratedFlag |  
  34. TouchSampleValidFlag | TouchSamplePreviousDownFlag)) 
  35. SendMessage(g_hwWnd, WM_LBUTTONUP, 0, MAKELPARAM(X,Y)); 
  36. xSaved = X; 
  37. ySaved = Y; 
  38. return TRUE; 
  39. Else 
  40. // 发送给系统 
  41. return pfnOrgTouchPanelCallback(Flags, X,Y); 
  42. //发送给系统 
  43. return pfnOrgTouchPanelCallback(Flags, X,Y); 
  44. BOOL TouchPanelEnable(PFN_TOUCH_PANEL_CALLBACK pfnCallback) 
  45. if(g_hInstTouch) 
  46. if (NULL == pfnTouchPanelEnable) 
  47. pfnTouchPanelEnable = (PFN_TOUCH_PANEL_ENABLE)GetProcAddress(g_hInstTouch,  
  48. TEXT("TouchPanelEnable")); 
  49. // 保存原来的Callback 
  50. pfnOrgTouchPanelCallback = pfnCallback; 
  51. // 传入自己的Callback 
  52. return pfnTouchPanelEnable(hwTouchPanelCallback); 
  53. return FALSE; 

其他的函数跟TouchPanelEnable一样的形式,就是GetProcAddress一下,然后再Call一下。

差点忘记,把自己的写的注册到系统,让GWES.exe调用。

[HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\TOUCH]

“DriverName”=”MyTouch.dll”

我把伪Touch的代码放到了MyTouch中

3. QASetWindowsJournalHook

这个函数在pwinuser.h中。微软在Windows CE中支持键盘HOOK、键盘/鼠标的记录、键盘/鼠标的回放,看pwinuser.h中的定义

#define WH_JOURNALRECORD    0

#define WH_JOURNALPLAYBACK  1

#define WH_KEYBOARD_LL      20

写累了,直接看代码

  1. EVENTMSG evtMsg;  
  2. ZeroMemory(&evtMsg, sizeof(EVENTMSG)); 
  3. g_hHook = QASetWindowsJournalHook(WH_JOURNALRECORD, KeyboardProc, &evtMsg); 
  4. LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) 
  5. switch (nCode) 
  6. case HC_ACTION: 
  7. EVENTMSG *mesg = (EVENTMSG *)lParam; 
  8. UINT message = mesg->message; 
  9. if(message == WM_LBUTTONDOWN)//pressed 
  10. //DEBUGMSG (ZONE_FUNCTION, (TEXT("WM_LBUTTONDOWN\r\n"))); 
  11. else if(message == WM_MOUSEMOVE) 
  12. //DEBUGMSG (ZONE_FUNCTION, (TEXT("WM_MOUSEMOVE\r\n"))); 
  13. else if(message == WM_LBUTTONUP) 
  14. //DEBUGMSG (ZONE_FUNCTION, (TEXT("WM_LBUTTONUP\r\n"))); 
  15. POINT pt = {0}; 
  16. pt.x = LOWORD(mesg->paramL); 
  17. pt.y = HIWORD(mesg->paramL); 
  18. if(message == WM_LBUTTONDOWN ) 
  19. HWND hWnd = WindowFromPoint(pt); 
  20. if(pMouseSpy != NULL && pt.y < 600) 
  21. // 用SetWindowsPos来设置到最前台,这样窗体就接收不到Down消息。 
  22. SetWindowPos(pMouseSpy->m_hWnd, HWND_TOPMOST,pt.x -10,
  23.  pt.y -10, 20, 20, SWP_SHOWWINDOW); 
  24. return TRUE; 
  25. default
  26. break

这里SetWindowPos(pMouseSpy->m_hWnd, HWND_TOPMOST,pt.x -10, pt.y -10, 20, 20, SWP_SHOWWINDOW);,

这个很重要很重要,是我试了很多方法才把WM_LBUTTONDOWN消息给屏蔽掉。但是WM_MOUSEMOVE和WM_LBUTTONUP用这种方法是不能过滤掉的。有人会问,在窗体 (pMouseSpy->m_hWnd) 的WM_LBUTTONDOWN中去SetCapture不就OK了,哈哈,不行的。

这个窗体虽然是TOPMOST的,但是也是收不到WM_LBUTTONDOWN消息的。又有人说,我们可以PostMessage(pMouseSpy->m_hWnd, WM_LBUTTONDOWN, 0,mesg->paramL)。

SetWindowPos后,发送WM_LBUTTONDOWN消息,但是WM_LBUTTONDOWN先于SetWindowPos发送的WM_WINDOWPOSCHANGED的消息,还是没用。

最后,当你不用的时候用QAUnhookWindowsJournalHook(WH_JOURNALRECORD);释放掉。

OK了, So Long No Write, 写的不好,请见谅。

(jinhaijian)
本站文章除注明转载外,均为本站原创或编译欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,同学习共成长。转载请注明:文章转载自:罗索实验室 [http://www.rosoo.net/a/201007/9764.html]
本文出处:CSDN博客 作者:jinhaijian
顶一下
(3)
100%
踩一下
(0)
0%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片
栏目列表
将本文分享到微信
织梦二维码生成器
推荐内容