2011-02-17 19 views
35

Estamos desarrollando un administrador de diseño en WPF que tiene ventanas gráficas que un usuario puede mover/cambiar de tamaño/etc. Las ventanas gráficas normalmente están llenas de datos (imágenes/películas/etc.) a través de proveedores que están bajo nuestro control en el administrador de diseño. Mi trabajo es examinar si también es posible alojar cualquier aplicación externa de Windows (por ejemplo, bloc de notas, calc, adobe reader, etc.) en una ventana gráfica. Me encuentro con una serie de problemas.Aplicación externa de alojamiento en la ventana de WPF

La mayoría de los recursos apuntan a utilizar la clase HwndHost. Estoy experimentando con este tutorial de la propia Microsoft: http://msdn.microsoft.com/en-us/library/ms752055.aspx

He adaptado esto para que el cuadro de lista se reemplace con el controlador de Windows desde la aplicación externa. Alguien me puede ayudar con estas preguntas:

  1. El tutorial añade una ventana sub estática adicional en el que se coloca el ListBox. No creo que necesite eso para aplicaciones externas. Si lo hago, tengo que hacer que la aplicación externa sea una ventana secundaria (usando Get/SetWindowLong de user32.dll para establecer GWL_STYLE como WS_CHILD). Pero si hago eso, la barra de menú de la aplicación desaparece (debido al estilo WS_CHILD) y ya no recibe ninguna entrada.
  2. Si utilizo la ventana secundaria y hago que la aplicación externa sea hija de que todo funciona de manera razonable, pero a veces la aplicación externa no se ve bien.
  3. Además, necesito la ventana secundaria para cambiar el tamaño de la ventana gráfica. es posible?
  4. Cuando la aplicación exernal genera una ventana secundaria (es decir, Bloc de notas-> Ayuda-> Acerca de), esta ventana no está alojada en el HwndHost (y, por lo tanto, se puede mover fuera de la ventana gráfica). ¿Hay alguna manera de prevenir eso?
  5. Como no necesito más interacción entre la aplicación externa y el administrador de diseño, ¿estoy en lo cierto al asumir que no necesito capturar y reenviar mensajes? (el tutorial agrega un HwndSourceHook a la ventana secundaria para detectar cambios de selección en el cuadro de lista).
  6. Cuando ejecuta el ejemplo (no modificado) VS2010 y cierra la ventana, VS2010 no ve que el programa finalizó. Si te rompes todo, terminas ensamblado sin fuente. Algo mal está sucediendo, pero no puedo encontrarlo.
  7. El tutorial en sí parece tener un código muy descuidado, pero no he encontrado ninguna documentación mejor sobre este tema. ¿Algún otro ejemplo?
  8. Otro enfoque es no usar HwndHost sino WindowsFormHost como se describe en here. Funciona (¡y es mucho más simple!) Pero no tengo control sobre el tamaño de la aplicación. Además, WinFormHost no es realmente para esto?

Gracias por cualquier apunta en la dirección correcta.

+0

Hola, cabe duda de que para el punto 8. –

Respuesta

22

Bueno ... si la pregunta se hubiera planteado hace 20 años, uno debería responder: "¡Claro, mira 'OLE'!", Aquí hay un enlace a lo que es "Vinculación e incrustación de objetos":

http://en.wikipedia.org/wiki/Object_Linking_and_Embedding

Si usted lee este artículo, verá el número de interfaces de esta especificación se define, no porque su autor pensó que era divertido, sino porque es técnicamente difícil de lograr en los casos generales

En realidad todavía es compatible con algunas aplicaciones (principalmente las de Microsoft, como Microsoft fue casi el único patrocinador de OLE ...)

puede incrustar estas aplicaciones usando algo llamado DSOFramer (ver los enlaces aquí en SO: MS KB311765 and DsoFramer are missing from MS site), un componente que le permite alojar servidor OLE (es decir: las aplicaciones externas que funcionan como otro proceso) visualmente dentro de una aplicación. Es una especie de gran truco que Microsoft dejó escapar hace unos años, que ya no es compatible hasta el punto de que los binarios son bastante difíciles de encontrar.

