Hospedando aplicativo externo na janela do WPF

Estamos desenvolvendo um gerenciador de layout no WPF que possui viewports que podem ser movidos / redimensionados / etc por um usuário. As viewports são normalmente preenchidas com dados (imagens / filmes / etc) através de fornecedores que estão sob nosso controle no gerenciador de layout. Meu trabalho é examinar se também é possível hospedar qualquer aplicativo externo do Windows (ou seja, bloco de notas, calc, leitor de adobe, etc) em uma viewport. Eu me deparo com vários problemas.

A maioria dos resources aponta para o uso da class HwndHost. Eu estou experimentando com este passo a passo da própria Microsoft: http://msdn.microsoft.com/en-us/library/ms752055.aspx

Eu adaptei isso para que a checkbox de listview seja substituída pelo identificador do Windows do aplicativo externo. Alguém pode me ajudar com estas perguntas:

  1. O passo a passo adiciona uma subjanela extra estática na qual o ListBox é colocado. Eu não acho que eu preciso disso para aplicativos externos. Se eu omiti-lo, eu tenho que fazer o aplicativo externo uma janela filho (usando Get / SetWindowLong de user32.dll para definir GWL_STYLE como WS_CHILD ). Mas se eu fizer isso, a barra de menu do aplicativo desaparece (por causa do estilo WS_CHILD ) e não recebe mais input.
  2. Se eu usar a subjanela, e fazer com que o aplicativo externo seja um filho dessas coisas funcione razoavelmente, mas às vezes o aplicativo externo não pinta ok.
  3. Além disso, preciso que a janela filho seja redimensionada para a viewport. Isso é possível?
  4. Quando o aplicativo exernal gera uma janela filho (ou seja, Bloco de Notas-> Ajuda-> Sobre), essa janela não é hospedada pelo HwndHost (e, portanto, pode ser movida para fora da viewport). Existe alguma maneira de evitar isso?
  5. Como não preciso de mais interação entre o aplicativo externo e o gerenciador de layout, estou certo em assumir que não preciso capturar e encaminhar mensagens? (o passo a passo adiciona um HwndSourceHook à subjanela para capturar alterações de seleção na checkbox de listview).
  6. Quando você executa o exemplo (não modificado) VS2010 e fecha a janela, o VS2010 não vê que o programa terminou. Se você quebrar tudo, você acaba na assembly sem fonte. Algo fedorento está acontecendo, mas não consigo encontrá-lo.
  7. O passo a passo em si parece ser muito desleixado, mas não encontrei nenhuma documentação melhor sobre este assunto. Algum outro exemplo?
  8. Outra abordagem é não usar o HwndHost mas o WindowsFormHost conforme discutido aqui . Ele funciona (e é muito mais simples!), Mas eu não tenho controle sobre o tamanho do aplicativo? Além disso, o WinFormHost não é realmente destinado a isso?

Obrigado por todos os pointers na direção certa.

Bem … se a pergunta tivesse sido feita há 20 anos, alguém teria que responder: “Claro, olhe para ‘OLE’!”, Aqui está um link para o que é “Object Linking and Embedding”:

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

Se você ler este artigo, verá o número de interfaces que essa especificação definiu, não porque seu autor achou divertido, mas porque é tecnicamente difícil de alcançar nos casos gerais

Na verdade, ainda é suportado por alguns aplicativos (principalmente Microsoft, como a Microsoft era quase o único patrocinador de OLE …)

Você pode incorporar esses aplicativos usando algo chamado DSOFramer (ver links aqui no SO: MS KB311765 e DsoFramer estão faltando no site MS ), um componente que permite que você hospede o servidor OLE (ou seja, aplicativos externos em execução como outro processo) visualmente dentro de um aplicativo . É uma grande invasão que a Microsoft divulgou há alguns anos, que não é mais suportada a ponto de os binários serem muito difíceis de encontrar!

