轉自 http://blog.csdn.net/jinhaijian/archive/2008/09/26/2985378.aspx
爲了做全屏手寫功能,需要把鼠標的事件全部截獲過來,研究了一個星期左右,發現有三種方法可以實現。而且對每種方法已經寫了測試代碼。根據三種方法效果的好壞排序:
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層由幾個獨立的模塊來協同完成。
l Touch Panel校準算法:源碼在PUBLIC/COMMON/OAK/DRIVERS/TCH_CAL下,這部分跟我們沒有多大關係,就不細介紹了。
l Touch Panel校準UI:源碼在PUBLIC/COMMON/OAK/DRIVERS/CALIBRUI
l Touch Panel DDI接口:C:/WM605/PUBLIC/COMMON/OAK/DRIVERS/TOUCH,這個目錄下又分了三個子目錄
1. TCHMAIN:實現DDI接口。
TouchPanelGetDeviceCaps
TouchPanelEnable
TouchPanelDisable
TouchPanelSetMode
TouchPanelReadCalibrationPoint
TouchPanelReadCalibrationAbort
TouchPanelSetCalibration
TouchPanelCalibrateAPoint
TouchPanelPowerHandler
這裏面最重要的一段代碼就在TouchPanelEnable中,只所以重要,是跟我們實現Touch截獲有關。看一下這個函數裏的代碼
extern PFN_TOUCH_PANEL_CALLBACK v_pfnCgrPointCallback;
extern PFN_TOUCH_PANEL_CALLBACK v_pfnCgrCallback;
...
BOOL
TouchPanelEnable(
PFN_TOUCH_PANEL_CALLBACK pfnCallback
)
{
...
v_pfnCgrPointCallback = pfnCallback;
if (v_pfnCgrCallback != NULL)
v_pfnPointCallback = v_pfnCgrCallback;
else
v_pfnPointCallback = pfnCallback;
...
}
說明下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消息給你。
/ / set flags
_iClientFlags = (int)GetWindowLong(_hClientWnd, 0);
//just adjust
if((_iClientFlags&TABLET_TEST_FIRST_POINT)!=0 &&
!SendMessage(_hClientWnd, WM_PEGREC_FIRSTPOINT, 0, MAKELPARAM(X, Y)))
{
_iClientFlags = TABLET_ALL_TO_SYSTEM;
}
…
很是奇怪,一般的窗體是不能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 內)。
// TCHSTUB strokes dispatch modes (set in window longs)
#define TABLET_SILENT 0x0000
#define TABLET_ALL_TO_CLIENT 0x0001
#define TABLET_ALL_TO_SYSTEM 0x0002
#define TABLET_CLICK_TO_SYSTEM 0x0004
#define TABLET_STARTDELAY_TO_SYSTEM 0x0008
#define TABLET_INTERDELAY_TO_SYSTEM 0x0010
#define TABLET_SEND_RELEASE_MSG 0x0020
#define TABLET_SEND 0x0040
#define TABLET_NEED_PERMITION 0x0080
#define TABLET_SAVE_FOCUS_WND 0x0100
#define TABLET_TEST_FIRST_POINT 0x0200
我現在調用TouchRegisterWindow後,要麼只截獲Touch Down的消息,mouse和up都沒有截獲,或者是把系統的所以消息都屏蔽了。哪位有興趣可以研究一下。在模擬器上是可以試的。模擬器的PDD代碼目錄在PLATFORM/DEVICEEMULATOR/SRC/DRIVERS/TOUCH下,需要把MDD的代碼合成在一起,通過打印信息就可以看出效果了。這個方法沒有實現,還講了這麼多的廢話,哈哈。
我把Touch的MDD層代碼放到了TouchMDD中了。
2. 編寫僞Touch驅動。
這個方法跟上面的方法其實是一個方法,只是微軟來寫和我們自己的區別。微軟寫的太複雜了,搞得我沒有搞定上面的方法。可能是微軟爲了更好的擴展和其他應用吧。但是我們只給自己的應用用的話,那就很簡單了。看代碼吧
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch ( ul_reason_for_call )
{
case DLL_PROCESS_ATTACH:
DisableThreadLibraryCalls((HMODULE) hModule);
g_hInstTouch = LoadLibrary(TOUCH_DLL);
break;
case DLL_PROCESS_DETACH:
if(NULL != g_hInstTouch)
FreeLibrary(g_hInstTouch);
break;
}
return TRUE;
}
在DllMain中,把Touch.dll Load進來。我們只要看TouchPanelEnable和自己的一個callback函數。
// 原始Callback
PFN_TOUCH_PANEL_CALLBACK pfnOrgTouchPanelCallback = NULL;
HWND g_sipWnd = NULL;
HWND g_hwWnd = NULL;
#define HW_CLASSNAME L"CeSipEng"
INT xSaved = 0;
INT ySaved = 0;
int iMinX = 4;
int iMinY = 4;
BOOL hwTouchPanelCallback(
TOUCH_PANEL_SAMPLE_FLAGS Flags,
INT X,
INT Y
)
{
if(NULL == g_sipWnd)
{
g_sipWnd = FindWindow(L"SipWndClass", NULL);
}
g_hwWnd = GetWindow(g_sipWnd, GW_CHILD);
if(IsWindowVisible(g_hwWnd))
{
TCHAR szClassName[32];
GetClassName(g_hwWnd, szClassName, 32);
if(wcsicmp(szClassName, HW_CLASSNAME) == 0)
{
// down
if(Flags == (TouchSampleDownFlag | TouchSampleIsCalibratedFlag | TouchSampleValidFlag))
{
SendMessage(g_hwWnd, WM_LBUTTONDOWN, 0, MAKELPARAM(X,Y));
}
// mouse
else if(Flags == (TouchSampleDownFlag | TouchSamplePreviousDownFlag | TouchSampleIsCalibratedFlag |
TouchSampleValidFlag) &&
xSaved - X > iMinX || X - xSaved > iMinX &&
ySaved - Y > iMinY || Y - ySaved > iMinY)
{
SendMessage(g_hwWnd, WM_MOUSEMOVE, 0, MAKELPARAM(X,Y));
}
// up
else if(Flags ==(TouchSampleIsCalibratedFlag |
TouchSampleValidFlag | TouchSamplePreviousDownFlag))
{
SendMessage(g_hwWnd, WM_LBUTTONUP, 0, MAKELPARAM(X,Y));
}
xSaved = X;
ySaved = Y;
return TRUE;
}
Else
// 發送給系統
return pfnOrgTouchPanelCallback(Flags, X,Y);
}
//發送給系統
return pfnOrgTouchPanelCallback(Flags, X,Y);
}
BOOL
TouchPanelEnable(
PFN_TOUCH_PANEL_CALLBACK pfnCallback
)
{
if(g_hInstTouch)
{
if (NULL == pfnTouchPanelEnable)
{
pfnTouchPanelEnable = (PFN_TOUCH_PANEL_ENABLE)GetProcAddress(g_hInstTouch,
TEXT("TouchPanelEnable"));
}
// 保存原來的Callback
pfnOrgTouchPanelCallback = pfnCallback;
// 傳入自己的Callback
return pfnTouchPanelEnable(hwTouchPanelCallback);
}
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
寫累了,直接看代碼
EVENTMSG evtMsg;
ZeroMemory(&evtMsg, sizeof(EVENTMSG));
g_hHook = QASetWindowsJournalHook(WH_JOURNALRECORD, KeyboardProc, &evtMsg);
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
switch (nCode)
{
case HC_ACTION:
{
EVENTMSG *mesg = (EVENTMSG *)lParam;
UINT message = mesg->message;
if(message == WM_LBUTTONDOWN)//pressed
{
//DEBUGMSG (ZONE_FUNCTION, (TEXT("WM_LBUTTONDOWN/r/n")));
}
else if(message == WM_MOUSEMOVE)
{
//DEBUGMSG (ZONE_FUNCTION, (TEXT("WM_MOUSEMOVE/r/n")));
}
else if(message == WM_LBUTTONUP)
{
//DEBUGMSG (ZONE_FUNCTION, (TEXT("WM_LBUTTONUP/r/n")));
}
POINT pt = {0};
pt.x = LOWORD(mesg->paramL);
pt.y = HIWORD(mesg->paramL);
if(message == WM_LBUTTONDOWN )
{
HWND hWnd = WindowFromPoint(pt);
if(pMouseSpy != NULL && pt.y < 600)
{
// 用SetWindowsPos來設置到最前臺,這樣窗體就接收不到Down消息。
SetWindowPos(pMouseSpy->m_hWnd, HWND_TOPMOST,pt.x -10, pt.y -10, 20, 20, SWP_SHOWWINDOW);
}
}
}
return TRUE;
default:
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, 寫的不好,請見諒。