【轉】C#自定義控制元件:WinForm將其它應用程式窗體嵌入自己內部
PS:文末的附件已更新,這次我放到部落格園裡面了,不會彈出廣告,放心下載,O(∩_∩)O謝謝!
這是最近在做的一個專案中提到的需求,把一個現有的窗體應用程式介面嵌入到自己開發的窗體中來,看起來就像自己開發的一樣(實際上……跟自己開發的還是有一點點區別的,就是內嵌程式和宿主程式的視窗啟用狀態問題)。
直接上圖先:
從開啟Adobe Reader那張圖片可以看出來所謂的“內嵌程式和宿主程式的視窗啟用狀態問題”。當內嵌程式視窗啟用時,表面上將其包裹起來的宿主視窗卻處於非啟用的狀態。想隱藏這一點的話,把視窗的FormBorderStyle屬性設為None吧,然後自己在視窗上畫關閉、最大化、最小化按鈕好了。
原作者的實現思路更能暴露本質,所以這裡用原作者的程式碼段解釋一下實現過程。
1、啟動要嵌入的應用程式程序
1 Process p = null; 2 try 3 { 4 // Start the process 5 p = System.Diagnostics.Process.Start(this.exeName); 6 7 // Wait for process to be created and enter idle condition 8 p.WaitForInputIdle(); 9 10 // Get the main handle11 appWin = p.MainWindowHandle; 12 } 13 catch (Exception ex) 14 { 15 MessageBox.Show(this, ex.Message, "Error"); 16 }
2、呼叫Windows API將啟動的應用程式視窗嵌入自定義的控制元件(作者用的是Panel控制元件)
1 // Put it into this form 2 SetParent(appWin, this.Handle);//this在這裡是Panel控制元件
3 4 // Remove border and whatnot 5 SetWindowLong(appWin, GWL_STYLE, WS_VISIBLE);6 7 // Move the window to overlay it on this window 8 MoveWindow(appWin, 0, 0, this.Width, this.Height, true);
3、設定被嵌入的窗體大小隨宿主窗體改變
1 protected override void OnResize(EventArgs e) 2 { 3 if (this.appWin != IntPtr.Zero) 4 { 5 MoveWindow(appWin, 0, 0, this.Width, this.Height, true); 6 } 7 base.OnResize (e); 8 }
4、設定被嵌入的窗體應用程式在宿主程式關閉時也關閉
1 protected override void OnHandleDestroyed(EventArgs e) 2 { 3 // Stop the application 4 if (appWin != IntPtr.Zero) 5 { 6 // Post a colse message 7 PostMessage(appWin, WM_CLOSE, 0, 0); 8 9 // Delay for it to get the message 10 System.Threading.Thread.Sleep(1000); 11 12 // Clear internal handle 13 appWin = IntPtr.Zero; 14 } 15 base.OnHandleDestroyed (e); 16 }
原作者的程式碼實際用起來是很不方便的,具體大家試試就知道,不細說了(反正我只學了學上面的步驟,也不用他的庫)。
本人開發了一個比較實用的控制元件,使用起來也很簡單,只需三步。
首先,在窗體應用程式專案中引用類庫SmileWei.EmbeddedApp。
然後,在宿主窗體上拖一個AppContainer控制元件,擺放好位置。(如果工具箱裡沒有AppContainer,就F6生成解決方案一下,然後再看就有了。)
最後,告訴AppContainer控制元件,要嵌入的應用程式(*.exe檔案)的絕對路徑(本人以使用OpenFileDialog為例),命令AppContainer控制元件啟動之。
1 appContainer1.AppFilename = openEXE.FileName; 2 appContainer1.Start();
這個AppContainer控制元件有什麼好處呢?
1、原作者想到的Resize和隨宿主程式關閉而關閉的問題,AppContainer都實現了。
2、AppContainer指定要嵌入的應用程式和啟動是分開的,這樣更靈活,開發過程中也不會看到如下的情況了:開發的時候原作者的控制元件就“情不自禁”地把內嵌程式載入進來了。
3、AppContainer防範了各種可能出錯的情形,例如禁止自己嵌入自己(死迴圈)、內嵌Console程式時提示不能嵌入、引數為null或無效的檢驗等。
4、其它。例如,AppContainer裡面不會使用Thread.Sleep(1000);這樣低端的句子來保證程式正確地嵌入(而且對於類似photoshop這樣啟動很慢的程式也保證不了),而是通過Application.Ilde事件實現了在被嵌程式載入完畢後才將其窗體嵌入的技巧。
當然,有些應用程式是不能這麼自動化地嵌入進來的。因為程式啟動窗體和主窗體控制代碼不一樣,AppContainer無法獲得主窗體控制代碼,所以無法自動嵌入。
為了解決這個問題,我在宿主窗體的狀態列上設定了“控制代碼嵌入”標籤,點選“控制代碼嵌入”,你可以填入想嵌入的應用程式主窗體控制代碼,然後宿主窗體就可以嵌入它了。
然後有同學就問了,我怎麼知道想要嵌入的窗體控制代碼是多少啊?方法很多啦,我這裡也提供一個自己製作的小程式,大家可以在這裡下載:WindowDetective(視窗偵探)0.20.rar
介面是這個樣子的:
裡面“控制代碼:{1903014}”那一行就給出了本人正在用的Windows Live Writer的主窗體控制代碼。
用法很簡單,啟動這個程式後,它會自動檢測滑鼠所在位置的窗體資訊,顯示在視窗中。所以把滑鼠放在你想了解的窗體選單欄上就OK了。QQ TM版也可以這樣嵌進來滴。(QQ嵌不進來,不知道騰訊在搞什麼)
大家還可以試試把QQ對話方塊嵌進來,很好玩哦~
我的原始碼都給出了明確的註釋,型別、變數名也都規範易懂,在此不再多做解釋了,有疑問請留言吧O(∩_∩)O
本文所有原始碼、可執行程式均可在下面列出的連結中下載到。