2012-02-01 145 views
1

我有業務需求,對於消息框,用戶不能按回車鍵接受默認選項,但按選擇的關鍵。例如。給定一個帶有選項Yes/No的MessageBox,用戶必須按下Y或N鍵。現在我實現了這個使用以下鍵盤鉤子,但是當代碼返回,KeyUp事件也得到返回到調用代碼。淨鍵盤鉤子額外KeyUp事件

所以,問題是:如何返回到調用代碼之前刷新所有的鍵盤事件?

我已經刪除鍋爐板代碼,但如果你需要它,請告知。

調用代碼:

private static ResultMsgBox MsgResultBaseNoEnter(string msg, string caption, uint options) 
    { 
     ResultMsgBox res; 
     _hookID = SetHook(_proc); 
     try 
     { 
      res = MessageBox(GetForegroundWindow(), msg, caption, options); 
     } 
     finally 
     { 
      UnhookWindowsHookEx(_hookID); 
     } 
     return res; 
    } 

和鉤代碼:

private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) 
    { 
     if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN) 
     { 
      int vkCode = Marshal.ReadInt32(lParam); 
      if (vkCode == VK_RETURN) 
       return (IntPtr)(-1); 
     } 
     return CallNextHookEx(_hookID, nCode, wParam, lParam); 
    } 
+1

我不在乎你是否已經推遲了這30次或更多 - 再次推回它。重寫像這樣的默認行爲是壞的 - 他們沒有意識到,對於這樣的舉動,他們實際上增加了每個使用這個系統的用戶的培訓成本? – 2012-02-01 08:03:13

+0

這是我繼承的自定義構建。 – Cheval 2012-02-03 06:55:01

+1

如果您想要自定義消息框,請將它們實現爲自定義消息框,而不是在其上鍵入鉤子。 – CodesInChaos 2012-02-03 13:21:21

回答

1

添加幾行代碼在類的地方(或可通過其他類使用的一些靜態類):

[StructLayout(LayoutKind.Sequential)] 
public class MSG 
{ 
    public IntPtr hwnd; 
    public uint message; 
    public IntPtr wParam; 
    public IntPtr lParam; 
    public uint time; 
    int x; 
    int y; 
} 

[DllImport("user32")] 
public static extern bool PeekMessage([Out]MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, int wRemoveMsg); 

/// <summary> 
/// Examines the message queue for key messages. 
/// </summary> 
/// <param name="remove">If this parameter is true, the returned message is also removed from the queue.</param> 
/// <returns>Returns the next available key message, or null if there is no key message available.</returns> 
public static MSG PeekKeyMessage(bool remove) 
{ 
    MSG msg = new MSG(); 
    if (PeekMessage(msg, IntPtr.Zero, 0x0100 /*WM_KEYFIRST*/, 0x0109 /*WM_KEYLAST*/, remove ? 1 : 0)) 
     return msg; 
    return null; 
} 

public static void RemoveAllKeyMessages() 
{ 
    while (PeekKeyMessage(true) != null) ; // Empty body. Every call to the method removes one key message. 
} 

調用RemoveAllKeyMessages()不正是你想要的。

+0

隨着一個小小的變化,我會在答覆中顯示,這似乎是答案。謝謝。 – Cheval 2012-02-06 03:18:51

0

其實你可以不刷新鍵盤事件,但可以防止該事件由線程的消息被接收循環。
您應該安裝一個處理程序WH_GETMESSAGE掛鉤。鉤子程序的lParam是指向MSG結構的指針。在檢查結構之後,您可以更改它以避免將消息傳遞給呼叫消息處理器。您應該將消息更改爲WM_NULL。
在.NET實際的過程有點長的,需要一個單獨的文章。但是,簡單地說,這裏是如何:

拷貝類完全一樣就是在項目中的一個新的C#文件:

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Runtime.InteropServices; 

namespace Unicorn 
{ 
    public static class HookManager 
    { 
     #region Fields 

     private delegate int HookDelegate(int ncode, IntPtr wParam, IntPtr lParam); 
     private static HookDelegate getMessageHookProc; 
     private static IntPtr getMessageHookHandle; 
     private static List<EventHandler<GetMessageHookEventArgs>> getMessageHandlers = 
      new List<EventHandler<GetMessageHookEventArgs>>(); 

