2010-05-21 22 views
16

Estoy implementando una pequeña aplicación (observador) que necesita "adjuntarse" a la parte inferior de otra ventana (observado). Este último no es una ventana dentro de la aplicación.Escuchar otra ventana Cambiar el tamaño de eventos en C#

En este momento resolví obteniendo el hWnd de la ventana y consultando periódicamente en un hilo la ubicación de la ventana observada, moviendo la ventana del observador en consecuencia.

Sin embargo, esta es una solución muy poco elegante. Lo que me gustaría hacer es escuchar el evento de cambio de tamaño de la ventana observada para que el observador solo reaccione cuando sea necesario.

Supongo que debería usar un gancho, y encontré muchas formas de hacerlo, pero mi falta de conocimiento del C WinAPI me está bloqueando para comprender qué gancho necesito crear y cómo (pinvoke/parameters/etc)

estoy bastante seguro de que esto es bastante trivial, y algunos de ustedes familiarizados con C/C++ y API de Windows tendrá la respuesta a la mano;)

Gracias

+0

Solo se menciona C# en el título y las etiquetas. ¿Estás usando C# o C? – Yuvi

+0

@Yuvi, supongo que quiere escuchar un "evento" en C# que se activa en otra aplicación que no está en su "dominio". –

+0

@Yuvi Menciono C# como mi programa base está escrito en C#. No es realmente cómodo usar C++ para aplicaciones de negocios;) –

Respuesta

7

Expandiendo la respuesta de Chris Taylor: En lugar de hacer la interoperabilidad nativa usted mismo, puede usar ManagedWinApi, que contiene una clase Hook.

EDITAR: Para usar ManagedWinApi. En algún lugar de su código:

Hook MyHook = new Hook(HookType.WH_CALLWNDPROC, false, false); 
MyHook.Callback += MyHookCallback; 
MyHook StartHook(); 

Para la devolución de llamada, referencia CallWndProc y CWPSTRUCT:

private static int MyHookCallback(int code, IntPtr wParam, IntPtr lParam, ref bool callNext) 
{ 
    if (code >= 0) 
    { 
     // You will need to define the struct 
     var message = (CWPSTRUCT)Marshal.PtrToStructure(lParam, typeof(CWPSTRUCT)); 
     // Do something with the data 
    } 
    return 0; // Return value is ignored unless you set callNext to false 
} 
+1

De hecho, me encontré con ManagedWinAPi pero los documentos no son tan auto explicativos como me gustaría, vi mi incompetencia en WinAPI. Estoy buscando en los ejemplos para obtenerlo. Por lo que he entendido, realmente no necesito un sistema global ya que un enganche directo a la ventana es suficiente. Solo necesito que se me notifique sobre los eventos de cambio de tamaño/movimiento. Idealmente, esto debería suceder en un evento C#. Lo que necesito saber es cómo conectar los puntos, por lo tanto, cualquier sugerencia es más que bienvenida, especialmente por parte de personas familiarizadas con ManagedWinApi. –

+0

Gracias! Eso es muy clarificador. Lo probaré lo antes posible. Como referencia, CWPSTRUCT está aquí http://www.pinvoke.net/default.aspx/Structures/CWPSTRUCT.html –

+0

Su código sugerido se engancha solo a ventanas dentro del proyecto C#. Necesito conectarme a una ventana externa. Buscando en Google comprendí que esto no parece posible en C#, a menos que Magadewinapi tenga una forma de hacerlo. Traté de establecer el parámetro global en el constructor de Hook en true, pero me golpeé la sesión. ¿Cualquier sugerencia? –

4

Un WH_CALLWNDPROC gancho probablemente sería suficiente, esto le permitirá controlar todos los mensajes destinados a la ventana de interés.

¿Estás preguntando cómo crear un gancho global usando C# o estás feliz de crear el gancho en C++ y luego interoperar con eso desde .NET? La segunda opción es la ruta que seguiría.

Básicamente la parte superior de la cabeza, lo que haría es la siguiente

1- Crear enlace global en C, y la exportación funciones a InstallHook y UninstallHook, que se puede llamar desde su aplicación de C# utiliza la interoperabilidad. InstallHook toma un hwnd de la ventana en su aplicación C#.

