web-dev-qa-db-ja.com

WPFウィンドウで外部アプリをホストする

ユーザーが移動/サイズ変更などを行えるビューポートを持つWPFのレイアウトマネージャーを開発しています。通常、ビューポートには、レイアウトマネージャーで管理されているプロバイダーを介してデータ(画像/映画/など)が入力されます。私の仕事は、外部のWindowsアプリ(メモ帳、電卓、Adobe Readerなど)をビューポートでホストできるかどうかを調べることです。多くの問題に遭遇しました。

ほとんどのリソースは、HwndHostクラスの使用を指しています。私はマイクロソフト自身からのこのチュートリアルで実験しています: http://msdn.Microsoft.com/en-us/library/ms752055.aspx

リストボックスが外部アプリケーションからのウィンドウハンドルに置き換えられるように、これを調整しました。誰でもこれらの質問で私を助けることができます:

  1. このチュートリアルでは、ListBoxが配置される静的なサブウィンドウを追加します。外部アプリには必要ないと思います。省略すると、外部アプリを子ウィンドウにする必要があります(user32.dllのGet/SetWindowLongを使用してGWL_STYLE なので WS_CHILD)。しかし、それを行うと、アプリのメニューバーが消えます(WS_CHILD style)そして、それはもはや入力を受け取りません。
  2. 私がサブウィンドウを使用し、外部アプリをその子の合理的なものにすると、外部アプリはうまくペイントされないことがあります。
  3. また、ビューポートに合わせてサイズを変更するには子ウィンドウが必要です。これは可能ですか?
  4. 外部アプリが子ウィンドウを生成するとき(つまり、メモ帳->ヘルプ->バージョン情報)、このウィンドウはHwndHostによってホストされません(したがって、ビューポート外に移動できます)。それを防ぐ方法はありますか?
  5. 外部アプリケーションとレイアウトマネージャーとの間でこれ以上対話する必要はないので、メッセージをキャッチして転送する必要はないと想定してもいいですか? (このチュートリアルでは、サブウィンドウにHwndSourceHookを追加して、リストボックスの選択の変更をキャッチします)。
  6. (変更されていない)VS2010の例を実行してウィンドウを閉じると、VS2010はプログラムが終了したことを確認しません。すべてを破ると、ソースなしのアセンブリになります。何か臭いが起こっていますが、見つけられません。
  7. ウォークスルー自体は非常にずさんなコードになっているようですが、このテーマに関するこれ以上のドキュメントは見つかりません。他の例はありますか?
  8. 別のアプローチは、HwndHostではなくWindowsFormHostを使用することです here で説明します。それは動作します(そしてもっと簡単です!)が、アプリケーションのサイズを制御することはできませんか?また、WinFormHostは本当にこれを目的としていませんか?

正しい方向へのポインタをありがとう。

38
Stiggy

ええと... 20年前のように質問が投げかけられた場合、「確かに、 'OLE'を見てください!」という答えがあります。ここに、「オブジェクトのリンクと埋め込み」へのリンクがあります。

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

この記事を読むと、作成者が楽しいと思ったからではなく、技術的に達成するのが技術的に難しいために、この仕様が定義したインターフェースの数を見ることができます一般的な場合

実際には、一部のアプリ(MicrosoftがOLEのほとんど唯一のスポンサーであるため、ほとんどがMicrosoftのアプリ...)によってまだサポートされています。

これらのアプリは、DSOFramerと呼ばれるものを使用して埋め込むことができます(SOのリンクを参照してください: MS KB311765およびDsoFramerはMSサイトにありません )。ホストOLE =サーバー(つまり、別のプロセスとして実行されている外部アプリ)は、アプリケーション内で視覚的に表示されます。Microsoftが数年前に発表した大きなハックのようなものです。

それは(おそらく)シンプルなOLEサーバーで動作しますが、Word 2010などの新しいMicrosoftアプリケーションでも動作しない場所を読んだと思います。したがって、サポートするアプリケーションにDSOFramerを使用できます試してみてください。

他のアプリケーションについては、今日、私たちが住んでいる現代の世界では、ホストapplicationsではなく、外部プロセスで実行され、ホストcomponentsであり、 inprocessを実行することになっている一般的な。それが、あなたがやりたいことをするのが非常に困難になる理由です一般。直面する問題の1つは(少なくとも最新バージョンのWindowsでは)セキュリティです。どのようにしてyour信頼できないプロセスが合法的に処理できるかmy作成されたウィンドウとメニュー私のプロセスによって:-)?

それでも、さまざまな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 (フォームのコントロールからメニュー項目を削除する方法-メニューボックス)。

「notepad.exe」を「winword.exe」に置き換えることもできますseems動作します。ただし、これには制限があります(キーボード、マウス、フォーカスなど)。

幸運を!

25
Simon Mourier

Simon Mourierの答えは非常によく書かれています。ただし、自分で作成したwinformアプリで試してみたところ、失敗しました。

_process.WaitForInputIdle();

に置き換えることができます

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

すべてがスムーズに進みます。

すばらしい質問と回答をありがとうございました。

5
PauLEffect

このスレッドの回答を読んで、自分で試行錯誤を繰り返した結果、かなりうまくいくものになりましたが、もちろん、特別な場合には注意が必要なことがあります。

Hostクラスの基本クラスとして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ウィンドウの子として追加すると、そこにホストされているメモ帳ウィンドウが表示されます。

4
TGasdf

私はこれを実稼働環境で実行しており、これまでのところWPFアプリケーションでうまく機能しています。 windowを所有するUIスレッドからSetNativeWindowInWPFWindowAsChild()を必ず呼び出してください。

    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
Andy

ソリューションは非常に複雑です。たくさんのコード。ここにいくつかのヒントがあります。

まず、あなたは正しい軌道に乗っています。

HwndHostとHwndSourceを使用する必要があります。そうしないと、視覚的なアーティファクトが得られます。ちらつきのように。警告。ホストとソースを使用しない場合、動作するように見えますが、最終的には動作しません。ランダムで小さな愚かなバグが発生します。

いくつかのヒントについては、これをご覧ください。完全ではありませんが、正しい方向に進むのに役立ちます。 http://microsoftdwayneneed.codeplex.com/SourceControl/changeset/view/50925#1029346

Win32にアクセスして、求めていることの多くを制御する必要があります。メッセージをキャッチして転送する必要があります。どのウィンドウが子ウィンドウを「所有する」かを制御する必要があります。

Spy ++ alotを使用します。

1
010110110101

私の答えを確認してください: wpfアプリケーション内でアプリケーションを実行する方法?

DwayneNeedジガリーなしでメモ帳の例を機能させることができました。 SetParent()とboomを追加しました...彼女はDwayneNeedの例のように動作します。

0
Youngy