ユーザーが移動/サイズ変更などを行えるビューポートを持つWPFのレイアウトマネージャーを開発しています。通常、ビューポートには、レイアウトマネージャーで管理されているプロバイダーを介してデータ(画像/映画/など)が入力されます。私の仕事は、外部のWindowsアプリ(メモ帳、電卓、Adobe Readerなど)をビューポートでホストできるかどうかを調べることです。多くの問題に遭遇しました。
ほとんどのリソースは、HwndHostクラスの使用を指しています。私はマイクロソフト自身からのこのチュートリアルで実験しています: http://msdn.Microsoft.com/en-us/library/ms752055.aspx
リストボックスが外部アプリケーションからのウィンドウハンドルに置き換えられるように、これを調整しました。誰でもこれらの質問で私を助けることができます:
ListBox
が配置される静的なサブウィンドウを追加します。外部アプリには必要ないと思います。省略すると、外部アプリを子ウィンドウにする必要があります(user32.dllのGet/SetWindowLongを使用してGWL_STYLE
なので WS_CHILD
)。しかし、それを行うと、アプリのメニューバーが消えます(WS_CHILD
style)そして、それはもはや入力を受け取りません。HwndHost
によってホストされません(したがって、ビューポート外に移動できます)。それを防ぐ方法はありますか?HwndHost
ではなくWindowsFormHost
を使用することです here で説明します。それは動作します(そしてもっと簡単です!)が、アプリケーションのサイズを制御することはできませんか?また、WinFormHostは本当にこれを目的としていませんか?正しい方向へのポインタをありがとう。
ええと... 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動作します。ただし、これには制限があります(キーボード、マウス、フォーカスなど)。
幸運を!
Simon Mourierの答えは非常によく書かれています。ただし、自分で作成したwinformアプリで試してみたところ、失敗しました。
_process.WaitForInputIdle();
に置き換えることができます
while (_process.MainWindowHandle==IntPtr.Zero)
{
Thread.Sleep(1);
}
すべてがスムーズに進みます。
すばらしい質問と回答をありがとうございました。
このスレッドの回答を読んで、自分で試行錯誤を繰り返した結果、かなりうまくいくものになりましたが、もちろん、特別な場合には注意が必要なことがあります。
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ウィンドウの子として追加すると、そこにホストされているメモ帳ウィンドウが表示されます。
私はこれを実稼働環境で実行しており、これまでのところ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);
ソリューションは非常に複雑です。たくさんのコード。ここにいくつかのヒントがあります。
まず、あなたは正しい軌道に乗っています。
HwndHostとHwndSourceを使用する必要があります。そうしないと、視覚的なアーティファクトが得られます。ちらつきのように。警告。ホストとソースを使用しない場合、動作するように見えますが、最終的には動作しません。ランダムで小さな愚かなバグが発生します。
いくつかのヒントについては、これをご覧ください。完全ではありませんが、正しい方向に進むのに役立ちます。 http://microsoftdwayneneed.codeplex.com/SourceControl/changeset/view/50925#1029346
Win32にアクセスして、求めていることの多くを制御する必要があります。メッセージをキャッチして転送する必要があります。どのウィンドウが子ウィンドウを「所有する」かを制御する必要があります。
Spy ++ alotを使用します。
私の答えを確認してください: wpfアプリケーション内でアプリケーションを実行する方法?
DwayneNeedジガリーなしでメモ帳の例を機能させることができました。 SetParent()とboomを追加しました...彼女はDwayneNeedの例のように動作します。