一、MDI 核心概念
1. 什么是 MDI
MDI(多文档界面):一个主窗体中可以嵌套多个子窗体,所有子窗体只能在主窗体内部显示,不会独立弹出悬浮窗口。
常见软件场景:Word、Excel、PS 多文档窗口。
2. 核心属性与方法
窗体.IsMdiContainer = true:设置当前窗体为MDI父容器(必须手动勾选/设置)子窗体.MdiParent = this:指定子窗体的所属父窗体this.MdiChildren:获取当前父窗体所有已打开的子窗体集合LayoutMdi():批量排布子窗体布局
二、MDI 三种经典布局枚举
布局枚举 | 效果 |
|---|---|
MdiLayout.TileVertical | 垂直平铺(左右均分) |
MdiLayout.TileHorizontal | 水平平铺(上下均分) |
MdiLayout.Cascade | 层叠平铺(堆叠窗口) |
三、基础功能:打开多个子窗体 + 自动布局
一次性打开 Form2、Form3,并设置垂直平铺布局
private void 打开窗体ToolStripMenuItem_Click(object sender, EventArgs e) { // 打开子窗体2,并绑定父容器 Form2 f2 = new Form2(); f2.MdiParent = this; f2.Show(); // 打开子窗体3,修改背景色并绑定父容器 Form3 f3 = new Form3(); f3.MdiParent = this; f3.BackColor = Color.Red; f3.Show(); // 所有子窗体 垂直平铺 LayoutMdi(MdiLayout.TileVertical); }四、布局切换功能
private void 设置窗体ToolStripMenuItem_Click(object sender, EventArgs e) { // 切换为水平平铺 LayoutMdi(MdiLayout.TileHorizontal); }五、核心重难点:通用 Open 方法(子窗体单例逻辑)
1. 功能需求
重复点击菜单,不会重复创建同类型窗体
如果当前已经打开该类型窗体:隐藏所有窗体、激活并显示原有窗体
如果是新类型窗体:隐藏所有旧窗体、展示新窗体
实现始终只显示一个子窗体的效果
2. 完整核心源码
/// <summary> /// MDI子窗体通用打开方法(去重+单例+切换逻辑) /// </summary> public void Open(Form f) { // 1. 遍历所有已打开的MDI子窗体 foreach (var item in this.MdiChildren) { // 判断:即将打开的窗体类型 == 已存在的子窗体类型 if (f.GetType() == item.GetType()) { // 隐藏所有子窗体 foreach (Form child in this.MdiChildren) { child.Hide(); } // 显示并激活当前已有窗体(不重复new) item.Show(); item.Activate(); return; } } // 2. 走到这里说明:当前没有同类型窗体 // 隐藏所有旧窗体 foreach (Form child in this.MdiChildren) { child.Hide(); } // 绑定父容器并展示新窗体 f.MdiParent = this; f.Show(); }3. 逻辑拆解
GetType() 类型对比:判断窗体是否为同一个窗体类,解决重复实例化问题
存在同类型窗体:全部隐藏 → 激活旧窗体 → return 终止方法,不创建新对象
不存在同类型窗体:隐藏所有旧窗体 → 创建并展示新窗体
六、菜单调用封装方法
// 打开Form2(单例) private void 打开窗体1ToolStripMenuItem_Click(object sender, EventArgs e) { Open(new Form2()); } // 打开Form3(单例) private void 打开窗体2ToolStripMenuItem_Click(object sender, EventArgs e) { Open(new Form3()); }七、MDI 两套写法对比
写法一(基础版)
每次点击都 new 新窗体,会出现多个同类型子窗体叠加
写法二(进阶封装版)
通过类型判断 + 隐藏激活,保证同类窗体永远唯一,企业项目标准写法
八、高频易错点
主窗体必须开启IsMdiContainer = true,否则无法嵌套子窗体
必须赋值MdiParent = this,否则窗体独立弹出,不属于子窗体
== 对比窗体对象会一直创建新窗体,必须用GetType() 对比类型
不写 return 会继续执行创建新窗体,无法实现单例
MdiChildren 只能获取当前存活、已加载的子窗体
九、MDI 背诵口诀
主窗开启容器态,子窗绑定父窗体;
MdiChildren遍历子,类型判断去重替;
先藏后显保唯一,布局平铺横竖齐;
封装通用Open法,多文档界面稳如一。