Ele (maio) ainda funciona para servidores OLE simples, mas acho que li em algum lugar que nem sequer funciona para novos aplicativos da Microsoft, como o Word 2010. Assim, você pode usar o DSOFramer para aplicativos que o suportam. Você pode experimentá-lo.

Para outros aplicativos, bem, hoje em dia, no mundo moderno em que vivemos, você não hospeda aplicativos , roda em processos externos, hospeda componentes e, em geral, supõe-se que executem o processo . É por isso que você terá grandes dificuldades para fazer o que deseja em geral . Um problema que você enfrentará (e não menos importante com versões recentes do Windows) é a segurança: como o seu processo em que não confio pode lidar legitimamente com minhas janelas e menus criados pelo meu processo :-)?

Ainda assim, você pode fazer bastante aplicação por aplicativo, usando vários hack do Windows. SetParent é basicamente a mãe de todos os hacks 🙂

Aqui está um trecho de código que estende a amostra que você aponta, adicionando o redimensionamento automático e a remoção da checkbox de legenda. Ele demonstra como remover implicitamente a checkbox de controle, o menu do sistema, como um exemplo:

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

Isso é basicamente todos os hacks “tradicionais” do Windows. Você também pode remover menus de item que não gosta, como explicado aqui: http://support.microsoft.com/kb/110393/en-us (Como remover itens de menu da checkbox de menu de controle de um formulário).

Você também pode replace “notepad.exe” por “winword.exe” e parece funcionar. Mas existem limitações para isso (teclado, mouse, foco, etc.).

Boa sorte!

Depois de ler as respostas neste tópico e fazer uma tentativa e erro eu acabei com algo que funciona muito bem, mas é claro que algumas coisas vão precisar de sua atenção para casos especiais.

Eu usei o HwndHostEx como class base para minha class de host, você pode encontrá-lo aqui: http://microsoftdwayneneed.codeplex.com/SourceControl/changeset/view/69631#1034035

Exemplo de código:

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

O HWND, NativeMethods e enums também vêm da biblioteca DwayneNeed (Microsoft.DwayneNeed.User32).

Basta adicionar o NotepadHwndHost como um filho em uma janela do WPF e você deverá ver a janela do bloco de notas hospedada lá.

A resposta de Simon Mourier é extremamente bem escrita. No entanto, quando eu tentei com um aplicativo winform feito por mim, ele falhou.

 _process.WaitForInputIdle(); 

pode ser substituído por

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

e tudo corre bem.

Obrigado pela ótima pergunta e todos vocês por suas respostas.

A solução está incrivelmente envolvida. Muito código Aqui estão algumas dicas.

Primeiro, você está no caminho certo.

Você tem que usar a coisa HwndHost e HwndSource. Se você não fizer isso, você obterá artefatos visuais. Como flicker. Um aviso, se você não usar o Host e o Source, parecerá que vai funcionar, mas não vai acabar – ele terá bugs randoms e estúpidos.

Dê uma olhada nisso por algumas dicas. Não está completo, mas ajudará você a ir na direção certa. http://microsoftdwayneneed.codeplex.com/SourceControl/changeset/view/50925#1029346

Você precisa entrar no Win32 para controlar muito do que está perguntando. Você precisa capturar e encaminhar mensagens. Você precisa controlar quais janelas “possuem” as janelas filho.

Use o Spy ++ muito.

Eu tenho isso em execução na produção e até aí tudo bem em um aplicativo WPF. Certifique-se de chamar SetNativeWindowInWPFWindowAsChild() do thread de interface do usuário que possui 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; } 

Aqui está a API nativa do Win32 que usei. (Há extras aqui porque eu tamanho / foco a janela depois de definir)

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

Confira minha resposta para: Como executar um aplicativo dentro do aplicativo wpf?

Consegui fazer com que o exemplo do bloco de notas funcionasse sem o jiggery DwayneNeed. Acabei de adicionar SetParent () e boom … ela funciona exatamente como o exemplo DwayneNeed.