2- Haga que la función de gancho instalado publique un mensaje personalizado en la ventana C# proporcionada en la llamada al InstallHook cuando haya un mensaje que le interese, como WM_SIZE en su caso.

3- En la aplicación C# su ventana que recibe los mensajes publicados desde el gancho anulará WndProc para manejar el mensaje personalizado.

Ese es un esquema de un enfoque.

4

le sugiero que utilice WinEvents:

public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime); 

    [DllImport("user32.dll")] 
    public static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags); 

Ver también: event hooks

+0

Estoy seguro de que esto funciona. Acabo de escribir un programa que aprovecha las ventajas de SetWinEventHook. –

1

I se encontró con lo mismo en algún código con el que estaba trabajando y descubrí que no podía inyectar un .DLL administrado en el proceso.

No queriendo escribir una DLL de C++ administrado que utiliza SetWindowsHook, fui con una combinación de SetWinEventHook, lo que indica cuando una ventana se inicia y termina un evento de movimiento y una Timer para sondear la ventana mientras se está moviendo a darle la apariencia de seguir la ventana mientras se mueve.

Aquí hay una versión (muy simplificada) de una clase que puede hacer eso (simplemente reemplaza las TODO con código para mover la ventana o generar un evento).

public class DockingHelper 
{ 
    private readonly uint m_processId, m_threadId; 

    private readonly IntPtr m_target; 

    // Needed to prevent the GC from sweeping up our callback 
    private readonly WinEventDelegate m_winEventDelegate; 
    private IntPtr m_hook; 

    private Timer m_timer; 

    public DockingHelper(string windowName, string className) 
    { 
     if (windowName == null && className == null) throw new ArgumentException("Either windowName or className must have a value"); 

     m_target = FindWindow(className, windowName); 
     ThrowOnWin32Error("Failed to get target window"); 

     m_threadId = GetWindowThreadProcessId(m_target, out m_processId); 
     ThrowOnWin32Error("Failed to get process id"); 

     m_winEventDelegate = WhenWindowMoveStartsOrEnds; 
    } 

    [DllImport("user32.dll", SetLastError = true)] 
    private static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect); 

    [DllImport("user32.dll", SetLastError = true)] 
    private static extern bool UnhookWinEvent(IntPtr hWinEventHook); 

    [DllImport("user32.dll")] 
    private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags); 

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] 
    private static extern IntPtr FindWindow(string lpClassName, string lpWindowName); 

    [DllImport("user32.dll", SetLastError = true)] 
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); 

    private void ThrowOnWin32Error(string message) 
    { 
     int err = Marshal.GetLastWin32Error(); 
     if (err != 0) 
      throw new Win32Exception(err, message); 
    } 

    private RECT GetWindowLocation() 
    { 
     RECT loc; 
     GetWindowRect(m_target, out loc); 
     if (Marshal.GetLastWin32Error() != 0) 
     { 
      // Do something useful with this to handle if the target window closes, etc. 
     } 
     return loc; 
    } 

    public void Subscribe() 
    { 
     // 10 = window move start, 11 = window move end, 0 = fire out of context 
     m_hook = SetWinEventHook(10, 11, m_target, m_winEventDelegate, m_processId, m_threadId, 0); 
    } 

    private void PollWindowLocation(object state) 
    { 
     var location = GetWindowLocation(); 
     // TODO: Reposition your window with the values from location (or fire an event with it attached) 
    } 

    public void Unsubscribe() 
    { 
     UnhookWinEvent(m_hook); 
    } 

    private void WhenWindowMoveStartsOrEnds(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) 
    { 
     if (hwnd != m_target) // We only want events from our target window, not other windows owned by the thread. 
      return; 

     if (eventType == 10) // Starts 
     { 
      m_timer = new Timer(PollWindowLocation, null, 10, Timeout.Infinite); 
      // This is always the original position of the window, so we don't need to do anything, yet. 
     } 
     else if (eventType == 11) 
     { 
      m_timer.Dispose(); 
      m_timer = null; 
      var location = GetWindowLocation(); 
      // TODO: Reposition your window with the values from location (or fire an event with it attached) 
     } 
    } 

    [StructLayout(LayoutKind.Sequential)] 
    private struct RECT 
    { 
     public int Left, Top, Right, Bottom; 
    } 

    private delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime); 
} 
Cuestiones relacionadas