Подтвердить что ты не робот

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

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

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

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

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

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

4b9b3361

Ответ 1

Ну... если бы вопрос был задан, как 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. 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 (Как удалить элементы меню из элемента Form Control- Меню).

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

Удачи!

Ответ 2

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

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

Ответ 3

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

_process.WaitForInputIdle();

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

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

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

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

Ответ 4

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

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

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

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

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

Использовать Spy ++ alot.

Ответ 5

У меня это работает в производстве и до сих пор так хорошо в приложении 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;
    }

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

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