7

我必须编写一个 C# API 来注册全局热键。为了接收 WM_HOTKEY 消息,我使用 aSystem.Windows.Forms.NativeWindow并运行一个自己的消息循环System.Windows.Forms.Application.Run(ApplicationContext)。当用户想要注册一个热键时,他必须运行一个名为的方法,该方法RegisterHotkey()使用 停止消息循环System.Windows.Forms.ApplicationContext.ExitThread(),使用 (P/Invoke) 函数注册热键并RegisterHotKey()再次启动消息循环。这是必需的,因为RegisterHotKey()必须在创建窗口的同一线程内调用,而该线程必须在运行消息循环的同一线程内再次实例化。

问题是,如果用户RegisterHotkey()在启动正在运行消息循环的线程后不久调用该方法,则会在之前调用该方法,因此应用程序ApplicationContext.ExitThread()会无限期地阻塞。Application.Run(ApplicationContext)有人知道等待消息循环启动的方法吗?

提前致谢!

4

4 回答 4

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 不太熟悉,因此无法评论它们的使用方式。

如果您不希望Form创建任意实例,那么您可能可以通过SendMessage等方式向线程提交自定义消息并对其进行处理以获得与方法自动提供NativeWindow.WndProc的相同效果。ISynchronizeInvoke

于 2011-09-12T19:58:00.057 回答
1

我不知道是否有更好的方法,但是您可以使用 aMutex并在调用时重置它并在调用时Application.Run使用。Mutex.Wait()Applicationcontext.ExitThread()

于 2011-09-12T19:04:00.397 回答
1

您可以尝试等到 Application.Idle 事件被触发以允许用户调用 RegisterHotKey。

于 2011-09-12T19:12:06.547 回答
0

我知道这个线程很旧,但是我在尝试解决问题时来到这里并花了一些时间查看接受的答案。这当然基本上是正确的,但就目前而言,代码不会编译,不会启动它创建的线程,即使我们修复它在创建 f 之前调用 f.Invoke,也会引发异常。

我修复了所有这些,工作代码如下。我会把它放在答案的评论中,但我没有足够的代表。这样做之后,我有点不确定在以这种方式调用 Application.Run 之前强制创建表单的有效性,或者在一个线程上执行“新表单”然后将其传递给另一个线程以完全创建(特别是因为这很容易避免):

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();
}
于 2016-01-17T13:53:20.900 回答