我必须编写一个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)
之前调用,因此应用程序无限期地阻塞。有人知道等待消息循环开始的方法吗?
提前致谢!
答案 0 :(得分: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
方法自动提供的效果相同的效果。
答案 1 :(得分:1)
我不知道是否有更好的方法,但您可以使用Mutex
并在致电Application.Run
时重置,并在致电Mutex.Wait()
时使用Applicationcontext.ExitThread()
。
答案 2 :(得分:1)
您可能会尝试等待,直到触发Application.Idle事件以允许用户调用RegisterHotKey。
答案 3 :(得分: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();
}