2011-09-12 13 views
5

Tengo que escribir una API C# para registrar teclas rápidas globales. Para recibir el mensaje WM_HOTKEY, uso un System.Windows.Forms.NativeWindow y ejecuto un bucle de mensaje propio con System.Windows.Forms.Application.Run(ApplicationContext). Cuando el usuario desea registrar una tecla de acceso directo, debe ejecutar un método llamado RegisterHotkey() que detiene el ciclo de mensajes con System.Windows.Forms.ApplicationContext.ExitThread(), registra la tecla de acceso directo con la función RegisterHotKey() (P/Invocar) y vuelve a iniciar el ciclo de mensajes. Esto es necesario, porque RegisterHotKey() debe llamarse dentro del mismo hilo que creó la ventana, que nuevamente debe ser instanciado dentro del mismo hilo que ejecuta el ciclo de mensajes.C# - Esperando el bucle de mensajes de WinForms

El problema es que si el usuario llama al método RegisterHotkey() poco después de comenzar el hilo que se está ejecutando el bucle de mensajes, ApplicationContext.ExitThread() es llamado antes Application.Run(ApplicationContext) y por lo tanto los bloques de aplicación de forma indefinida. ¿Alguien conoce un enfoque para esperar a que se inicie un ciclo de mensajes?

¡Gracias de antemano!

+0

Esto no tiene mucho sentido. Simplemente llame a RegisterHotkey() * antes * de que inicie el ciclo del mensaje. Además, se requiere una ventana real, debe tener un identificador válido. –

+0

Como no quiero iniciar un bucle de mensaje por tecla de acceso rápido, tengo que detener y reiniciar el existente para llamar a RegisterHotKey() dentro del mismo hilo. Y dado que mi código funciona, no creo que necesite una ventana real, el identificador válido lo crea 'NativeWindow.CreateHandle (CreateParams)' en su lugar. –

Respuesta

5

Así que RegisterHotKey necesita ser llamado desde el mismo hilo que creó la ventana y comenzó el ciclo de mensajes. ¿Por qué no inyectar la ejecución de RegisterHotKey en su cadena de bucle de mensaje personalizado? De esta forma, no necesita detenerse y reiniciar el ciclo de mensajes. Puedes reutilizar el primero que comenzaste y evitar las condiciones de carrera extraña al mismo tiempo.

Puede inyectar un delegado en otro hilo usando ISynchronizeInvoke.Invoke que dirigirá ese delegado al hilo que aloja la instancia ISynchronizeInvoke. Aquí es cómo se podría hacer.

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

No sé ... tal vez tendrá que llamar UnregisterHotKey así en función del comportamiento que está después. No estoy tan familiarizado con estas API, así que no puedo comentar cómo se podrían usar.

Si no desea que arbitraria Form instancia creada, entonces probablemente podría salirse con la presentación de un mensaje personalizado para el hilo a través de SendMessage y similares y procesarla en NativeWindow.WndProc para obtener el mismo efecto que los métodos ISynchronizeInvoke proporcionan automáticamente.

+0

Quería evitar el uso de la clase de formulario pesado y usar la clase ligera NativeWindow en su lugar. Pero esta parece ser la solución más limpia para tener la oportunidad de usar el enfoque Invoke ... ¡Gracias! –

+0

@Jonas: Ya sospechaba que basaste otro de tus comentarios. Actualicé mi respuesta: Básicamente, debería poder imitar a 'Invoke' utilizando técnicas manuales para pasar mensajes. –

+0

Eso es todo, gracias! –

1

No sé si hay una mejor manera, pero se puede usar un Mutex y restablecerla cuando se llama Application.Run y utilizar Mutex.Wait() al llamar Applicationcontext.ExitThread().

+1

¡Gracias por su rápida respuesta! Ya probé esto con la clase 'AutoResetEvent', pero en este caso tendría que llamar a' AutoResetEvent.Set() 'antes de' Application.Run() ', lo que significa que una llamada a' ApplicationContext.ExitThread() ' después de esperar con 'AutoResetEvent.WaitOne()' todavía es posible ... –

1

Puede intentar esperar hasta que se active el evento Application.Idle para permitir que el usuario llame a RegisterHotKey.

+0

¡Gracias por su respuesta! ¿No sería esto un problema si la biblioteca se utiliza en otra aplicación de winforms con su propio ciclo de mensajes? –

+0

Es ciertamente posible crear una bomba de mensajes en una secuencia secundaria y mostrar un formulario. Ese hilo puede procesar eventos independientemente del primer hilo. Puede depender de cómo se usa su API. – CommonSense

+0

He intentado su enfoque y funciona muy bien, excepto una cosa: el evento Application.Idle solo se dispara una vez y no reconoce el reinicio de la bomba de mensajes: S –

0

Sé que este hilo es viejo, pero vine aquí cuando trataba de resolver un problema y dediqué un tiempo a mirar la respuesta aceptada. Por supuesto, es básicamente correcto, pero tal como está, el código no se compila, no inicia el hilo que crea, e incluso si lo arreglamos, llama a f.Invocar antes de que f se cree, por lo que arroja excepciones.

Lo arreglé todo y código de trabajo está por debajo. Lo pondría en un comentario sobre la respuesta, pero no tengo suficientes representantes. Habiendo hecho eso, estoy un poco inseguro de la validez de forzar el formulario a ser creado antes de llamar a Application.Run de esta manera, o de hacer 'new Form' en un thread y luego pasarlo a otro para ser completamente creado (particularmente porque esto se evita fácilmente):

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