Хостинг внешнего приложения в окне WPF

Мы разрабатываем диспетчер компоновки в WPF, у которого есть видовые экраны, которые пользователь может перемещать / изменять размер / etc. Видовые экраны обычно заполняются данными (изображениями / фильмами и т. Д.) Через поставщиков, которые находятся под нашим контролем в менеджере компоновки. Моя задача – проверить, можно ли также разместить в окне просмотра любое внешнее приложение Windows (например, блокнот, calc, adobe reader и т. Д.). Я столкнулся с рядом проблем.

Большинство ресурсов указывают на использование classа HwndHost. Я экспериментирую с этим пошаговым руководством самой Microsoft: http://msdn.microsoft.com/en-us/library/ms752055.aspx

Я адаптировал это, чтобы окно списка было заменено дескриптором окон из внешнего приложения. Может кто-нибудь помочь мне с этими вопросами:

  1. В пошаговом руководстве добавлено дополнительное статическое вспомогательное окно, в которое помещается ListBox . Я не думаю, что это нужно для внешних приложений. Если я опустить это, я должен сделать внешнее приложение дочерним окном (используя Get / SetWindowLong из user32.dll, чтобы установить GWL_STYLE как WS_CHILD ). Но если я это сделаю, панель меню приложения исчезнет (из-за стиля WS_CHILD ), и он больше не получает вход.
  2. Если я использую вспомогательное окно и сделаю внешнее приложение дочерним, то это работает разумно, но иногда внешнее приложение не рисует нормально.
  3. Кроме того, мне нужно, чтобы дочернее окно изменялось в область просмотра. Это возможно?
  4. Когда приложение exernal запускает дочернее окно (например, «Блокнот-> Справка-> О программе»), это окно не размещается в HwndHost (и, следовательно, его можно перемещать за пределы области просмотра). Есть ли способ предотвратить это?
  5. Поскольку мне не нужно больше взаимодействовать между внешним приложением и менеджером макета, правильно ли я предполагаю, что мне не нужно ловить и пересылать сообщения? (пошаговое руководство добавляет HwndSourceHook в вспомогательное окно, чтобы уловить изменения выбора в списке).
  6. Когда вы запускаете (немодифицированный) пример VS2010 и закрываете окно, VS2010 не видит, что программа закончилась. Если вы сломаете все, вы попадете в сборку без источника. Что-то вонючее происходит, но я не могу его найти.
  7. Пошаговое руководство кажется очень неаккуратным, но я не нашел лучшей документации по этому вопросу. Любые другие примеры?
  8. Другой подход заключается не в использовании HwndHost а в WindowsFormHost как обсуждалось здесь . Он работает (и намного проще!), Но у меня нет контроля над размером приложения? Кроме того, WinFormHost на самом деле не предназначен для этого?

Спасибо за любые указатели в правильном направлении.

Ну … если бы вопрос был задан, как и 20 лет назад, можно было бы ответить: «Конечно, посмотрите на« OLE »!», Вот ссылка на «Связывание и вложение объектов»:

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

Если вы прочитаете эту статью, вы увидите количество интерфейсов, определенных этой спецификацией, а не потому, что ее автор считал это забавой, а потому, что технически трудно добиться в общих случаях

Он по-прежнему поддерживается некоторыми приложениями (в основном, Microsoft, поскольку Microsoft была почти единственным спонсором OLE …)

Вы можете встраивать эти приложения с помощью DSOFramer (см. Ссылки здесь на SO: MS KB311765 и DsoFramer отсутствуют на сайте MS ), компонент, который позволяет вам размещать OLE-сервер (т. Е. Внешние приложения, выполняемые как другой процесс) визуально внутри приложения , Это какой-то большой взлом Microsoft, выпущенный несколько лет назад, который не поддерживается до такой степени, что двоичные файлы довольно сложно найти!

Он (возможно) все еще работает для простых OLE-серверов, но я думаю, что я читал где-то, что он даже не работает для новых приложений Microsoft, таких как Word 2010. Таким образом, вы можете использовать DSOFramer для приложений, которые его поддерживают. Можешь попробовать.

