Hosten einer WPF-App in einer Nicht-WPF-App mit WIn32 SetParent()
Frage
Ich habe eine WPF-Anwendung, die so aussehen soll, als wäre sie in einer anderen – Nicht-WPF-Anwendung – gehostet.Im wirklichen Leben ist diese Nicht-WPF-App ein ActiveX im Internet Explorer, aber um das Problem zu veranschaulichen, verwende ich eine einfache Windows Forms-App.
Ich verwende die Windows-API-Funktion SetParent, zu der es bereits Dutzende Threads gibt.Allerdings kann ich zu meinem genauen Problem nichts Geschriebenes finden: Ein kleiner Bereich rechts und unten in der WPF-App wird nicht im Fenster der Nicht-WPF-App angezeigt.
Das WPF-Fenster läuft alleine:
Das WPF-Fenster mit dem Fenster der WinForm-App als übergeordnetem Fenster:
Das Problem tritt bei mir nicht auf, wenn ich die WPF-App durch eine WinForms-App oder eine einfache Win32-App (wie Notepad) austausche.
Der WinForm-Code sieht folgendermaßen aus:
private void Form1_Load(object sender, EventArgs e)
{
// Start process
var psi = new ProcessStartInfo("c:\\wpfapp\\wpfapp\\bin\\Debug\\wpfapp.exe");
psi.WindowStyle = ProcessWindowStyle.Normal;
_process = Process.Start(psi);
// Sleep until new process is ready
Thread.Sleep(1000);
// Set new process's parent to this window
SetParent(_process.MainWindowHandle, this.Handle);
// Remove WS_POPUP and add WS_CHILD window style to child window
const int GWL_STYLE = -16;
const long WS_POPUP = 0x80000000;
const long WS_CHILD = 0x40000000;
long style = GetWindowLong(_process.MainWindowHandle, GWL_STYLE);
style = (style & ~(WS_POPUP)) | WS_CHILD;
SetWindowLong(_process.MainWindowHandle, GWL_STYLE, style);
// Move and resize child window to fit into parent's
MoveWindow(_process.MainWindowHandle, 0, 0, this.Width, this.Height, true);
}
Notiz:Mir ist bewusst, dass diese Verwendung von SetParent nicht unbedingt eine empfohlene Vorgehensweise ist, aber ich möchte und muss herausfinden, wie das auf diese Weise geht, also lassen Sie es mich bitte :)
Lösung
Ich habe einen Workaround gefunden, der ziemlich gut funktioniert:Rufen Sie MoveWindow vor SetParent auf:
private void Form1_Load(object sender, EventArgs e)
{
// Start process
var psi = new ProcessStartInfo("C:\\WpfApp\\WpfApp.exe");
psi.WindowStyle = ProcessWindowStyle.Normal;
_process = Process.Start(psi);
// Sleep until new process is ready
Thread.Sleep(3000);
// Move and resize child window to fit into parent's
Rectangle rect;
GetClientRect(this.Handle, out rect);
MoveWindow(_process.MainWindowHandle, 0, 0, rect.Right - rect.Left, rect.Bottom - rect.Top, true);
// Set new process's parent to this window
SetParent(_process.MainWindowHandle, this.Handle);
// Remove WS_POPUP and add WS_CHILD window style to child window
long style = GetWindowLong(_process.MainWindowHandle, GWL_STYLE);
style = (style & ~(WS_POPUP) & ~(WS_CAPTION)) & ~(WS_THICKFRAME) | WS_CHILD;
SetWindowLong(_process.MainWindowHandle, GWL_STYLE, style);
}
Im SizeChanged-Ereignishandler nehme ich das untergeordnete Element aus dem übergeordneten Element, rufe MoveWindow auf und verschiebe es zurück in das übergeordnete Element.Um das Flackern zu reduzieren, blende ich das Fenster aus, während ich diese Vorgänge ausführe:
private void Form1_SizeChanged(object sender, EventArgs e)
{
ShowWindow(_process.MainWindowHandle, SW_HIDE);
var ptr = new IntPtr();
SetParent(_process.MainWindowHandle, ptr);
Rectangle rect;
GetClientRect(this.Handle, out rect);
MoveWindow(_process.MainWindowHandle, 0, 0, rect.Right - rect.Left, rect.Bottom - rect.Top, true);
SetParent(_process.MainWindowHandle, this.Handle);
ShowWindow(_process.MainWindowHandle, SW_SHOW);
}
Es erklärt nicht, warum MoveWindow nach SetParent nicht funktioniert, aber es löst mein Problem, daher werde ich dies als Antwort markieren, sofern nicht etwas Besseres auftaucht.
Andere Tipps
Sie sollten wahrscheinlich die Größe Ihres WPF-Fensters ändern, damit es in das passt Kundenbereich seines neuen übergeordneten Fensters:
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[DllImport("user32.dll")]
static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);
// Move and resize child window to fit into parent's client area.
RECT rc = new RECT();
GetClientRect(this.Handle, out rc);
MoveWindow(_process.MainWindowHandle, 0, 0, rc.Right - rc.Left,
rc.Bottom - rc.Top, true)
Ich habe ein Formular/UserControl mit einem Panel verwendet.Dann habe ich das Panel als neues übergeordnetes Fenster verwendet und die neue Position und Größe des Panels mit SetWindowPos auf das untergeordnete Fenster festgelegt.
NOTIZ:MS empfiehlt, SetWindowPos nach der Verwendung von SetWindowLong (oder SetWindowLongPtr) zu verwenden, um die neuen Stile zu aktivieren.