c# -等待WinForms消息循环

本文关键字:消息 循环 WinForms 等待 | 更新日期: 2023-09-27 18:08:17

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

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

提前感谢!

c# -等待WinForms消息循环

因此需要从创建窗口并启动消息循环的同一线程调用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方法自动提供的相同效果。

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

您可以尝试等到应用程序。触发空闲事件以允许用户调用RegisterHotKey

我知道这个线程是旧的,但我来到这里时,试图解决一个问题,并花了一些时间看接受的答案。当然,这基本上是正确的,但就目前而言,代码不能编译,不能启动它创建的线程,即使我们修复了它在创建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();
}