Puede (todavía) funcionar para servidores OLE simples, pero creo que leí en alguna parte que ni siquiera funciona para nuevas aplicaciones de Microsoft como Word 2010. Entonces, puede usar DSOFramer para aplicaciones que lo soportan. Puedes probarlo.

Para otros usos, así, en la actualidad, en el mundo moderno en que vivimos, que no alojan aplicaciones , corrieron en proceso externo, aloja componentes, y que son, en general, supone para funcionar En proc. Es por eso que tendrá grandes dificultades para hacer lo que quiera hacer en general. Uno de los problemas que tendrá que enfrentar (y no menos con las versiones más recientes de Windows) es la seguridad: ¿cómo puede su proceso no confiar legítimamente puede manejar mi ventanas y menús creados por mi proceso :-)?

Aún así, puede hacer bastante aplicación por aplicación, utilizando varios hack de Windows. SetParent es básicamente la madre de todos los hacks :-)

Aquí hay un fragmento de código que amplía la muestra que señala, agrega el cambio de tamaño automático y la eliminación del cuadro de subtítulos. Demuestra cómo eliminar implícitamente la caja de control, el menú del sistema, como un ejemplo:

public partial class Window1 : Window 
{ 
    private System.Windows.Forms.Panel _panel; 
    private Process _process; 

    public Window1() 
    { 
     InitializeComponent(); 
     _panel = new System.Windows.Forms.Panel(); 
     windowsFormsHost1.Child = _panel; 
    } 

    [DllImport("user32.dll")] 
    private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); 

    [DllImport("user32.dll", SetLastError = true)] 
    private static extern int GetWindowLong(IntPtr hWnd, int nIndex); 

    [DllImport("user32")] 
    private static extern IntPtr SetParent(IntPtr hWnd, IntPtr hWndParent); 

    [DllImport("user32")] 
    private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); 

    private const int SWP_NOZORDER = 0x0004; 
    private const int SWP_NOACTIVATE = 0x0010; 
    private const int GWL_STYLE = -16; 
    private const int WS_CAPTION = 0x00C00000; 
    private const int WS_THICKFRAME = 0x00040000; 

    private void button1_Click(object sender, RoutedEventArgs e) 
    { 
     button1.Visibility = Visibility.Hidden; 
     ProcessStartInfo psi = new ProcessStartInfo("notepad.exe"); 
     _process = Process.Start(psi); 
     _process.WaitForInputIdle(); 
     SetParent(_process.MainWindowHandle, _panel.Handle); 

     // remove control box 
     int style = GetWindowLong(_process.MainWindowHandle, GWL_STYLE); 
     style = style & ~WS_CAPTION & ~WS_THICKFRAME; 
     SetWindowLong(_process.MainWindowHandle, GWL_STYLE, style); 

     // resize embedded application & refresh 
     ResizeEmbeddedApp(); 
    } 

    protected override void OnClosing(System.ComponentModel.CancelEventArgs e) 
    { 
     base.OnClosing(e); 
     if (_process != null) 
     { 
      _process.Refresh(); 
      _process.Close(); 
     } 
    } 

    private void ResizeEmbeddedApp() 
    { 
     if (_process == null) 
      return; 

     SetWindowPos(_process.MainWindowHandle, IntPtr.Zero, 0, 0, (int)_panel.ClientSize.Width, (int)_panel.ClientSize.Height, SWP_NOZORDER | SWP_NOACTIVATE); 
    } 

    protected override Size MeasureOverride(Size availableSize) 
    { 
     Size size = base.MeasureOverride(availableSize); 
     ResizeEmbeddedApp(); 
     return size; 
    } 
} 

Esto es, básicamente, todos los cortes de Windows "tradicionales". También puede eliminar los menús de elementos que no le gustan, como se explica aquí: http://support.microsoft.com/kb/110393/en-us (Cómo quitar elementos de menú del cuadro de menú de control de un formulario).

También puede reemplazar "notepad.exe" por "winword.exe" y parece para que funcione. Pero hay limitaciones para esto (teclado, mouse, enfoque, etc.).

