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)
之前被调用,因此应用程序无限期阻塞。有人知道等待消息循环启动的方法吗?
提前感谢!
因此需要从创建窗口并启动消息循环的同一线程调用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();
}