在 C# WinForms 中通過 WinAPI (SetParent) 將外部程序窗口嵌入 Panel 控件是常見需求。但當父窗體關閉再重新打開時,若不正確處理嵌入窗口的歸屬,會導致無法再次獲取句柄或外部程序異常退出。本文詳細解析問題根因與標準解決方案。
private void Form2_Load(object sender, EventArgs e)
{
ptrskypeWnd = API.FindWindow("CBoxMainFrame", null);
API.SetWindowPos(ptrskypeWnd, new IntPtr(-2), 0, 0,
Global.Globals.Object_WIDTH, Global.Globals.Object_HEIGHT, 0x0002);
System.Threading.Thread.Sleep(500);
MyInvoke mi = new MyInvoke(SetPanel);
BeginInvoke(mi, new object[] { ptrskypeWnd });
}
public void SetPanel(IntPtr ptrskypeWnd)
{
API.SetParent(ptrskypeWnd, this.panel1.Handle);
API.ShowWindow(ptrskypeWnd, WindowsApi.API.SW_MAXIMIZE);
}
SetParent(hWndChild, hWndNewParent) 會將子窗口的所有權轉移給新父窗口。當 Form2 關閉時:
Form2 被銷毀WM_DESTROY)Form2 時,FindWindow 找不到目標窗口private void Form2_FormClosing(object sender, FormClosingEventArgs e)
{
if (ptrskypeWnd != IntPtr.Zero)
{
// 將窗口重新設置為桌面窗口的子窗口(類名 #32769 或 HWND_DESKTOP)
API.SetParent(ptrskypeWnd, new IntPtr(0)); // 或 GetDesktopWindow()
// 可選:恢復窗口狀態
API.ShowWindow(ptrskypeWnd, API.SW_RESTORE);
}
}
| API | 用途 |
|---|---|
SetParent(hWndChild, hWndNewParent) | 改變窗口父子關係 |
GetDesktopWindow() | 獲取桌面窗口句柄(安全的「歸宿」) |
ShowWindow(hWnd, SW_RESTORE) | 恢復窗口正常顯示狀態 |
public partial class Form2 : Form
{
private IntPtr _externalWnd = IntPtr.Zero;
private const string TargetClassName = "CBoxMainFrame";
private void Form2_Load(object sender, EventArgs e)
{
AttachExternalWindow();
}
private void Form2_FormClosing(object sender, FormClosingEventArgs e)
{
DetachExternalWindow();
}
private void AttachExternalWindow()
{
_externalWnd = API.FindWindow(TargetClassName, null);
if (_externalWnd == IntPtr.Zero) return;
// 調整位置大小
API.SetWindowPos(_externalWnd, new IntPtr(-2), 0, 0,
Width, Height, 0x0002); // SWP_NOMOVE | SWP_NOZORDER 等
// 異步設置父窗口,避免 UI 線程阻塞
BeginInvoke(new Action(() =>
{
API.SetParent(_externalWnd, panel1.Handle);
API.ShowWindow(_externalWnd, API.SW_MAXIMIZE);
}));
}
private void DetachExternalWindow()
{
if (_externalWnd == IntPtr.Zero) return;
// 關鍵:歸還給桌面,避免連帶銷毀
API.SetParent(_externalWnd, API.GetDesktopWindow());
API.ShowWindow(_externalWnd, API.SW_RESTORE);
_externalWnd = IntPtr.Zero;
}
}
SetParent 受 UIPI (User Interface Privilege Isolation) 影響,可能需調用 ChangeWindowMessageFilterEx 允許消息傳遞try/finally 確保 DetachExternalWindow 必執行EnumWindows + 進程 ID 過濾精確定位關鍵在於「借用歸還」模式:Load 時借用嵌入,FormClosing 時歸還桌面。這不僅解決重複獲取句柄問題,更避免了對外部程序生命周期的破壞性干擾。