2016-11-14 16 views
3

所以,我一直在開發一個類來處理VSTO插件中的Kwyboard輸入,到目前爲止我一直在使用Windows鉤子來取得相對的成功。VSTO Windows Hook keydown event called 10 times

有了這個代碼:

//..... 
    private const int WH_KEYBOARD = 2; 
    private const int WH_MOUSE = 7; 

    private enum WM : uint { 
     KEYDOWN = 0x0100, 
     KEYFIRST = 0x0100, 
     KEYLAST = 0x0108, 
     KEYUP = 0x0101, 
     MOUSELEFTDBLCLICK = 0x0203, 
     MOUSELEFTBTNDOWN = 0x0201, 
     MOUSELEFTBTNUP = 0x0202, 
     MOUSEMIDDBLCLICK = 0x0209, 
     MOUSEMIDBTNDOWN = 0x0207, 
     MOUSEMIDBTNUP = 0x0208, 
     MOUSERIGHTDBLCLK = 0x0206, 
     MOUSERIGHTBTNDOWN = 0x0204, 
     MOUSERIGHTBTNUP = 0x0205 
    } 

    private hookProcedure proc; 

    private static IntPtr hookID = IntPtr.Zero; 

    //Enganches 

    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] 
    private static extern IntPtr SetWindowsHookEx(int hookId, hookProcedure proc, IntPtr hInstance, uint thread); 

    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] 
    private static extern bool unHookWindowsHookEx(int hookId); 

    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] 
    private static extern IntPtr CallNextHookEx(IntPtr hookId, int ncode, IntPtr wparam, IntPtr lparam); 

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
    private static extern IntPtr GetModuleHandle(string name); 

    [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)] 
    public static extern int GetCurrentThreadId(); 

    public CPInputListener() { 

     proc = keyBoardCallback; 

     hookID = setHook(proc); 
    } 


    private IntPtr setHook(hookProcedure procedure){ 

     ProcessModule module = Process.GetCurrentProcess().MainModule; 
     uint threadId = (uint)GetCurrentThreadId(); 

     return SetWindowsHookEx(WH_KEYBOARD, procedure, IntPtr.Zero, threadId); 
    } 

    public void stopListeningAll() { 
     unHookWindowsHookEx(WH_KEYBOARD);//For now 
    } 


    private IntPtr keyBoardCallback(int ncode, IntPtr wParam, IntPtr lParam) { 

     if (ncode >= 0) { 
      //LPARAM pretty useless 

      Keys key = (Keys)wParam; 

      KeyEventArgs args = new KeyEventArgs(key); 

      onKeyDown(args);//for now 

     } 
     return CallNextHookEx(hookID, ncode, wParam, lParam); 
    } 
    //.... 

我成功地接收鍵盤輸入,但這裏是大謎;每次按下某個按鍵時,不管其速度如何快,事件(onKeyDown)都被精確調用10次,不多不少。

如果長時間按下該按鍵,該事件將繼續被調用,但是會被調用10次,而不是隻調用一次。

到目前爲止,我已經試過

  1. 使用wParam參數來調用鍵向上所需的事件:好像沒有工作,所有的代碼我已經看到了重點處理向下和向上事件,IntPtr wParam被使用,但是從那個變量我只能檢索沒有幫助的鍵碼。
  2. 使用lParamnCode:這些瓦爾是那些10個調用之間給予unconsistent值,ncode趨於檢索0和3的和lParam一些值,這似乎是託管內存不會忽略...

什麼我期望

我確實期望onKeyDown只在鍵被按下時調用一次,或者另一方面可以通過在鍵上調用該方法,我希望每個鍵釋放時只調用一次。

如何繞過這個

如果我不能找到一個合理的答案,我想使用由計時器自定義丟棄所有的召喚,並使用只有最後一個,你會推薦這個,如果一切都失敗了?

非常感謝!快樂,善良! :D

回答

3

首先,您必須篩選正確的ncode以僅獲取您應該處理的按鍵。 (例如,您不應該處理HC_NOREMOVE。)
然後,您必須檢查使用lParam中的標誌是否是KeyDownKeyUp事件。

如果密鑰長按,Win32會將多個KeyDown事件合併爲一個調用,所以您不必在此處做任何特殊的事情。但是如果你只想得到最後的KeyUp事件,那麼你必須檢查lParam中的另一個標誌。

所以,在這裏您需要更改代碼:作爲評論請

private IntPtr keyBoardCallback(int ncode, IntPtr wParam, IntPtr lParam) 
{ 
    // Feel free to move the const to a private field. 
    const int HC_ACTION = 0; 
    if (ncode == HC_ACTION) 
    { 
     Keys key = (Keys)wParam; 
     KeyEventArgs args = new KeyEventArgs(key); 

     bool isKeyDown = ((ulong)lParam & 0x40000000) == 0; 
     if (isKeyDown) 
      onKeyDown(args); 
     else 
     { 
      bool isLastKeyUp = ((ulong)lParam & 0x80000000) == 0x80000000; 
      if (isLastKeyUp) 
       onKeyUp(args); 
     } 
    } 
    return CallNextHookEx(hookID, ncode, wParam, lParam); 
} 

編輯:
不幸的是這些參數的文檔是相當稀少。

一個「暗示」不處理任何其他然後HC_ACTION可以發現here,他說:

if (nCode < 0) // do not process message 
    return ...; 

// ... 
switch (nCode) 
{ 
    case HC_ACTION: 
     // ... do something ... 
     break; 

    default: 
     break; 
} 
// ... 
return CallNextHookEx(...); 

另一個配套語句在這裏做:
Why does my keyboard hook receive the same key-up and key-down events multiple times?

lParam的內容被定義as follows

typedef struct tagKBDLLHOOKSTRUCT { 
    DWORD  vkCode; 
    DWORD  scanCode; 
    DWORD  flags; 
    DWORD  time; 
    ULONG_PTR dwExtraInfo; 
} 

(正如一個提醒:DWORD這裏是在x86和x64平臺上的大小4 bytes。)

lParamflags的文件可以發現herehere
在這個環節它描述了

  • 位30(= 0x40000000)是以前國家重點
    1如果密鑰下來,0如果密鑰以前導致此呼叫的新密鑰狀態)
  • 位31(= 0x80000000)是過渡狀態
    0上的按鍵和1上鍵釋放現在

術語「以前國家重點」是相當混亂,但有效的它目前的狀態正好相反(因爲只有向上或向下並沒有第三個國家)。

當「鍵盤的自動重複功能」被激活時,即按下按鍵足夠長時,過渡狀態尤其相關。

另一個樣品(使用VC7)可以發現here

if (HIWORD (lParam) & 0xC000) 
    // Key up without autorepeat 
else 
    // Key down 

哪裏0xC000僅僅是0x4000 || 0x8000並定義了密鑰被釋放,並創造了一個關鍵事件的方式。

總而言之,這很令人困惑,但仍然如此。
也許還有其他一些可以更好地描述這種情況的鏈接,但我猜想在像這樣的時代,新的應用程序開發「應該完成」在微小的沙箱(如UWP)中,VSTO肯定會死於製作newer Office add-ins的方式寫在HTML and JavaScript,沒有人關心低層次的鉤子了。

+0

確實!它確實有用!非常感謝你的幫助! 現在,爲了學習,你介意解釋我(或處理任何文檔)關於你如何弄清楚這些標誌? 再次感謝! –

+0

@LuisMoyano很高興能幫到你!爲了學習,我彙集了所有相關文件和缺少的解釋。我希望這有幫助。現在,只需享受血腥的細節。 ;-) – haindl

相關問題