Для других приложений, ну, сегодня, в современном мире, в котором мы живем, вы не размещаете приложения , не работаете во внешнем процессе, а размещаете компоненты , и они, как правило, должны работать inprocess . Вот почему у вас будут большие трудности, чтобы делать то, что вы хотите делать в целом . Одной из проблем, с которой вы столкнетесь (и не в последнюю очередь с последними версиями Windows), является безопасность: как ваш процесс, которому я не доверяю, может законно обрабатывать мои windows и меню, созданные моим процессом :-)?

Тем не менее, вы можете делать довольно много приложений по приложению, используя различные взломы Windows. SetParent в основном является матерью всех хаков 🙂

Вот fragment кода, который расширяет образец, который вы указываете, добавляя автоматическое изменение размера и удаление поля надписи. Он демонстрирует, как неявно удалить блок управления, системное меню, в качестве примера:

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

Это в основном все «традиционные» хаки Windows. Вы также можете удалить пункты меню, которые вам не нравятся, как описано здесь: http://support.microsoft.com/kb/110393/en-us (Как удалить элементы меню из windows меню управления формой).

Вы также можете заменить «notepad.exe» на «winword.exe», и, похоже, он работает. Но есть ограничения на это (клавиатура, мышь, фокус и т. Д.).

Удачи!

Прочитав ответы в этом streamе и сделав некоторые пробные ошибки и ошибки, я оказался в чем-то, что работает очень хорошо, но, конечно, некоторые вещи понадобятся вам для особых случаев.

Я использовал HwndHostEx в качестве базового classа для моего classа хоста, вы можете найти его здесь: http://microsoftdwayneneed.codeplex.com/SourceControl/changeset/view/69631#1034035

Пример кода:

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

HWND, NativeMethods и enums поступают из библиотеки DwayneNeed (Microsoft.DwayneNeed.User32).

Просто добавьте NotepadHwndHost в качестве дочернего элемента в окне WPF, и вы должны увидеть там окно блокнота.

Ответ Симона Моурира очень хорошо написан. Однако, когда я попробовал это с помощью приложения winform, сделанного мною, он потерпел неудачу.

 _process.WaitForInputIdle(); 

можно заменить на

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

и все идет гладко.

Благодарим вас за большой вопрос и всех вас за ваши ответы.

Решение невероятно связано. Много кода. Вот несколько советов.

Во-первых, вы на правильном пути.

Вам нужно использовать вещи HwndHost и HwndSource. Если вы этого не сделаете, вы получите визуальные артефакты. Как мерцание. Предупреждение, если вы не используете Host и Source, похоже, что это сработает, но в конце концов это не будет – у него будут случайные маленькие глупые ошибки.

Взгляните на это для некоторых советов. Это не полно, но это поможет вам идти в правильном направлении. http://microsoftdwayneneed.codeplex.com/SourceControl/changeset/view/50925#1029346

Вам нужно попасть в Win32, чтобы контролировать многое из того, о чем вы просите. Вам нужно поймать и переслать сообщения. Вам нужно контролировать, какие windows «владеют» дочерними windowsми.

Используйте Spy ++ alot.

У меня это работает в производстве и до сих пор так хорошо в приложении WPF. Убедитесь, что вы вызываете SetNativeWindowInWPFWindowAsChild() из streamа пользовательского интерфейса, которому принадлежит 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; } 

Вот собственный API Win32, который я использовал. (Есть дополнительные функции здесь, потому что я размер / фокус windows после его установки)

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

Проверьте мой ответ на: Как запустить приложение в приложении wpf?

Мне удалось запустить пример блокнота без DwayneNeed jiggery. Я просто добавил SetParent () и стрелу … она работает так же, как пример DwayneNeed.

  • Как я могу привязать данные строк к ListBox в WPF / WP7?
  • Как инвертировать BooleanToVisibilityConverter?
  • Как отображать элементы в Canvas через Binding
  • Как изменить источник изображения динамически из кода в WPF с изображением в свойствах. Ресурсы?
  • WPF - Как создать меню и подменю, используя привязку
  • В каком порядке панели наиболее эффективны с точки зрения времени и производительности?
  • Значение * (звездочка) в WPF ColumnDefinition?
  • Как вы убедитесь, что WPF выпускает большой битмап-источник из памяти?
  • Что означает «Захват мыши» в WPF?
  • Пользовательская реализация MVVM Vs. PRISM
  • Установите позицию курсора / курсора в конец текстового поля WPF с строковым значением
  • Давайте будем гением компьютера.