2011-09-12 56 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(); 
}