Размещение внешнего приложения в окне WPF - PullRequest
38 голосов
/ 17 февраля 2011

Мы разрабатываем менеджер разметки в WPF, который имеет видовые окна, которые могут быть перемещены / изменены по размеру / и т.д. пользователем. Окна просмотра обычно заполняются данными (изображения / фильмы / и т. Д.) Через провайдеров, которые находятся под нашим контролем в менеджере макетов. Моя работа состоит в том, чтобы проверить, возможно ли также разместить какое-либо внешнее приложение Windows (например, блокнот, калькулятор, Adobe Reader и т. Д.) В окне просмотра. Я столкнулся с рядом проблем.

Большинство ресурсов указывают на использование класса 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. Когда внешнее приложение порождает дочернее окно (то есть Блокнот-> Справка-> О программе), это окно не размещается на HwndHost (и, следовательно, может быть перемещено за пределы области просмотра). Есть ли способ, которым я могу предотвратить это?
  5. Поскольку мне больше не нужно взаимодействие между внешним приложением и менеджером макета, я прав, предполагая, что мне не нужно перехватывать и пересылать сообщения? (пошаговое руководство добавляет HwndSourceHook в подокно, чтобы отследить изменения выбора в списке).
  6. Когда вы запускаете (неизмененный) пример VS2010 и закрываете окно, VS2010 не видит, что программа завершилась. Если вы разбиваете все, вы в конечном итоге в сборке без источника. Что-то вонючее происходит, но я не могу найти это.
  7. Само пошаговое руководство кажется очень неаккуратным, но я не нашел лучшей документации по этому вопросу. Любые другие примеры?
  8. Другой подход заключается не в использовании HwndHost, а WindowsFormHost, как обсуждено здесь . Это работает (и намного проще!), Но у меня нет контроля над размером приложения? Кроме того, WinFormHost не предназначен для этого?

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

Ответы [ 6 ]

25 голосов
/ 21 февраля 2011

Ну ... если бы вопрос был задан как 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 для приложения, которое его поддерживает.Вы можете попробовать.

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

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

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

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 (Как удалить элементы меню из поля меню управления формы).

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

Удачи!

5 голосов
/ 28 августа 2014

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

_process.WaitForInputIdle();

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

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

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

Спасибо за отличный вопрос и всем вам за ваши ответы.

4 голосов
/ 14 ноября 2012

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

Я использовал HwndHostEx в качестве базового класса для своего хост-класса, вы можете найти его здесь: 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 и перечисления также поступают из библиотеки DwayneNeed (Microsoft.DwayneNeed.User32).

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

1 голос
/ 13 февраля 2017

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

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

        [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);
1 голос
/ 25 февраля 2011

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

Сначала вы на правильном пути.

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

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

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

Используйте Spy ++ много.

0 голосов
/ 05 декабря 2012

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

Мне удалось получить пример с блокнотом, работающий без использования DwayneNeed. Я просто добавил SetParent () и boom ... она работает так же, как пример DwayneNeed.

...