Windows Mobile 觸摸屏(Touch Panel)截獲

轉自 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, 寫的不好,請見諒。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章