一. 關於定義
主窗體的定義一般有兩種。第一種就是一般上,普遍意義認為是程序中第一個被創建出來的窗體,但是由於一些程序在顯示主窗體之前會有一個登錄或者引導窗體,在使用完了之后直接隱藏而不是關閉。這個時候,主窗體並不會是第一個窗體。所以,第二種說法就是說,包含了軟件整體功能的展示性界面所在的窗體,我們稱之為主窗體。
而在本文中,所有敘述中所指的主窗體都是指的是第二種定義的主窗體。
二. WPF中的主窗體與引子
對於 WPF 用戶來說,我們可以直接使用:
Window window = Application.Current.MainWindow;
而對於 Winform 來說就比較麻煩,因為它並沒有提供任何獲得主窗體的接口屬性(?)。網上有的文章說可以通過
Process.GetCurrentProcess().MainWindowHandle
來判斷當前的窗體是不是主窗體,經過博主的測試發現,這個方法並不能很好的判斷。經過測試核實 MainWindowHandle 有如下特點:
1. 首先,雖然這個屬性的名稱是 MainWindowHandle,但是不能在某個進程中使用這個屬性來判斷哪一個窗體是進程的主窗體;進程之間獲得主窗體可以考慮使用這個方式(但不是特別好用),所以這個方法適合做運行外接程序時使用,並且僅當進程有圖形界面(有窗體)時,該進程才具有與其關聯的主窗口。如果關聯進程沒有主窗口,則 MainWindowHandle 值為零。如果剛啟動了一個進程,並且想使用其主窗口句柄,則需要考慮使用 WaitForInputIdle 方法讓該進程完成啟動,從而確保創建了主窗體窗口的句柄。否則,將引發異常。
一般避免這個異常的代碼如下:
Process startProcess=... //獲得創建一個進程 startProcess.WaitForInputIdle(); Intptr handle=startProcess.MainWindowHandle;
2. 如果這個函數是在 Show(null) 或者 Application.Run(Form) 中的窗體打開的,那么這個值是這個打開窗體的句柄;如果這個函數是在 ShowDialog(null) 或者 ShowDialog(parentForm) 或者 Show(parentForm) 的時候,這個值是父級窗體的句柄;
具體的文章參考可以見下面地址鏈接的文章:
三. 三種博主的方法
博主目前有如下面所述的三種處理方式,如果有更好的方式可以在評論中和博主交流。
- 通過公共配置類的接口方式
原理:使用 Form 作為一個公共配置類的接口,所有的上端都通過這個公共配置類來訪問主窗體的對象。
這邊的公共配置類的代碼一般為:
/// <summary> /// 公共配置類 /// </summary> public class CommonBase { /// <summary> /// 主窗體對象的引用 /// </summary> public static Form MainForm { get; set; } }
上端調用代碼,通過在使用窗體的時候,將主窗體注入到公共配置類的接口中:
//創建主窗體 Form1 form1 = new Form1(); //設置主窗體 CommonBase.MainForm = form1; //添加到消息循環 Application.Run(form1);
由於這個公共配置類 CommonBase 會作為所有類的下端,所以所有的類都可以訪問到這個主窗體。
- 使用派生 AppliactionContext 的方式
原理:Application.Run 有多種重載的方式,我們一般使用的是
// // 摘要: // 在當前線程上開始運行標准應用程序消息循環,並使指定窗體可見。 // // 參數: // mainForm: // 一個 System.Windows.Forms.Form,它代表要使之可見的窗體。 // // 異常: // T:System.InvalidOperationException: // 主消息循環已在當前線程上運行。 public static void Run(Form mainForm);
這邊傳遞的是一個窗體的對象;而這個 Run 方法還有一個別的重載:
// 摘要: // 在特定的 System.Windows.Forms.ApplicationContext 中,在當前線程上開始運行標准應用程序消息循環。 // // 參數: // context: // 一個 System.Windows.Forms.ApplicationContext,應用程序將在其中運行。 // // 異常: // T:System.InvalidOperationException: // 主消息循環已在此線程上運行。 public static void Run(ApplicationContext context);
這個重載需要一個派生自 ApplictionContext 的對象。通過派生這個上下文對象,並在其中包裝一個主窗體的對象,然后在這個派生的子類中給出獲取自身的靜態方法,讓用戶可以獲得這個派生類的全局實例,最好是使用單例的方式來獲取。接着,利用 AppliactionContext 中的 MainForm 屬性來獲得注冊到 ApplicationContext 的主窗體。
下面給出一個派生類 MainFormContext 的代碼:
/// <summary> /// 派生ApplicationContext的方法 /// </summary> public class MainFormContext : ApplicationContext { /// <summary> /// 線程鎖 /// </summary> private static object objLock = new object(); /// <summary> /// 全局單例 /// </summary> private static MainFormContext context = null; /// <summary> /// 隱藏構造函數不讓外部調用創建 /// </summary> /// <param name="mainForm"></param> private MainFormContext(Form mainForm) : base(mainForm) { } /// <summary> /// 獲得MainFormContext /// </summary> /// <param name="mainForm"></param> /// <returns></returns> public static MainFormContext GetInstance(Form mainForm = null) { if (mainForm == null) { return context; } //創建單例 if (context == null) { lock (objLock) { if (context == null) { context = new MainFormContext(mainForm); } } } return context; } }
上端調用的代碼,創建包括 MainFormContext 的上下文,並使用 Application.Run(Application)進行執行:
//主窗體 Form1 form = new Form1(); //創建上下文 MainFormContext context = MainFormContext.GetInstance(form); //開啟消息循環 Application.Run(context);
- 利用 Application.OpenForms 集合找到主窗體
原理:通過 Application.OpenForms 集合遍歷所有的呈打開展示的窗體對象,然后通過比對窗體的名稱(或者別的特點)來找到主窗體的 Form 類型的對象。
注意:網上很多的文章都說,Application.OpenForms[0] 就是主窗體。其實,按照第一大點的定義,如果在出現主窗體之前還有別的窗體創建並且沒有被關閉(銷毀),那么這個 0 號序號的窗體就會是別的窗體(非主窗體)。這邊 Application.OpenForms 窗體的集合,是按照你在程序運行的過程中創建顯示窗體的順序來進行排列的。
比如,主窗體的名稱 Text 為 "MainForm",那么用來獲得主窗體的幫助類的代碼如下:
/// <summary> /// 匹配獲得主窗體的工具類 /// </summary> public class MainFormHelper { public Form GetMainForm(string mainFormName= "MainForm") { if(string.IsNullOrEmpty(mainFormName)) { return null; } foreach(Form frm in Application.OpenForms) { if(frm.Text.Trim()== mainFormName) { return frm; } } return null; } }
四. 方法的缺陷
這邊三種方案都存在一定的問題:
1. 要創建一個額外的幫助類,並且這個類要是所有類的下端,如果有一個類是這個幫助類的下端,那么這個類會訪問不到幫助類中的成員,也就是說不能訪問到主窗體的對象屬性;
2. 由於這個幫助類是所有類的下端,所以這個配置類中的主窗體只能以窗體的 Form 基類出現,導致其他的使用端不能直接使用主窗體具體類型的方法(需要轉換,甚至不能轉換);
3. 使用之前需要注入一次,操作和代碼都比較繁瑣;