¡Buena suerte!

+3

espera, llamadas de API de Windows se consideran "hacks" para los programadores .NET? o_O –

+1

@ TamásSzelei - La API es compatible, pero se supone que no debes jugar con las aplicaciones de otros, como cambiar su título, fotograma y relación principal. Podría bloquear la aplicación de destino. –

+0

Bastante justo. Por cierto, MainWindowHandle a menudo no funciona (con los últimos IE, Chrome, Firefox, por ejemplo). Un enfoque un poco más confiable es utilizar FindWindow con la clase de ventana para obtener el hwnd. Aparte de esto, ¿conoces una manera de mantener el foco de la ventana incluso cuando se hace clic en la aplicación externa? –

1

La solución es increíblemente complicada. Montones de código Aquí hay algunos consejos.

Primero, está en el camino correcto.

Tienes que usar HwndHost y HwndSource. Si no lo haces, obtendrás artefactos visuales. Como parpadeo. Una advertencia, si no usas el Host y la Fuente, parecerá que funcionará, pero al final no tendrá errores pequeños y estúpidos.

Eche un vistazo a esto para obtener algunos consejos. No está completo, pero te ayudará a ir en la dirección correcta. http://microsoftdwayneneed.codeplex.com/SourceControl/changeset/view/50925#1029346

Tienes que entrar en Win32 para controlar mucho de lo que estás preguntando. Necesita capturar y reenviar mensajes. Debes controlar qué ventanas son "propias" de las ventanas secundarias.

Utilice Spy ++ mucho.

4

Después de leer las respuestas en este hilo y hacer un poco de prueba y error, terminé con algo que funciona bastante bien, pero por supuesto algunas cosas necesitarán su atención para casos especiales.

he utilizado la HwndHostEx como clase base para mi clase de host, puede encontrar aquí: Código http://microsoftdwayneneed.codeplex.com/SourceControl/changeset/view/69631#1034035

Ejemplo:

public class NotepadHwndHost : HwndHostEx 
{ 
    private Process _process; 

    protected override HWND BuildWindowOverride(HWND hwndParent) 
    { 
     ProcessStartInfo psi = new ProcessStartInfo("notepad.exe"); 
     _process = Process.Start(psi); 
     _process.WaitForInputIdle(); 

     // The main window handle may be unavailable for a while, just wait for it 
     while (_process.MainWindowHandle == IntPtr.Zero) 
     { 
      Thread.Yield(); 
     } 

     HWND hwnd = new HWND(_process.MainWindowHandle); 

     int style = NativeMethods.GetWindowLong(hwnd, GWL.STYLE); 

     style = style & ~((int)WS.CAPTION) & ~((int)WS.THICKFRAME); // Removes Caption bar and the sizing border 
     style |= ((int)WS.CHILD); // Must be a child window to be hosted 

     NativeMethods.SetWindowLong(hwnd, GWL.STYLE, style); 

     return hwnd; 
    } 

    protected override void DestroyWindowOverride(HWND hwnd) 
    { 
     _process.CloseMainWindow(); 

     _process.WaitForExit(5000); 

     if (_process.HasExited == false) 
     { 
      _process.Kill(); 
     } 

     _process.Close(); 
     _process.Dispose(); 
     _process = null; 
     hwnd.Dispose(); 
     hwnd = null; 
    } 
} 

La HWND, NativeMethods y enumeraciones proviene de la biblioteca DwayneNeed así (Microsoft.DwayneNeed.User32).

Simplemente agregue el NotepadHwndHost como un niño en una ventana de WPF y debería ver la ventana del bloc de notas alojada allí.

4

La respuesta de Simon Mourier está muy bien escrita. Sin embargo, cuando lo probé con una aplicación winform hecha por mí, falló.

_process.WaitForInputIdle(); 

pueden ser reemplazados por

while (_process.MainWindowHandle==IntPtr.Zero) 
      { 
       Thread.Sleep(1); 
      } 

y todo va bien.

Gracias por la gran pregunta y a todos ustedes por sus respuestas.

1

