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 容器MdiParentMDI 父级(子窗体侧,运行时通常用代码赋值)。

何为 MDI:IsMdiContainerMdiParent

关系可概括为两步:

  1. 父窗体:把 IsMdiContainer 设为 true。窗体客户区会变成 MDI 客户区(通常呈深色工作区背景),此后可承载子窗体。
  2. 子窗体:在 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 控制合并位置。父级可加 ToolStripMenuItemMdiWindowListItem,自动列出已打开子窗体标题。复杂合并策略见 菜单栏、工具栏与状态栏

方法与排列(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 → 设 MdiParentShow(),并在 FormClosed 里清空缓存。

与 KryptonForm 共用时的注意

主题 说明
API 是否存在 KryptonForm 继承 Form,公开 IsMdiContainer;父/子赋值与 LayoutMdi 写法与传统窗体相同
推荐入门路径 学习 MDI 时优先用传统 Form 作父、子,确认行为后再换现代化壳
父为 KryptonForm 可将 IsMdiContainer = true,子窗体用传统 FormKryptonForm 并设 MdiParent
自定义标题栏 / 最大化子窗 Krypton 自绘非客户区与 MDI 最大化按钮皮肤;混用传统子窗与 Krypton 子窗时,个别主题下标题栏或系统按钮观感可能不一致。若遇绘制异常,先统一子窗基类再排查
设计器 MDI 预览 在 LCode 设计器中实时预览「已嵌入的多个 MDI 子窗」布局暂不支持,待更新完善;请运行后验证
现代化多页替代 若不需要经典浮动子窗,可考虑 Krypton Navigator / Workspace 等多页布局(见控件手册),与本专题经典 MDI 是不同模式
范围:本页不改动 krypton/** 控件正文。Krypton 窗体细节见 KryptonForm

相关阅读