[時間]:2009-3-28
[摘要]:本文分為兩部分,第一部分:使用SwapMouseButton()切換鼠標左右鍵功能。第二部分:在全局鼠標鉤子中模擬鼠標右鍵單擊。
[關鍵字]:鼠標失靈,鼠標右鍵,切換鼠標左右鍵,屏幕寬度,屏幕高度,SendInput,全局鼠標鉤子,鼠標事件
[平臺]:Windows XP SP3 ,VC6
[作者]:Winty (wintys@gmail.com)
[正文]:
我的鼠標左鍵失靈了。暫時只能使用鼠標右鍵。就只能把鼠標右鍵改成左鍵用(切換方法:控制面板->鼠標->切換主要和次要的按鍵)。當然,切換了之后,右鍵功能就沒有了。可是平時用慣了鼠標右鍵,現在沒有可不行。利用程序切換時,代碼片段如下:
//bSwap:BOOL型
//為TRUE為切換左右鍵功能,為FALSE為恢復原左右鍵功能
::SwapMouseButton(bSwap);
//為TRUE為切換左右鍵功能,為FALSE為恢復原左右鍵功能
::SwapMouseButton(bSwap);
可以使用RegisterHotKey()注冊一個熱鍵,在需要時切換。但是還得知道該傳入TRUE還是FALSE到SwapMouseButton(),那么可以使用GetSystemMetrics(SM_SWAPBUTTON)查詢鼠標當前左右鍵功能是否反轉了:
//返回TRUE表示鼠標左右鍵功能反轉了,FALSE表示正常
::GetSystemMetrics(SM_SWAPBUTTON);
::GetSystemMetrics(SM_SWAPBUTTON);
最后可以讓程序每次自動啟動,需要右鍵的時候按一下熱鍵就可以了,使用完了再切換回來。問題是,這樣每次使用時,在左右鍵之間來回切換很麻煩。
下面在全局鼠標鉤子中模擬鼠標右鍵單擊。當按Ctrl+鼠標左鍵時,相當于點擊右鍵。這里所說的鼠標左鍵與右鍵都是用SwapMouseButton(TRUE)切換過的,也就是說,我的真實的鼠標左鍵已壞,實際上只有鼠標右鍵可以使用,現在我把鼠標右鍵當左鍵用,但又添加右鍵本身的功能(就是加按Ctrl時相當于原來的右鍵)。未特殊說明,則下同。
1、使用SetWindowsHookEx安裝鉤子。
HHOOK CUtil::InstallMouseHook(HINSTANCE hInstance)
{
HHOOK hhkMouseHook;
hhkMouseHook = SetWindowsHookEx(
WH_MOUSE_LL, // hook type
CUtil::LowLevelMouseProc, // hook procedure
hInstance, // handle to application instance
0 // thread identifier:為0表示全局鉤子
);
if(hhkMouseHook==NULL)
MessageBox(NULL,"安裝MouseHook失敗!","InstallMouseHook()",MB_OK);
return hhkMouseHook;
}
{
HHOOK hhkMouseHook;
hhkMouseHook = SetWindowsHookEx(
WH_MOUSE_LL, // hook type
CUtil::LowLevelMouseProc, // hook procedure
hInstance, // handle to application instance
0 // thread identifier:為0表示全局鉤子
);
if(hhkMouseHook==NULL)
MessageBox(NULL,"安裝MouseHook失敗!","InstallMouseHook()",MB_OK);
return hhkMouseHook;
}
在應用程序的InitInstance()中調用:
//m_hInstance為應用程序實例句柄
HHOOK hhkLowLevelKybd = InstallMouseHook(m_hInstance):
HHOOK hhkLowLevelKybd = InstallMouseHook(m_hInstance):
2、編寫全局鼠標鉤子回調函數LowLevelMouseProc:
//當按下"Ctrl+鼠標左鍵" 或 "數字鍵盤減號鍵 + 鼠標左鍵"時,模擬鼠標右鍵
LRESULT CALLBACK
CUtil::LowLevelMouseProc(int nCode,
WPARAM wParam,
LPARAM lParam)
{
if(nCode == HC_ACTION){
//左Ctrl鍵按下
BOOL bLeftCtrlDown =
(GetAsyncKeyState(VK_LCONTROL) & 0x8000) != 0;
//數字鍵盤減號"-"鍵按下
BOOL bNumpadSubtractDown =
(GetAsyncKeyState(VK_SUBTRACT) & 0x8000) != 0;
//當按下"Ctrl+鼠標左鍵" 或 "數字鍵盤減號鍵+鼠標左鍵"時,模擬鼠標右鍵
if( (wParam == WM_LBUTTONDOWN) &&
(bLeftCtrlDown || bNumpadSubtractDown) )
{
const INPUT_SIZE = 2;
INPUT input[INPUT_SIZE];
ZeroMemory( &input, sizeof(INPUT)*INPUT_SIZE);//初始化INPUT結構體
//鼠標右鍵按下
input[0].type = INPUT_MOUSE;
input[0].mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
//鼠標右鍵彈起
input[1].type = INPUT_MOUSE;
input[1].mi.dwFlags = MOUSEEVENTF_LEFTUP;
//發送鼠標右鍵單擊
SendInput(
INPUT_SIZE, // count of input events
input, // array of input events
sizeof(INPUT) // size of structure
);
//發送模擬的鼠標右鍵單擊后,不再響應鼠標左鍵單擊消息
return 1;
}
}
//如果不是"Ctrl+鼠標左鍵"按下,則向后傳遞鼠標消息。
return CallNextHookEx(NULL,nCode,wParam,lParam);
}
LRESULT CALLBACK
CUtil::LowLevelMouseProc(int nCode,
WPARAM wParam,
LPARAM lParam)
{
if(nCode == HC_ACTION){
//左Ctrl鍵按下
BOOL bLeftCtrlDown =
(GetAsyncKeyState(VK_LCONTROL) & 0x8000) != 0;
//數字鍵盤減號"-"鍵按下
BOOL bNumpadSubtractDown =
(GetAsyncKeyState(VK_SUBTRACT) & 0x8000) != 0;
//當按下"Ctrl+鼠標左鍵" 或 "數字鍵盤減號鍵+鼠標左鍵"時,模擬鼠標右鍵
if( (wParam == WM_LBUTTONDOWN) &&
(bLeftCtrlDown || bNumpadSubtractDown) )
{
const INPUT_SIZE = 2;
INPUT input[INPUT_SIZE];
ZeroMemory( &input, sizeof(INPUT)*INPUT_SIZE);//初始化INPUT結構體
//鼠標右鍵按下
input[0].type = INPUT_MOUSE;
input[0].mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
//鼠標右鍵彈起
input[1].type = INPUT_MOUSE;
input[1].mi.dwFlags = MOUSEEVENTF_LEFTUP;
//發送鼠標右鍵單擊
SendInput(
INPUT_SIZE, // count of input events
input, // array of input events
sizeof(INPUT) // size of structure
);
//發送模擬的鼠標右鍵單擊后,不再響應鼠標左鍵單擊消息
return 1;
}
}
//如果不是"Ctrl+鼠標左鍵"按下,則向后傳遞鼠標消息。
return CallNextHookEx(NULL,nCode,wParam,lParam);
}
LowLevelMouseProc()有幾點要注意的地方:
a)、LowLevelMouseProc()應聲明為static,因為在一個類中,只有static函數才能作為回調函數。
b)、wParam == WM_LBUTTONDOWN一句原來寫的是WM_LBUTTONUP,結果點擊后右鍵菜單出來了,但無法完成復制。所以應為WM_LBUTTONDOWN就不會有問題了。
c)、只用使用VK_LCONTROL而不是VK_CONTROL是因為Ctrl+鼠標左鍵單擊在選擇多個文件時已被使用。所以把VK_RCONTROL留給選擇多個文件等其它用途時使用,而用數字鍵盤中的減號代替VK_RCONTROL。也就是說,當按下"Ctrl+鼠標左鍵" 或 "數字鍵盤減號鍵+鼠標左鍵"時,模擬鼠標右鍵。
d)、input[i].mi.dwFlags中不用MOUSEEVENTF_ABSOLUTE,代表input[i].mi.dx或input[i].mi.dx中的坐標是相對于上一次鼠標事件時的坐標,而dx和dy都已被初始化為0,即在原地顯示鼠標右鍵菜單;
e)、也可以在input[i].mi.dwFlags中使用MOUSEEVENTF_ABSOLUTE,此時是絕對坐標,input[i].mi.dx和input[i].mi.dx的計算如下(而不是直接使用當前鼠標位置,具體原因請查看MSDN):
PMSLLHOOKSTRUCT pllh = (PMSLLHOOKSTRUCT)lParam;//鼠標坐標等信息
int cx=GetSystemMetrics(SM_CXSCREEN);//得到屏幕寬度
int cy=GetSystemMetrics(SM_CYSCREEN);//得到屏幕高度
LONG dx = pllh->pt.x * 65535 / cx;
LONG dy = pllh->pt.y * 65535 / cy;
input[0].mi.dx = dx;
input[0].mi.dy = dy;
input[1].mi.dx = dx;
input[1].mi.dy = dy;
f)、在發送完模擬的鼠標右鍵事件后,需要return 1;來阻止消息的繼續傳遞,而不是return CallNextHookEx(NULL,nCode,wParam,lParam);,不然會出現不正確的右鍵行為。int cx=GetSystemMetrics(SM_CXSCREEN);//得到屏幕寬度
int cy=GetSystemMetrics(SM_CYSCREEN);//得到屏幕高度
LONG dx = pllh->pt.x * 65535 / cx;
LONG dy = pllh->pt.y * 65535 / cy;
input[0].mi.dx = dx;
input[0].mi.dy = dy;
input[1].mi.dx = dx;
input[1].mi.dy = dy;
g)、需要發送鼠標右鍵按下和彈起兩個事件,不然會出現不正確的右鍵行為。
h)、WM_LBUTTONDOWN映射的是邏輯鼠標按鍵,wParam == WM_LBUTTONDOWN表示鼠標左鍵按下。而MOUSEEVENTF_LEFTDOWN映射的是物理鼠標按鍵,input[0].mi.dwFlags = MOUSEEVENTF_LEFTDOWN對應已切換功能的真實鼠標左鍵,所以實際表示發送的是鼠標右鍵消息。MOUSEEVENTF_LEFTUP意義相仿。
3、在應用程序的ExitInstance()中卸載全局鼠標鉤子:
UnhookWindowsHookEx(hhkMouseHook);
這樣就完成了所需要的功能。當按下"Ctrl+鼠標左鍵" 或 "數字鍵盤減號鍵 + 鼠標左鍵"時,相當于按了右鍵。