MDI 窗口模式
开发专题 · 多文档界面(父容器 + 子窗体)
MDI(Multiple Document Interface,多文档界面)在一个父窗体客户区内打开多个子窗体,适合「主框架 + 多文档/多业务页」布局。本页以传统 System.Windows.Forms.Form 为准,说明 IsMdiContainer / MdiParent、排列方法与最小可运行实例。面向 .NET Framework 4.8;凡有代码均同时给出 C# 5.0 与 VB.NET。设计器文案以 LCode 中文界面为准。
模板说明:LCode「Windows 应用程序」项目模板与「Windows 窗体」文件模板不预置 MDI;主窗体默认是普通
Form。需在属性面板自行把父窗体设为 MDI 容器,并新增子窗体类。属性面板中文名:IsMdiContainer → 是 MDI 容器;MdiParent → MDI 父级(子窗体侧,运行时通常用代码赋值)。
何为 MDI:IsMdiContainer 与 MdiParent
关系可概括为两步:
- 父窗体:把
IsMdiContainer设为true。窗体客户区会变成 MDI 客户区(通常呈深色工作区背景),此后可承载子窗体。 - 子窗体:在
Show()之前把MdiParent指到该父窗体。子窗体只能有一个 MDI 父级;未设置MdiParent时按普通顶级窗体显示。
| 角色 | 关键设置 | 说明 |
|---|---|---|
| 父窗体(容器) | IsMdiContainer = true |
设计器选中主窗体 → 属性面板「是 MDI 容器」= True;或写在构造/InitializeComponent 中 |
| 子窗体 | child.MdiParent = parent; 然后 child.Show(); |
必须在显示前设置。子窗体可继续放菜单、网格、WebView2 等业务控件 |
| 菜单 / 工具栏 | 通常放在父窗体上 | 用父级菜单打开子窗体、调用 LayoutMdi;子窗体菜单可与父级 MenuStrip 合并(见下文注意) |
设计器步骤(父容器):按 窗体应用入门 建好项目后,打开主窗体设计面 → 属性面板找到「是 MDI 容器」→ 设为 True。再「添加」→「Windows 窗体」新建子窗体(如
ChildForm)。不要把手写逻辑塞进 InitializeComponent。
属性表
下列属性均属于 Form(传统窗体与继承自 Form 的窗体基类通用)。属性面板中文名以 LCode 本地化为准。
| 属性 | 面板显示名 | 类型 | 说明 |
|---|---|---|---|
IsMdiContainer |
是 MDI 容器 | bool |
父窗体设为 true;同一应用通常只有一个主框架如此设置 |
MdiParent |
MDI 父级 | Form |
子窗体指向容器;设为 null 则脱离 MDI(一般不在运行中途切换) |
MdiChildren |
(只读) | Form[] |
当前仍打开的子窗体数组;关闭全部时可遍历后 Close() |
ActiveMdiChild |
(只读) | Form |
当前激活的子窗体;无子窗体时为 null |
IsMdiChild |
(只读) | bool |
该窗体是否已作为 MDI 子窗体显示 |
菜单合并(可选):父、
MenuStrip 上可设 AllowMerge;子窗体菜单项用 MergeAction / MergeIndex 控制合并位置。父级可加 ToolStripMenuItem 的 MdiWindowListItem,自动列出已打开子窗体标题。复杂合并策略见 菜单栏、工具栏与状态栏。
方法与排列(Cascade / Tile)
父窗体调用 LayoutMdi(MdiLayout …) 重排客户区内的子窗体。常见菜单项:「层叠」「水平平铺」「垂直平铺」「排列图标」。
| 方法 / 枚举 | 说明 |
|---|---|
LayoutMdi(MdiLayout.Cascade) |
层叠:子窗体错位叠放,标题栏可见 |
LayoutMdi(MdiLayout.TileHorizontal) |
水平平铺:按行均分客户区 |
LayoutMdi(MdiLayout.TileVertical) |
垂直平铺:按列均分客户区 |
LayoutMdi(MdiLayout.ArrangeIcons) |
排列已最小化子窗体的图标 |
ActivateMdiChild(Form) |
激活指定子窗体(较少手写;一般 child.Activate() 即可) |
菜单处理示例(挂在父窗体):
C#
private void miCascade_Click(object sender, EventArgs e)
{
LayoutMdi(MdiLayout.Cascade);
}
private void miTileH_Click(object sender, EventArgs e)
{
LayoutMdi(MdiLayout.TileHorizontal);
}
private void miTileV_Click(object sender, EventArgs e)
{
LayoutMdi(MdiLayout.TileVertical);
}
private void miArrangeIcons_Click(object sender, EventArgs e)
{
LayoutMdi(MdiLayout.ArrangeIcons);
}
private void miCloseAll_Click(object sender, EventArgs e)
{
foreach (Form child in MdiChildren)
child.Close();
}
VB.NET
Private Sub miCascade_Click(sender As Object, e As EventArgs)
LayoutMdi(MdiLayout.Cascade)
End Sub
Private Sub miTileH_Click(sender As Object, e As EventArgs)
LayoutMdi(MdiLayout.TileHorizontal)
End Sub
Private Sub miTileV_Click(sender As Object, e As EventArgs)
LayoutMdi(MdiLayout.TileVertical)
End Sub
Private Sub miArrangeIcons_Click(sender As Object, e As EventArgs)
LayoutMdi(MdiLayout.ArrangeIcons)
End Sub
Private Sub miCloseAll_Click(sender As Object, e As EventArgs)
For Each child As Form In MdiChildren
child.Close()
Next
End Sub
完整最小实例
目标:主窗体为 MDI 容器,菜单「文件 → 新建子窗口」每次打开一个新的 ChildForm;「窗口」菜单提供层叠 / 水平平铺。假设项目已有 MainForm,并已添加子窗体类 ChildForm(继承 Form)。
设计器清单
| 对象 | 设置 |
|---|---|
MainForm |
IsMdiContainer = True(是 MDI 容器) |
MenuStrip |
顶级「文件(&F)」「窗口(&W)」 |
| 文件 → 新建子窗口 | Name = miNewChild,绑定单击 |
| 窗口 → 层叠 / 水平平铺 | miCascade / miTileH,绑定单击 |
ChildForm |
普通窗体即可;可放一个 Label 显示标题,便于区分多个实例 |
父窗体代码(C# / VB.NET)
C#
using System;
using System.Windows.Forms;
public partial class MainForm : Form
{
private int _childCount;
public MainForm()
{
InitializeComponent();
// 若未在设计器勾选「是 MDI 容器」,可在此强制:
// this.IsMdiContainer = true;
miNewChild.Click += miNewChild_Click;
miCascade.Click += miCascade_Click;
miTileH.Click += miTileH_Click;
}
private void miNewChild_Click(object sender, EventArgs e)
{
_childCount++;
ChildForm child = new ChildForm();
child.Text = "文档 " + _childCount.ToString();
child.MdiParent = this;
child.Show();
}
private void miCascade_Click(object sender, EventArgs e)
{
LayoutMdi(MdiLayout.Cascade);
}
private void miTileH_Click(object sender, EventArgs e)
{
LayoutMdi(MdiLayout.TileHorizontal);
}
}
VB.NET
Imports System
Imports System.Windows.Forms
Public Class MainForm
Inherits Form
Private _childCount As Integer
Public Sub New()
InitializeComponent()
' 若未在设计器勾选「是 MDI 容器」,可在此强制:
' Me.IsMdiContainer = True
AddHandler miNewChild.Click, AddressOf miNewChild_Click
AddHandler miCascade.Click, AddressOf miCascade_Click
AddHandler miTileH.Click, AddressOf miTileH_Click
End Sub
Private Sub miNewChild_Click(sender As Object, e As EventArgs)
_childCount += 1
Dim child As New ChildForm()
child.Text = "文档 " & _childCount.ToString()
child.MdiParent = Me
child.Show()
End Sub
Private Sub miCascade_Click(sender As Object, e As EventArgs)
LayoutMdi(MdiLayout.Cascade)
End Sub
Private Sub miTileH_Click(sender As Object, e As EventArgs)
LayoutMdi(MdiLayout.TileHorizontal)
End Sub
End Class
子窗体代码(可选:显示自己的标题)
C#
using System;
using System.Windows.Forms;
public partial class ChildForm : Form
{
public ChildForm()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
// 假设设计器已放 labelTitle
if (labelTitle != null)
labelTitle.Text = this.Text;
}
}
VB.NET
Imports System
Imports System.Windows.Forms
Public Class ChildForm
Inherits Form
Public Sub New()
InitializeComponent()
End Sub
Protected Overrides Sub OnLoad(e As EventArgs)
MyBase.OnLoad(e)
' 假设设计器已放 labelTitle
If labelTitle IsNot Nothing Then
labelTitle.Text = Me.Text
End If
End Sub
End Class
单例子窗体:若某业务窗体只应打开一份(如「订单列表」),可先在字段中缓存实例;已存在且未释放则
Activate(),否则 new → 设 MdiParent → Show(),并在 FormClosed 里清空缓存。
与 KryptonForm 共用时的注意
| 主题 | 说明 |
|---|---|
| API 是否存在 | KryptonForm 继承 Form,公开 IsMdiContainer;父/子赋值与 LayoutMdi 写法与传统窗体相同 |
| 推荐入门路径 | 学习 MDI 时优先用传统 Form 作父、子,确认行为后再换现代化壳 |
父为 KryptonForm |
可将 IsMdiContainer = true,子窗体用传统 Form 或 KryptonForm 并设 MdiParent |
| 自定义标题栏 / 最大化子窗 | Krypton 自绘非客户区与 MDI 最大化按钮皮肤;混用传统子窗与 Krypton 子窗时,个别主题下标题栏或系统按钮观感可能不一致。若遇绘制异常,先统一子窗基类再排查 |
| 设计器 MDI 预览 | 在 LCode 设计器中实时预览「已嵌入的多个 MDI 子窗」布局暂不支持,待更新完善;请运行后验证 |
| 现代化多页替代 | 若不需要经典浮动子窗,可考虑 Krypton Navigator / Workspace 等多页布局(见控件手册),与本专题经典 MDI 是不同模式 |
范围:本页不改动
krypton/** 控件正文。Krypton 窗体细节见 KryptonForm。
相关阅读
- C# / VB.NET 窗体应用入门 — 新建窗体项目、传统 Form 与 KryptonForm
- 菜单栏、工具栏与状态栏 — 父窗体菜单、工具栏与合并相关
- 文件与编辑器 — 设计器、属性面板与事件
- WebView2 — 子窗体内嵌浏览器时注意关闭释放
- 数据表单与自定义列 — 子窗体内数据网格场景
- KryptonForm — 现代化窗体基类