C# WinForm获得主窗体——如何判断哪个是主窗体
一. 关于定义
主窗体的定义一般有两种。第一种就是一般上,普遍意义认为是程序中第一个被创建出来的窗体,但是由于一些程序在显示主窗体之前会有一个登录或者引导窗体,在使用完了之后直接隐藏而不是关闭。这个时候,主窗体并不会是第一个窗体。所以,第二种说法就是说,包含了软件整体功能的展示性界面所在的窗体,我们称之为主窗体。
而在本文中,所有叙述中所指的主窗体都是指的是第二种定义的主窗体。
二. 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. 使用之前需要注入一次,操作和代码都比较繁琐;