2011-09-12 1 views
5

Je dois écrire une API C# pour l'enregistrement des raccourcis clavier globaux. Pour recevoir le message WM_HOTKEY, j'utilise un System.Windows.Forms.NativeWindow et exécute une propre boucle de message avec System.Windows.Forms.Application.Run(ApplicationContext). Lorsque l'utilisateur veut enregistrer une touche de raccourci, il doit exécuter une méthode appelée RegisterHotkey() qui arrête la boucle de message avec System.Windows.Forms.ApplicationContext.ExitThread(), enregistre la touche de raccourci avec la fonction RegisterHotKey() (P/Invoke) et redémarre la boucle de message. Ceci est nécessaire, car RegisterHotKey() doit être appelée dans le même thread que celui qui a créé la fenêtre, qui doit à nouveau être instancié dans le même thread qui exécute la boucle de message.C# - En attente de la boucle de message WinForms

Le problème est que si l'utilisateur appelle la méthode peu RegisterHotkey() après le démarrage du fil qui exécute la boucle de message, ApplicationContext.ExitThread() est appelé avant Application.Run(ApplicationContext) et donc les blocs d'application indéfiniment. Est-ce que quelqu'un connaît une approche pour attendre le début d'une boucle de message?

Merci d'avance!

+0

Cela n'a pas beaucoup de sens. Appelez simplement RegisterHotkey() * avant de commencer la boucle du message. En outre, une vraie fenêtre est requise, vous devez avoir un handle valide. –

+0

Puisque je ne veux pas démarrer une boucle de message par raccourci clavier, je dois arrêter et redémarrer celui existant pour appeler RegisterHotKey() dans le même fil. Et puisque mon code fonctionne, je ne pense pas avoir besoin d'une vraie fenêtre, le handle valide est créé par 'NativeWindow.CreateHandle (CreateParams)' à la place. –

Répondre

5

Donc, RegisterHotKey doit être appelé à partir du même thread qui a créé la fenêtre et a lancé la boucle de message. Pourquoi ne pas injecter l'exécution de RegisterHotKey dans votre thread de boucle de message personnalisé? De cette façon, vous n'avez pas besoin d'arrêter et de redémarrer la boucle de message. Vous pouvez simplement réutiliser le premier que vous avez commencé et éviter les étranges conditions de course en même temps.

Vous pouvez injecter un délégué dans un autre thread à l'aide de ISynchronizeInvoke.Invoke qui va rassembler ce délégué sur le thread hébergeant l'instance ISynchronizeInvoke. Voici comment cela pourrait être fait.

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); 
} 

Je ne sais pas ... peut-être que vous voulez appeler UnregisterHotKey ainsi en fonction du comportement que vous recherchez. Je ne suis pas familier avec ces API, donc je ne peux pas commenter sur la façon dont ils pourraient être utilisés.

Si vous ne voulez pas que Form exemple arbitraire créé alors vous pourriez probablement avec soumission d'un message personnalisé au fil via SendMessage et comme et le traitement de NativeWindow.WndProc pour obtenir le même effet que les méthodes ISynchronizeInvoke fournissent automatiquement.

+0

Je voulais éviter d'utiliser la classe Form poids lourd et utiliser la classe légère NativeWindow à la place. Mais cela semble être la solution la plus propre pour avoir l'opportunité d'utiliser l'approche Invoke ... Merci! –

+0

@Jonas: Je me doutais déjà que basé sur un autre de vos commentaires. J'ai mis à jour ma réponse: Fondamentalement, vous devriez être capable d'imiter 'Invoke' en utilisant des techniques de passage de message manuel. –

+0

Voilà, merci! –

1

Je ne sais pas s'il y a une meilleure façon, mais vous pouvez utiliser un Mutex et le réinitialiser lorsque vous appelez Application.Run et utilisez Mutex.Wait() lorsque vous appelez Applicationcontext.ExitThread().

+1

Merci pour votre réponse rapide! J'ai déjà essayé ceci avec la classe 'AutoResetEvent', mais dans ce cas je devrais appeler' AutoResetEvent.Set() 'avant' Application.Run() ', ce qui signifie qu'un appel à' ApplicationContext.ExitThread() ' après avoir attendu avec 'AutoResetEvent.WaitOne()' est encore possible ... –

1

Vous pouvez attendre jusqu'à ce que l'événement Application.Idle soit déclenché pour permettre à l'utilisateur d'appeler RegisterHotKey.

+0

Merci pour votre réponse! Ne serait-ce pas un problème si la bibliothèque est utilisée dans une autre application winforms avec sa propre boucle de messages? –

+0

Il est certainement possible de créer une pompe de message sur un thread secondaire et d'afficher un formulaire. Ce thread peut ensuite traiter les événements indépendamment du premier thread. Cela peut dépendre de la façon dont votre API est utilisée. – CommonSense

+0

J'ai essayé votre approche et cela fonctionne très bien, sauf une chose: L'événement Application.Idle ne se déclenche qu'une seule fois et ne reconnaît pas le redémarrage du message pump: S –

0

Je sais que ce sujet est ancien, mais je suis venu ici en essayant de résoudre un problème et j'ai passé du temps à examiner la réponse acceptée. Il est bien sûr fondamentalement correct, mais en l'état le code ne compile pas, ne démarre pas le thread qu'il crée, et même si on le corrige, il appelle f.Invoke avant que f soit créé, donc jette des exceptions.

J'ai corrigé tout cela et le code de travail est ci-dessous. Je l'aurais mis dans un commentaire sur la réponse mais je n'ai pas suffisamment de rep. Après avoir fait cela, je ne suis pas sûr de la validité de forcer la création du formulaire avant d'appeler Application.Run de cette façon, ou de faire une nouvelle forme sur un thread et de le passer à un autre pour être entièrement créé (en particulier puisque cela est facilement évité):

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(); 
} 
Questions connexes