C # - Ожидание цикла сообщений WinForms - PullRequest
5 голосов
/ 12 сентября 2011

Мне нужно написать 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), и поэтому приложение блокируется на неопределенный срок. Кто-нибудь знает подход для ожидания запуска цикла сообщений?

Заранее спасибо!

Ответы [ 4 ]

5 голосов
/ 12 сентября 2011

Так что RegisterHotKey необходимо вызывать из того же потока, который создал окно и запустил цикл обработки сообщений.Почему бы не внедрить выполнение RegisterHotKey в пользовательскую цепочку сообщений?Таким образом, вам не нужно останавливать и перезапускать цикл сообщений.Вы можете просто использовать первый, который вы начали, и одновременно избежать странных условий гонки.

Вы можете внедрить делегат в другой поток, используя ISynchronizeInvoke.Invoke, который будет маршалировать этот делегат в поток, в котором находится * 1006.* пример.Вот как это можно сделать.

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 голос
/ 12 сентября 2011

Вы можете попробовать подождать, пока не сработает событие Application.Idle, чтобы позволить пользователю вызвать RegisterHotKey.

1 голос
/ 12 сентября 2011

Я не знаю, есть ли лучший способ, но вы могли бы использовать Mutex и сбросить его при вызове Application.Run и использовать Mutex.Wait() при вызове Applicationcontext.ExitThread().

0 голосов
/ 17 января 2016

Я знаю, что эта ветка старая, но я пришел сюда, чтобы попытаться решить проблему, и потратил некоторое время на рассмотрение принятого ответа. Это, конечно, в основном правильно, но в нынешнем виде код не компилируется, не запускает созданный им поток, и даже если мы исправим его, он вызывает f.Invoke до того, как будет создан f, поэтому выдает исключения.

Я исправил все это, и рабочий код приведен ниже. Я бы поставил его в комментарии к ответу, но у меня недостаточно представителей. Сделав это, я немного не уверен в правильности принудительного создания формы перед вызовом 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();
}
...