一、什么是多窗体应用程序?
多窗体应用程序就是包含两个或多个窗体的程序。在运动控制卡开发中,这非常常见,比如:
-
主窗体:显示主控界面,包含各种操作按钮
-
参数设置窗体:设置运动参数(速度、加速度等)
-
监控窗体:实时显示轴的位置、速度、状态
-
日志窗体:显示运行日志和错误信息
二、窗体之间的四种关系
1. 普通父子窗体
原理:子窗体与父窗体关联,关闭父窗体时子窗体也会关闭。
特点:
-
子窗体始终显示在父窗体前面
-
最小化父窗体时,子窗体也会最小化
-
关闭父窗体时,子窗体自动关闭
应用场景:工具窗口、辅助窗口
核心代码:
ChildForm child = new ChildForm();
child.Show(this); // this 表示父窗体
2. 模态窗体
原理:打开后阻塞父窗体,必须关闭子窗体才能操作父窗体。
特点:
-
用户必须先处理完模态窗体,才能回到主窗体
-
通常用于需要用户确认或输入的场景
-
可以返回操作结果(确定/取消)
应用场景:登录窗口、参数设置对话框、确认对话框
核心代码:
// 打开模态窗体
ChildForm child = new ChildForm();
DialogResult result = child.ShowDialog();
// 检查用户的选择
if (result == DialogResult.OK)
{
// 用户点击了确定
}
子窗体中设置返回值:
private void buttonOK_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.OK; // 返回"确定"
this.Close();
}
3. 非模态窗体
原理:打开后不阻塞父窗体,可以同时操作多个窗体。
特点:
-
主窗体和子窗体都可以独立操作
-
可以同时打开多个相同类型的窗体
-
需要手动管理窗体的生命周期(避免内存泄漏)
应用场景:监控窗口、日志查看器、工具箱
核心代码:
// 简单打开
ChildForm child = new ChildForm();
child.Show();
// 推荐方式:保持引用,避免重复创建
private ChildForm childForm;
private void buttonOpenChild_Click(object sender, EventArgs e)
{
if (childForm == null || childForm.IsDisposed)
{
childForm = new ChildForm();
}
childForm.Show();
}
4. MDI(多文档界面)
原理:一个父窗体包含多个子窗体,子窗体限制在父窗体内部。
特点:
-
子窗体不能超出父窗体范围
-
可以进行窗口排列(平铺、层叠)
-
类似 Word、Excel 的多文档编辑模式
应用场景:文本编辑器、图形编辑软件
核心代码:
// 主窗体构造函数中
this.IsMdiContainer = true; // 设置为 MDI 父窗体
// 打开 MDI 子窗体
ChildForm child = new ChildForm();
child.MdiParent = this; // 设置父窗体
child.Show();
// 窗口排列
LayoutMdi(MdiLayout.TileHorizontal); // 水平平铺
LayoutMdi(MdiLayout.Cascade); // 层叠
三、窗体之间的数据传递
1. 通过构造函数传递(主窗体 → 子窗体)
原理:在创建子窗体时,通过构造函数参数传递数据。
适用场景:简单的、一次性的数据传递
核心代码:
// 子窗体
public class ChildForm : Form
{
private string userName;
public ChildForm(string name) // 接收参数
{
InitializeComponent();
userName = name;
labelWelcome.Text = $"欢迎, {userName}!";
}
}
// 主窗体调用
ChildForm child = new ChildForm("张三");
child.ShowDialog();
2. 通过属性传递(双向)
原理:子窗体定义公共属性,主窗体通过属性读写数据。
适用场景:灵活的数据传递,可以设置和获取
核心代码:
// 子窗体
public class ChildForm : Form
{
public string UserName { get; set; } // 公共属性
private void ChildForm_Load(object sender, EventArgs e)
{
labelWelcome.Text = $"欢迎, {UserName}!";
}
}
// 主窗体调用
ChildForm child = new ChildForm();
child.UserName = "张三"; // 设置数据
child.ShowDialog();
// 子窗体关闭后获取数据
string name = child.UserName;
3. 通过公共方法传递
原理:子窗体提供公共方法,主窗体调用方法传递数据。
适用场景:需要数据验证或处理的情况
核心代码:
// 子窗体
public class ChildForm : Form
{
public void SetData(string name, int age)
{
textBoxName.Text = name;
numericUpDownAge.Value = age;
}
public string GetData()
{
return textBoxName.Text;
}
}
// 主窗体调用
ChildForm child = new ChildForm();
child.SetData("张三", 25);
child.ShowDialog();
string data = child.GetData();
4. 通过事件传递(子窗体 → 主窗体)
原理:子窗体定义事件,主窗体订阅事件,子窗体触发事件时通知主窗体。
适用场景:非模态窗体,需要实时传递数据
核心代码:
// 子窗体
public class ChildForm : Form
{
// 定义事件
public event EventHandler<string> DataChanged;
private void textBox_TextChanged(object sender, EventArgs e)
{
// 触发事件,通知主窗体
DataChanged?.Invoke(this, textBox.Text);
}
}
// 主窗体
private void buttonOpenChild_Click(object sender, EventArgs e)
{
ChildForm child = new ChildForm();
// 订阅事件
child.DataChanged += (s, data) =>
{
labelMain.Text = data; // 接收数据
};
child.Show();
}
5. 通过静态类共享数据
原理:定义一个静态类,所有窗体都可以访问其中的数据。
适用场景:全局数据、配置信息、用户信息
核心代码:
// 静态数据类
public static class AppData
{
public static string CurrentUser { get; set; }
public static int CurrentUserId { get; set; }
}
// 在任何窗体中使用
AppData.CurrentUser = "张三";
string user = AppData.CurrentUser;
四、窗体管理的最佳实践
1. 避免内存泄漏
问题:每次打开窗体都创建新对象,关闭后不释放,造成内存泄漏。
解决方案:
-
检查窗体是否已存在,重用现有窗体
-
使用单例模式
-
监听 FormClosed 事件,及时清理
核心代码:
private ChildForm childForm;
private void buttonOpen_Click(object sender, EventArgs e)
{
if (childForm == null || childForm.IsDisposed)
{
childForm = new ChildForm();
childForm.FormClosed += (s, e) => { childForm = null; };
}
childForm.Show();
}
2. 模态窗体使用 using 语句
好处:自动释放资源,代码更清晰
核心代码:
using (var dialog = new SettingsForm())
{
if (dialog.ShowDialog() == DialogResult.OK)
{
// 处理结果
}
} // 自动释放
3. 单例模式(确保只有一个实例)
原理:使用静态变量保存窗体实例,确保全局只有一个实例。
应用场景:日志窗口、状态窗口、设置窗口
核心代码:
public class SingletonForm : Form
{
private static SingletonForm instance;
private SingletonForm() { }
public static SingletonForm GetInstance()
{
if (instance == null || instance.IsDisposed)
{
instance = new SingletonForm();
}
return instance;
}
}
// 使用
var form = SingletonForm.GetInstance();
form.Show();
五、运动控制应用中的多窗体设计
典型架构
MainForm (主控窗体)
├── AxisMonitorForm (轴监控窗体) - 可打开多个,非模态
├── IOStatusForm (IO状态窗体) - 单例,非模态
├── SettingsForm (参数设置窗体) - 模态
├── ProgramEditorForm (程序编辑窗体) - MDI
└── LogViewerForm (日志查看窗体) - 单例,非模态
设计原则
-
主窗体:负责整体控制,打开其他窗体
-
监控窗体:使用非模态,可以同时打开多个,实时刷新数据
-
设置窗体:使用模态,确保用户完成设置后再继续
-
日志窗体:使用单例,全局只有一个日志窗口
-
程序编辑窗体:使用 MDI,可以同时编辑多个程序
关键注意事项
-
关闭主窗体前:
-
检查是否有轴在运动
-
停止所有运动
-
关闭所有子窗体
-
保存设置
-
-
监控窗体:
-
使用定时器刷新数据
-
窗体关闭时停止定时器
-
避免重复打开同一轴的监控窗体
-
-
数据同步:
-
使用事件机制,子窗体数据变化时通知主窗体
-
主窗体参数变化时,通知所有监控窗体更新
-
六、总结
窗体类型对比
| 类型 | 方法 | 阻塞父窗体 | 应用场景 |
|---|---|---|---|
| 普通子窗体 | Show() |
否 | 工具窗口 |
| 模态窗体 | ShowDialog() |
是 | 设置对话框 |
| MDI 子窗体 | Show() + MdiParent |
否 | 多文档编辑 |
| 单例窗体 | 自定义管理 | 否 | 状态窗口 |
数据传递方式选择
| 方式 | 适用场景 | 优点 |
|---|---|---|
| 构造函数 | 简单数据传递 | 简单直接 |
| 属性 | 灵活的数据传递 | 可读可写 |
| 公共方法 | 需要数据处理 | 可验证逻辑 |
| 事件 | 非模态窗体实时通信 | 解耦、实时 |
| 静态类 | 全局数据共享 | 全局访问 |
最佳实践要点
-
模态窗体用于需要用户确认的场景
-
非模态窗体要正确管理生命周期,避免内存泄漏
-
使用事件实现窗体间松耦合通信
-
合理使用单例模式管理全局窗体
-
运动控制应用中,关闭前确保停止所有运动