     #endregion 
     #region Private Methods - Installation and Uninstallation 

     private static void InstallGetMessageHook() 
     { 
      if (getMessageHookProc != null) 
       return; 
      getMessageHookProc = new HookDelegate(GetMessageHookProc); 
      getMessageHookHandle = SetWindowsHookEx(WH_GETMESSAGE, getMessageHookProc, 0, GetCurrentThreadId()); 
     } 

     private static void UninstallGetMessageHook() 
     { 
      if (getMessageHookProc == null) 
       return; 
      UnhookWindowsHookEx(getMessageHookHandle); 
      getMessageHookHandle = IntPtr.Zero; 
      getMessageHookProc = null; 
     } 

     #endregion 
     #region Public Methods - Add and Remove Handlers 

     public static void AddGetMessageHookHandler(EventHandler<GetMessageHookEventArgs> handler) 
     { 
      if (getMessageHandlers.Contains(handler)) 
       return; 
      getMessageHandlers.Add(handler); 
      if (getMessageHandlers.Count == 1) 
       InstallGetMessageHook(); 
     } 

     public static void RemoveGetMessageHookHandler(EventHandler<GetMessageHookEventArgs> handler) 
     { 
      getMessageHandlers.Remove(handler); 
      if (getMessageHandlers.Count == 0) 
       UninstallGetMessageHook(); 
     } 

     #endregion 
     #region Private Methods - Hook Procedures 

     [DebuggerStepThrough] 
     private static int GetMessageHookProc(int code, IntPtr wParam, IntPtr lParam) 
     { 
      if (code == 0) // HC_ACTION 
      { 
       MSG msg = new MSG(); 
       Marshal.PtrToStructure(lParam, msg); 
       GetMessageHookEventArgs e = new GetMessageHookEventArgs() 
       { 
        HWnd = msg.hwnd, 
        Msg = msg.message, 
        WParam = msg.wParam, 
        LParam = msg.lParam, 
        MessageRemoved = (int)wParam == 1, 
        ShouldApplyChanges = false 
       }; 

       foreach (var handler in getMessageHandlers.ToArray()) 
       { 
        handler(null, e); 
        if (e.ShouldApplyChanges) 
        { 
         msg.hwnd = e.HWnd; 
         msg.message = e.Msg; 
         msg.wParam = e.WParam; 
         msg.lParam = e.LParam; 
         Marshal.StructureToPtr(msg, (IntPtr)lParam, false); 
         e.ShouldApplyChanges = false; 
        } 
       } 
      } 

      return CallNextHookEx(getMessageHookHandle, code, wParam, lParam); 
     } 

     #endregion 
     #region Win32 stuff 

     private const int WH_KEYBOARD = 2; 
     private const int WH_GETMESSAGE = 3; 
     private const int WH_CALLWNDPROC = 4; 
     private const int WH_MOUSE = 7; 
     private const int WH_CALLWNDPROCRET = 12; 

     [StructLayout(LayoutKind.Sequential)] 
     public class MSG 
     { 
      public IntPtr hwnd; 
      public uint message; 
      public IntPtr wParam; 
      public IntPtr lParam; 
      public uint time; 
      int x; 
      int y; 
     } 


     [DllImport("USER32.dll", CharSet = CharSet.Auto)] 
     private static extern IntPtr SetWindowsHookEx(int idHook, HookDelegate lpfn, int hMod, int dwThreadId); 

     [DllImport("USER32.dll", CharSet = CharSet.Auto)] 
     private static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); 

     [DllImport("USER32.dll", CharSet = CharSet.Auto)] 
     private static extern bool UnhookWindowsHookEx(IntPtr hhk); 

     [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto)] 
     private static extern int GetCurrentThreadId(); 
     #endregion 
    } 

    #region EventArgs 

    public class GetMessageHookEventArgs : EventArgs 
    { 
     public uint Msg { get; set; } 
     public IntPtr HWnd { get; set; } 
     public IntPtr WParam { get; set; } 
     public IntPtr LParam { get; set; } 

     public bool MessageRemoved { get; set; } 
     public bool ShouldApplyChanges { get; set; } 
    } 

    #endregion 
} 

