Aaron Liu的部落格,紀錄我的生活點點滴滴

[C#]全域鍵盤事件攔截(Low Level Keyboard Hook)

| 2009年1月11日 星期日
寫XDHome的時候其實只有兩個技術問題需要突破,一個是上次提過的影片播放,另一個就是要如何在視窗不被focus的時候也能夠抓到鍵盤。

基本上要解決這個問題,必須要呼叫Windows API去設定所謂的Low Level Hook,網路上有一些範例可以抓,基本上我參考了George MamaladzeHuan Lin兩人的著作,將程式改為指針對Keyboard的KeyDown事件的版本,程式碼如下:

Low Level Keyboard Hook服務程式
此段程式碼提供兩個static KeyDown Event供客戶端程式碼新增Even Handler

// Low Level Keyboard Hook
public class KeyboardHook
{

// System Code: Low Level Keyboard Hook
private const int WH_KEYBOARD_LL = 13;

// System Event Code: Key Down Event
private const int WM_KEYDOWN = 0x100;

// System Event Code: System Key Down Event
private const int WM_SYSKEYDOWN = 0x104;

// Stores the handle to the Keyboard hook procedure.
private static int s_KeyboardHookHandle;

//是否只由這個Global Hook抓取鍵盤事件
public static bool globalControlOnly = false;

//Private KeyDown Event, 與GlobalKeyDown配合使用
private static event KeyEventHandler _globalKeyDown;

// Public KeyDown Event
// 每次只能一個Event Handler處理這個Event
// 加入和解除EventHandler時會自動安裝和解除Hook
public static event KeyEventHandler GlobalKeyDown
{
add
{
KeyboardHook.HookKeyboard();
KeyboardHook._globalKeyDown += value;
}
remove
{
KeyboardHook._globalKeyDown -= value;
KeyboardHook.UnhookKeyboard();
}
}




// 當hook抓到key event時的處理程序
public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);

// Hook handle
private static int m_HookHandle = 0;

// Keyboard Hook函式指標
private static HookProc m_KbdHookProc;

// WinAPI 取得Module Handle
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);

// WinAPI 加入Hook
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);


// WinAPI 解除Hook
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int idHook);


// WinAPI 將Event傳給下一個Hook,如不執行此項,則只有這個Hook會被執行
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);


private static void HookKeyboard()
{
if (m_HookHandle == 0)
{

KeyboardHook.s_KeyboardHookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, m_KbdHookProc,
Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);

using (Process curProcess = Process.GetCurrentProcess())
{
using (ProcessModule curModule = curProcess.MainModule)
{
m_KbdHookProc = new HookProc(KeyboardHookProc);
m_HookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, m_KbdHookProc, GetModuleHandle(curModule.ModuleName), 0);
}
}

if (m_HookHandle == 0)
{
throw new Exception("Install Global Keyboard Hook Faild.");
}

}
}


private static void UnhookKeyboard()
{

if (m_HookHandle != 0)
{
bool ret = UnhookWindowsHookEx(m_HookHandle);
if (ret)
{
m_HookHandle = 0;
}
else
{
throw new Exception("Uninstall Global Keyboard Hook Faild.");
}

}

}





private static int KeyboardHookProc(int nCode, IntPtr wParam, IntPtr lParam)
{
bool handled = false;

if(nCode >= 0)
{

if ((int)wParam == WM_KEYDOWN || (int)wParam == WM_SYSKEYDOWN)
{
KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));

Keys keyData = (Keys)MyKeyboardHookStruct.VirtualKeyCode;
KeyEventArgs e = new KeyEventArgs(keyData);

if (KeyboardHook.globalControlOnly)
{
e.Handled = true;
}
else
{
e.Handled = false;
}

_globalKeyDown.Invoke(null, e);

handled = e.Handled;
}

}


if (KeyboardHook.globalControlOnly) return -1;

return CallNextHookEx(s_KeyboardHookHandle, nCode, wParam, lParam);


}



[StructLayout(LayoutKind.Sequential)]
private struct KeyboardHookStruct
{
///
/// Specifies a virtual-key code. The code must be a value in the range 1 to 254.
///

public int VirtualKeyCode;
///
/// Specifies a hardware scan code for the key.
///

public int ScanCode;
///
/// Specifies the extended-key flag, event-injected flag, context code, and transition-state flag.
///

public int Flags;
///
/// Specifies the Time stamp for this message.
///

public int Time;
///
/// Specifies extra information associated with the message.
///

public int ExtraInfo;
}

}


客戶端程式碼,Handel static的全域KeyDown Event

private void KeyboardHook_KeyDown(object sender, KeyEventArgs e)
{
string key = e.KeyCode.ToString(); //取得KeyCode字串
//TODO: 加入處理KeyDown事件的程式
}


要加入全域事件時

KeyboardHook.globalControlOnly = true; //只有Global KeyDown會起作用,其他程式不能攔截鍵盤事件
KeyboardHook.GlobalKeyDown += KeyboardHook_KeyDown;


要取消全域事件處理時

KeyboardHook.GlobalKeyDown -= KeyboardHook_KeyDown;

0 意見:

張貼留言