2011-09-12 55 views
5

我必須編寫一個用於註冊全局熱鍵的C#API。要接收WM_HOTKEY消息,我使用System.Windows.Forms.NativeWindow並與System.Windows.Forms.Application.Run(ApplicationContext)一起運行自己的消息循環。當用戶想要註冊一個熱鍵時,他必須運行一個名爲RegisterHotkey()的方法,該方法用System.Windows.Forms.ApplicationContext.ExitThread()停止消息循環,用RegisterHotKey()(P/Invoke)函數註冊熱鍵並再次啓動消息循環。這是必需的,因爲RegisterHotKey()必須在創建窗口的同一個線程中調用,而這個線程必須在運行消息循環的同一個線程中實例化。C# - 等待WinForms消息循環

問題是,如果用戶在啓動正在運行消息循環的線程後立即調用RegisterHotkey()方法,ApplicationContext.ExitThread()Application.Run(ApplicationContext)之前被調用,因此應用程序將無限期地阻塞。有沒有人知道等待消息循環開始的方法?

在此先感謝!

+0

這沒有什麼意義。只需在啓動消息循環之前調用RegisterHotkey()*即可。而且,真正的窗口是必需的,你必須有一個有效的句柄。 –

+0

由於我不想爲每個熱鍵啓動一個消息循環,我必須停止並重新啓動現有的以在同一個線程中調用RegisterHotKey()。由於我的代碼工作,我不認爲我需要一個真正的窗口,有效的句柄是由'NativeWindow.CreateHandle(CreateParams)'創建的。 –

回答

5

因此RegisterHotKey需要從創建該窗口的相同線程中調用並啓動消息循環。爲什麼不將RegisterHotKey的執行注入到自定義消息循環線程中?這樣您就不需要停止並重新啓動消息循環。您可以重複使用第一個,同時避免奇怪的競爭條件。

您可以使用ISynchronizeInvoke.Invoke將代理注入到另一個線程,該代理將該代理編組到該主題的ISynchronizeInvoke實例的線程上。這是如何完成的。

void Main() 
{ 
    var f = new Form(); 

    // Start your custom message loop here. 
    new Thread(
    () => 
    { 
     var nw = NativeWindow.FromHandle(f.Handle); 
     Application.Run(new ApplicationContext(f)); 
    } 

    // This can be called from any thread. 
    f.Invoke(
    (Action)(() => 
    { 
     RegisterHotKey(/*...*/); 
    }), null); 
} 

我不知道......也許你會想打電話給UnregisterHotKey也取決於你是以後的行爲。我對這些API並不熟悉,所以我不能評論它們如何使用。

如果你不想創建,那麼你很可能逃脫通過SendMessage等提交的自定義消息的線程和處理它NativeWindow.WndProc得到了ISynchronizeInvoke方法自動提供相同功能的任意Form實例。

+0

我想避免使用重量級的Form類,而是使用輕量級的NativeWindow類。但是,這似乎是獲得使用Invoke方法的最簡潔的解決方案......謝謝! –

+0

@Jonas:我已經懷疑那是基於你的另一個評論。我更新了我的答案:基本上,你應該能夠使用手動消息傳遞技術來模仿'Invoke'。 –

+0

這就是它,謝謝! –

1

我不知道是否有更好的方法,但您可以使用Mutex並在您致電Application.Run時重置它,並在致電Applicationcontext.ExitThread()時使用Mutex.Wait()

+1

感謝您的快速回復!我已經嘗試過使用'AutoResetEvent'類,但在這種情況下,我必須在Application.Run()之前調用'AutoResetEvent.Set()',這意味着對'ApplicationContext.ExitThread()在等待'AutoResetEvent.WaitOne()'後仍然可以... –

1

您可能會嘗試等待,直到Application.Idle事件被觸發以允許用戶調用RegisterHotKey。

+0

感謝您的回覆!如果庫在另一個帶有自己的消息循環的winforms應用程序中使用,會不會是一個問題? –

+0

當然可以在輔助線程上創建消息泵並顯示錶單。該線程可以獨立於第一個線程處理事件。這可能取決於您的API的使用方式。 – CommonSense

+0

我已經嘗試了你的方法,除了一件事以外,它工作的很好:Application.Idle事件只會觸發一次,並且不會識別消息泵的重新啓動:S –

0

我知道這個線程很舊,但是當我試圖解決一個問題時,我來到了這裏,花了一些時間看看接受的答案。它當然基本上是正確的,但由於代碼沒有編譯,不會啓動它創建的線程,即使我們修復它在創建f之前調用f.Invoke,也會拋出異常。

我修正了所有的問題,工作代碼如下。我會在評論中回答這個問題,但我沒有足夠的代表。這樣做後,我有點不確定強制在創建表單之前創建表單,或者在一個線程上執行'new Form',然後將它傳遞給另一個線程以完全創建(特別是因爲這很容易避免):

static void Main(string[] args) 
{ 
    AutoResetEvent are = new AutoResetEvent(false); 
    Form f = new Form(); 
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId); 

    new Thread(
     () => 
     { 
      Console.WriteLine(Thread.CurrentThread.ManagedThreadId); 
      var nw = NativeWindow.FromHandle(f.Handle); 
      are.Set(); 
      Application.Run(f); 
     }).Start(); 

    are.WaitOne(new TimeSpan(0, 0, 2)); 

    f.Invoke(
     (Action)(() => 
     { 
      Console.WriteLine(Thread.CurrentThread.ManagedThreadId); 
     }), null); 

    Console.ReadLine(); 
}