這是一個輔助類,做你所需要的。我的實際類稍微長一些,可以處理更多的鉤子類型,但是我清除了代碼以使其更小。

在此之後,你的代碼應該是這樣的:

private static void GetMessageProcHook(object sender, Unicorn.GetMessageHookEventArgs e) 
{ 
    if (e.Msg == 0x100 && (Keys)e.WParam == Keys.Return) // WM_KEYDOWN 
    { 
     // swallow the message 
     e.Msg = 0; // WM_NULL 
     e.WParam = IntPtr.Zero; 
     e.LParam = IntPtr.Zero; 
     e.ShouldApplyChanges = true; // This will tell the HookManager to copy the changes back. 
    } 
} 

private static ResultMsgBox MsgResultBaseNoEnter(string msg, string caption, uint options) 
{ 
    ResultMsgBox res; 
    Unicorn.HookManager.AddGetMessageHookHandler(GetMessageProcHook); 
    try 
    { 
     res = MessageBox(GetForegroundWindow(), msg, caption, options); 
    } 
    finally 
    { 
     Unicorn.HookManager.RemoveGetMessageHookHandler(GetMessageProcHook); 
    } 
    return res; 
} 

如果您遇到任何其他問題,讓我知道。

+0

感謝你的支持,但不幸的是它沒有解決問題。按下Y鍵後,它仍然從消息框中返回,並且KeyUp事件在調用窗口中觸發。在窗口的KeyUp事件處理程序中,事件列出數據中的Y字符。 – Cheval 2012-02-03 06:36:15

+0

我也嘗試了「else if(e.Msg == WM_KEYUP)//吞下上面的消息代碼」,但是這並沒有阻止事件發生。 – Cheval 2012-02-03 07:12:15

+0

所以返回鍵工作正常,對不對?另外還有一個要求:你想按下'Y'按鈕後,鍵入事件不會進入調用窗口。這不能這樣做,因爲當消息框收到'y'鍵時,消息框關閉,返回到前一個窗口後,卸載鉤子。所以你在第二條評論中提到的是不行的。我會繼續努力,並將其附加到我的答案中,但最好是在您的問題中提及您的新要求。 – 2012-02-03 08:17:06

0

感謝MD.Unicorn。 PeekMessage和RemoveAllKeyMessages方法運行良好,除了一個小的改變。

我一直在做這個問題的更多的研究,顯然這是一個已知的問題(甚至在微軟連接列爲一個不會修復問題),MessageBox接受KeyDown事件的輸入選項,然後關閉窗口,那麼返回的窗口將在稍後收到KeyUp事件。

當我知道這KeyUp事件不會立即出現在將來的某個時候,但。 (本身的RemoveAllKeyMessages沒有解決這個問題。)我只是調整輪詢它的方法如下。我已將該方法重命名爲指示它是MessageBox問題的自定義用法。

public static void RemoveMessageBoxKeyMessages() 
    { 
     //Loop until the MessageBox KeyUp event fires 
     var timeOut = DateTime.Now; 
     while (PeekKeyMessage(false) == null && DateTime.Now.Subtract(timeOut).TotalSeconds < 1) 
      System.Threading.Thread.Sleep(100); 

     while (PeekKeyMessage(true) != null) ; // Empty body. Every call to the method removes one key message. 
    } 

除非有一個明顯的缺陷(比如果消息框不發送KeyUp事件等),這應該是一種具有類似問題的其他解決方案。

+0

@Chevel:當用戶按下一個按鍵然後釋放按鍵時,有一段時間按住按鍵。所以你是對的。關鍵事件不會在關鍵事件發生後關閉消息框完全發送。但是,如果用戶持有密鑰的時間超過100毫秒,則您的解決方案可能無法正常工作。在這種情況下,你可能不得不做其他事情。例如,等待keyup事件。但它可能會凍結應用程序。 – 2012-02-06 05:42:06

+0

我測試了用戶按下按鍵超過100毫秒的情況,並且KeyUp事件也被髮送到調用代碼。這可能是讓事件首先通過的原因。關於潛在的凍結也很好。爲了以防萬一,我已經更新了代碼以放置一段時間。 – Cheval 2012-02-07 00:22:45