Tengo esto funcionando en producción y hasta ahora muy bien en una aplicación WPF. Asegúrese de llamar al SetNativeWindowInWPFWindowAsChild() desde la interfaz de usuario que posee window.

public static bool SetNativeWindowInWPFWindowAsChild(IntPtr hWndNative, Window window) 
    { 
     UInt32 dwSyleToRemove = WS_POPUP | WS_CAPTION | WS_THICKFRAME; 
     UInt32 dwExStyleToRemove = WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE; 

     UInt32 dwStyle = GetWindowLong(hWndNative, GWL_STYLE); 
     UInt32 dwExStyle = GetWindowLong(hWndNative, GWL_EXSTYLE); 

     dwStyle &= ~dwSyleToRemove; 
     dwExStyle &= ~dwExStyleToRemove; 

     SetWindowLong(hWndNative, GWL_STYLE, dwStyle | WS_CHILD); 
     SetWindowLong(hWndNative, GWL_EXSTYLE, dwExStyle); 

     IntPtr hWndOld = SetParent(hWndNative, new WindowInteropHelper(window).Handle); 
     if (hWndOld == IntPtr.Zero) 
     { 
      System.Diagnostics.Debug.WriteLine("SetParent() Failed -> LAST ERROR: " + Marshal.GetLastWin32Error() + "\n"); 
     } 
     return hWndOld != IntPtr.Zero; 
    } 

Aquí está la API nativa Win32 que utilicé. (Hay extras en aquí porque Tamaño/enfocar la ventana después de que se establece)

 [StructLayout(LayoutKind.Sequential)] 
     private struct RECT 
     { 
      public Int32 left; 
      public Int32 top; 
      public Int32 right; 
      public Int32 bottom; 
     } 
     [DllImport("user32.dll", SetLastError = true)] 
     private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); 
     [DllImport("user32.dll")] 
     private static extern UInt32 SetWindowLong(IntPtr hWnd, int nIndex, UInt32 dwNewLong); 
     [DllImport("user32.dll")] 
     private static extern UInt32 GetWindowLong(IntPtr hWnd, int nIndex); 
     [DllImport("user32.dll")] 
     private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); 
     [DllImport("user32.dll")] 
     private static extern IntPtr SetFocus(IntPtr hWnd); 
     [DllImport("user32.dll")] 
     private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags); 

     private static int GWL_STYLE = -16; 
     private static int GWL_EXSTYLE = -20; 

     private static UInt32 WS_CHILD = 0x40000000; 
     private static UInt32 WS_POPUP = 0x80000000; 
     private static UInt32 WS_CAPTION = 0x00C00000; 
     private static UInt32 WS_THICKFRAME = 0x00040000; 

     private static UInt32 WS_EX_DLGMODALFRAME = 0x00000001; 
     private static UInt32 WS_EX_WINDOWEDGE = 0x00000100; 
     private static UInt32 WS_EX_CLIENTEDGE = 0x00000200; 
     private static UInt32 WS_EX_STATICEDGE = 0x00020000; 

     [Flags] 
     private enum SetWindowPosFlags : uint 
     { 
      SWP_ASYNCWINDOWPOS = 0x4000, 
      SWP_DEFERERASE = 0x2000, 
      SWP_DRAWFRAME = 0x0020, 
      SWP_FRAMECHANGED = 0x0020, 
      SWP_HIDEWINDOW = 0x0080, 
      SWP_NOACTIVATE = 0x0010, 
      SWP_NOCOPYBITS = 0x0100, 
      SWP_NOMOVE = 0x0002, 
      SWP_NOOWNERZORDER = 0x0200, 
      SWP_NOREDRAW = 0x0008, 
      SWP_NOREPOSITION = 0x0200, 
      SWP_NOSENDCHANGING = 0x0400, 
      SWP_NOSIZE = 0x0001, 
      SWP_NOZORDER = 0x0004, 
      SWP_SHOWWINDOW = 0x0040 
     } 
     private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); 
     private static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2); 
     private static readonly IntPtr HWND_TOP = new IntPtr(0); 
     private static readonly IntPtr HWND_BOTTOM = new IntPtr(1); 
Cuestiones relacionadas