C#打开关闭窗体事件顺序

C#打开关闭窗体事件顺序

C#.net开发    2009-06-25 17:02   阅读61   评论0  
字号:    
应用程序启动与关闭事件 Form 和 Control 类和应用程序启动与关闭有关的时间。
   

   当 Windows Form 应用程序启动时,会以下列顺序引发主要表单的启动事件:
        System.Windows.Forms.Control.HandleCreated
        System.Windows.Forms.Control.BindingContextChanged
        System.Windows.Forms.Form.Load
        System.Windows.Forms.Control.VisibleChanged
        System.Windows.Forms.Form.Activated
        System.Windows.Forms.Form.Shown

    当应用程序关闭时,会以下列顺序引发主要表单的关闭事件:         
        System.Windows.Forms.Form.Closing
        System.Windows.Forms.Form.FormClosing
        System.Windows.Forms.Form.Closed
        System.Windows.Forms.Form.FormClosed
        System.Windows.Forms.Form.Deactivate

转载于:https://www.cnblogs.com/chenhs/archive/2009/08/05/1539197.html

<think>我们正在处理一个C# WinForms应用程序中限制同时打开窗口数量的需求。根据提供的引用,我们可以结合WinForms的特性和一些编程技巧来实现。 思路: 1. 我们需要跟踪当前打开窗口数量。 2. 在打开窗口之前,检查当前打开窗口数量是否已达到上限。 3. 如果达到上限,则阻止打开窗口,或者关闭一个已打开窗口打开窗口(根据需求而定)。 4. 当窗口关闭时,更新当前打开窗口数量。 实现方法: 我们可以使用一个计数器来记录当前打开窗口数量。同时,我们需要在打开窗口事件中(如按钮点击事件)进行判断。 步骤: a. 在父窗体中定义一个整数变量(如`openChildCount`)来记录当前打开窗口数量。 b. 设置一个最大同时打开窗口数量的常量(如`MAX_CHILD_WINDOWS = 5`)。 c. 在创建窗口之前,检查`openChildCount`是否小于`MAX_CHILD_WINDOWS`。如果小于,则允许打开;否则,可以给出提示或采取其他措施。 d. 当窗口打开时,增加计数器(`openChildCount++`)。 e. 当窗口关闭时,减少计数器(`openChildCount--`)。注意:我们需要在窗口关闭事件中处理。 但是,窗口关闭事件处理需要注意,因为关闭事件可能由多种方式触发(比如用户点击关闭按钮、程序调用Close等)。我们可以通过订阅窗口的FormClosed事件来减少计数器。 代码示例: 在父窗体中: ```csharp public partial class ParentForm : Form { private int openChildCount = 0; private const int MAX_CHILD_WINDOWS = 5; // 最大同时打开窗口数量 public ParentForm() { InitializeComponent(); } private void btnOpenChild_Click(object sender, EventArgs e) { // 检查是否达到最大数量 if (openChildCount >= MAX_CHILD_WINDOWS) { MessageBox.Show($"同时打开窗口不能超过{MAX_CHILD_WINDOWS}个", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } // 创建窗口 ChildForm childForm = new ChildForm(); // 订阅窗口关闭事件 childForm.FormClosed += (s, args) => { openChildCount--; // 窗口关闭时减少计数 }; childForm.Show(); // 显示窗口 openChildCount++; // 增加计数 } } ``` 注意:上面的代码使用lambda表达式订阅了窗口的FormClosed事件。这样当窗口关闭时,就会执行计数减一的操作。 但是,这里有一个潜在的问题:如果用户多次点击打开按钮,会创建多个窗口,每个窗口关闭时都会减少计数。这样处理是合理的。 另外,我们也可以考虑使用List来保存打开窗口引用,这样不仅可以计数,还可以在需要时操作具体的窗口(例如强制关闭一个窗口)。但如果我们只需要计数,那么使用计数器就足够了。 另一种情况:如果我们希望当打开窗口数量达到上限时,自动关闭最早打开窗口打开窗口,我们可以使用队列来保存窗口的引用。 示例(使用队列): ```csharp public partial class ParentForm : Form { private Queue<ChildForm> openChildForms = new Queue<ChildForm>(); private const int MAX_CHILD_WINDOWS = 5; public ParentForm() { InitializeComponent(); } private void btnOpenChild_Click(object sender, EventArgs e) { // 如果达到最大数量,则关闭最早打开窗口 if (openChildForms.Count >= MAX_CHILD_WINDOWS) { // 关闭队列中的第一个窗口(最早打开的) ChildForm oldestChild = openChildForms.Dequeue(); oldestChild.Close(); // 关闭窗口,这会触发其FormClosed事件,我们会在事件中移除它(见下面),所以这里我们不需要在队列中移除,因为关闭事件会处理 } ChildForm childForm = new ChildForm(); childForm.FormClosed += (s, args) => { // 从队列中移除该窗口(注意:由于队列中保存的是引用,我们需要遍历队列来移除,但这样效率不高) // 因此,我们可能需要改变策略:在关闭窗口时,我们不需要从队列中移除,因为我们在打开时已经控制了数量,而且关闭时队列中的引用会被自动丢弃(因为窗口关闭后,我们不再使用它) // 但是,如果我们使用队列,并且希望关闭窗口时从队列中移除,那么我们需要记录每个窗口在队列中的位置,这比较复杂。 // 所以,我们换一种思路:我们并不需要真正从队列中移除,因为当窗口关闭时,我们只需要在队列中保留当前打开窗口。但是,由于我们使用队列来控制数量,而关闭事件发生时,该窗口已经被关闭,我们只需要在队列中移除它?但是,我们怎么知道它在队列中的位置呢? // 因此,我们可能需要重新设计:不使用队列,而是使用List,并在关闭事件中从List中移除。然后,当需要关闭最早窗口时,我们取List的第一个元素(注意:我们打开时按顺序加入List,所以第一个就是最早打开的)。 } // 为了避免上述问题,我们使用List来保存窗口引用,并实现关闭时移除。 } } ``` 由于使用队列在移除时比较麻烦,我们改用List: ```csharp public partial class ParentForm : Form { private List<ChildForm> openChildForms = new List<ChildForm>(); private const int MAX_CHILD_WINDOWS = 5; public ParentForm() { InitializeComponent(); } private void btnOpenChild_Click(object sender, EventArgs e) { // 如果达到最大数量,则关闭最早打开窗口(即列表中的第一个) if (openChildForms.Count >= MAX_CHILD_WINDOWS) { ChildForm oldestChild = openChildForms[0]; oldestChild.Close(); // 关闭后,会触发FormClosed事件,我们在事件处理中会从列表中移除 openChildForms.RemoveAt(0); // 注意:这里先移除,然后关闭?不行,因为关闭事件中我们也会移除,这样会移除两次。所以我们应该在关闭事件中移除。 // 所以,我们不在这里移除,而是在关闭事件中移除。因此,我们只需要关闭窗口关闭事件会自动从列表中移除。 } ChildForm childForm = new ChildForm(); childForm.FormClosed += ChildForm_FormClosed; openChildForms.Add(childForm); childForm.Show(); } private void ChildForm_FormClosed(object sender, FormClosedEventArgs e) { ChildForm child = (ChildForm)sender; openChildForms.Remove(child); // 从列表中移除已关闭窗口 } } ``` 但是,上面的代码有一个问题:在达到上限时,我们关闭了最早打开窗口(openChildForms[0]),然后立即创建新窗口并加入列表。但是,关闭窗口的操作是异步的,也就是说,在调用`oldestChild.Close()`之后,窗口不会立即关闭,因此不会立即触发FormClosed事件,也就不会从列表中移除。所以,在关闭窗口后立即将新窗口加入列表,会导致列表中的窗口数量暂时超过最大值(因为我们先关闭了一个,但还没有从列表中移除,然后又加入一个新的)。这虽然不会造成错误,但列表中的数量会暂时超过最大值,然后等到关闭事件触发后才会调整。 另外,我们关闭窗口后,在关闭事件触发之前,该窗口仍然在列表中。所以,我们不应该在调用Close之后立即创建新窗口吗?实际上,我们可以在关闭事件中再打开窗口?但这样逻辑就复杂了。 因此,我们调整思路:在关闭事件中,我们移除窗口,然后当需要打开窗口且达到上限时,我们关闭最早的一个窗口,然后等待这个窗口关闭事件发生后,再打开窗口?这样就需要递归或事件等待,比较复杂。 另一种简单做法:我们不需要在打开窗口关闭一个窗口,而是直接不允许打开。或者,我们可以在关闭事件中检查是否还有空间,如果有,则自动打开之前被阻止的窗口?这需要额外的队列来保存等待打开窗口请求。 考虑到复杂性,我们通常的做法是:当达到最大数量时,不允许打开窗口(即第一种方法)。或者,在关闭一个窗口后,允许用户再次尝试打开。 因此,根据实际需求选择: 1. 如果需求是严格限制同时打开的数量,那么使用第一种方法(计数器)简单有效。 2. 如果需求是维持最多不超过MAX个,并且当打开窗口时如果超过就关闭最早的一个,那么我们可以使用第二种方法(List),但要注意上面提到的异步问题。实际上,我们可以在关闭事件中再打开窗口?但这样并不好,因为用户点击一次可能期望立即打开窗口,而关闭事件是异步的,用户可能感觉延迟。 所以,我们采用第一种方法(计数器)来限制同时打开的数量,并在达到上限时提示用户。 但是,如果我们确实需要关闭最早打开窗口打开窗口,我们可以这样做: 在父窗体中: ```csharp public partial class ParentForm : Form { private List<ChildForm> openChildForms = new List<ChildForm>(); private const int MAX_CHILD_WINDOWS = 5; public ParentForm() { InitializeComponent(); } private void btnOpenChild_Click(object sender, EventArgs e) { // 如果已经达到最大数量,则关闭最早打开的一个窗口 if (openChildForms.Count >= MAX_CHILD_WINDOWS) { // 关闭最早打开窗口(即列表中的第一个) ChildForm oldest = openChildForms[0]; // 注意:这里我们直接关闭关闭事件会处理从列表中移除 oldest.Close(); } // 创建新窗口 ChildForm child = new ChildForm(); child.FormClosed += (s, args) => { // 从列表中移除 openChildForms.Remove(child); }; openChildForms.Add(child); child.Show(); } } ``` 注意:这里在关闭窗口时,我们并没有等待它完全关闭,而是立即创建新窗口。这样,在关闭窗口的同时,我们创建了新窗口,并加入列表。此时,列表中的窗口数量在关闭事件发生前会超过最大值(因为我们先关闭了一个,但还没有从列表中移除,然后加入了一个新的,所以数量是MAX+1)。然后,当旧窗口关闭事件发生时,会从列表中移除,数量又变为MAX。这样,我们同时打开窗口数量最多为MAX(在旧窗口关闭后)或MAX+1(在旧窗口关闭前和新窗口打开后这段时间)。但用户看到的是,新窗口立即打开了,而旧窗口正在关闭(或已经关闭)。这通常是可以接受的,因为关闭窗口是很快的。 但是,严格来说,同时打开的数量可能会短暂超过MAX。如果我们要求严格不超过,那么我们需要在关闭事件发生后再打开窗口。这可以通过在关闭事件中触发打开窗口的动作来实现,但这样需要维护一个等待打开的队列。 总结: 根据需求,如果要求严格不超过MAX,并且允许用户等待,那么我们可以使用一个队列来保存等待打开窗口请求。在关闭事件发生时,检查队列并打开下一个窗口。但这样实现较为复杂。 因此,我们提供两种常见方案: 方案一:限制同时打开的数量,超过则提示(简单,常用)。 方案二:自动关闭最早打开窗口,然后打开窗口(注意可能短暂超过限制)。 这里我们选择方案一作为主要实现,因为它更符合常规需求,且不会导致窗口数量超过限制。 回到方案一(计数器)的实现,并注意:如果窗口可能被用户独立关闭,也可能通过程序关闭(如父窗口关闭关闭所有窗口),我们需要确保在窗口关闭时都能减少计数。上面的代码通过订阅FormClosed事件已经处理了。 另外,如果父窗口关闭,那么所有窗口也会关闭。但是,我们订阅了窗口的FormClosed事件,当父窗口关闭时,窗口关闭会触发FormClosed事件,从而减少计数。但是,父窗口关闭时,窗口关闭事件先触发,然后父窗口关闭。所以,计数器的减少是正常的。 但是,有一个细节:如果父窗口关闭,那么窗口的FormClosed事件处理函数中会访问父窗体的成员(如计数器),而父窗体可能已经被释放?实际上,在父窗体关闭过程中,窗体的FormClosed事件先触发,此时父窗体还没有完全关闭,所以是安全的。然后父窗体关闭时,会释放资源,包括窗体。所以,我们不需要特别处理。 但是,为了避免潜在的问题,我们可以在父窗体的FormClosing事件中取消所有窗口事件订阅?但通常没有必要。 因此,我们采用方案一(计数器)的代码作为最终实现。 另外,引用[1]中提到窗体吸附、多窗口协作等高级技术,但我们的需求主要是限制数量,所以没有使用这些技术。引用[2]中提到了按钮控件,我们这里使用按钮来打开窗口。引用[3]中提到了文件管理系统,与我们的需求无关。 所以,我们给出以下代码作为解决方案: ```csharp public partial class ParentForm : Form { private int openChildCount = 0; private const int MAX_CHILD_WINDOWS = 5; // 最大同时打开窗口数量 public ParentForm() { InitializeComponent(); } private void btnOpenChild_Click(object sender, EventArgs e) { // 检查是否达到最大数量 if (openChildCount >= MAX_CHILD_WINDOWS) { MessageBox.Show($"同时打开窗口不能超过{MAX_CHILD_WINDOWS}个", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } // 创建窗口 ChildForm childForm = new ChildForm(); // 订阅窗口关闭事件 childForm.FormClosed += (s, args) => { openChildCount--; // 窗口关闭时减少计数 }; childForm.Show(); // 显示窗口 openChildCount++; // 增加计数 } } ``` 注意:这里我们使用`FormClosed`事件而不是`FormClosing`事件,因为`FormClosing`发生在关闭过程中,还可以取消关闭,而`FormClosed`是已经关闭后的事件,此时窗口已经关闭,计数减少是安全的。 此外,如果窗口以模态对话框方式打开(使用ShowDialog),则上述方法不适用,因为ShowDialog会阻塞代码,而且关闭事件的处理方式也不同。但通常,我们限制的是非模态窗口。如果窗口是模态对话框,那么用户必须关闭当前对话框才能操作父窗口,所以通常不会同时打开多个模态对话框(除非是多个非阻塞的窗口,但模态对话框是阻塞的)。所以,我们这里假设窗口是非模态的(使用Show方法打开)。 相关问题: 1. 在C# WinForms中,如何确保窗口关闭时能够正确更新父窗口的计数器? 2. 除了使用计数器,还有哪些方法可以限制同时打开窗口数量? 3. 在C# WinForms中,模态对话框(ShowDialog)和非模态对话框(Show)在限制打开数量时有什么不同?</think>### C# WinForms 限制同时打开窗口数量实现方法 在C# WinForms应用程序中限制同时打开窗口数量,可以通过以下步骤实现: #### 方法一:使用计数器跟踪窗口数量(推荐) ```csharp public partial class MainForm : Form { private int openChildCount = 0; private const int MAX_CHILD_WINDOWS = 3; // 最大允许打开窗口数 public MainForm() { InitializeComponent(); } private void btnOpenChild_Click(object sender, EventArgs e) { // 检查当前打开窗口数量 if (openChildCount >= MAX_CHILD_WINDOWS) { MessageBox.Show($"已达到最大打开窗口数({MAX_CHILD_WINDOWS})", "操作限制", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } // 创建窗口实例 ChildForm childForm = new ChildForm(); childForm.FormClosed += (s, args) => { openChildCount--; // 窗口关闭时减少计数 }; childForm.Show(); openChildCount++; // 增加打开窗口计数 } } ``` #### 方法二:使用列表管理窗口实例 ```csharp public partial class MainForm : Form { private List<ChildForm> openChildren = new List<ChildForm>(); private const int MAX_CHILD_WINDOWS = 3; private void btnOpenChild_Click(object sender, EventArgs e) { // 移除已关闭窗口 openChildren.RemoveAll(form => form.IsDisposed); if (openChildren.Count >= MAX_CHILD_WINDOWS) { MessageBox.Show($"无法打开更多窗口 (最大限制: {MAX_CHILD_WINDOWS})"); return; } ChildForm child = new ChildForm(); child.FormClosed += (s, args) => { // 窗口关闭时自动从列表中移除 openChildren.Remove(child); }; openChildren.Add(child); child.Show(); } } ``` #### 关键实现要点: 1. **计数机制**:使用计数器变量或列表跟踪当前打开窗口数量 2. **窗口限制检查**:在打开窗口前检查当前计数是否达到上限 3. **事件处理**:订阅窗口的`FormClosed`事件,在窗口关闭时更新计数 4. **最大数量设置**:通过常量定义最大允许打开窗口数 5. **用户反馈**:达到限制时显示提示信息(如MessageBox) #### 高级优化技巧: 1. **自动关闭最早窗口**(当需要强制限制数量时): ```csharp if (openChildren.Count >= MAX_CHILD_WINDOWS) { var oldest = openChildren.First(); oldest.Close(); openChildren.Remove(oldest); } ``` 2. **使用MDI容器**(多文档界面): ```csharp // 设置主窗体为MDI容器 this.IsMdiContainer = true; // 在窗体中设置 childForm.MdiParent = this; ``` 3. **窗口激活控制**: ```csharp // 只允许激活已存在的窗口 var existing = openChildren.FirstOrDefault(f => f.WindowState != FormWindowState.Minimized); if (existing != null) { existing.Activate(); return; } ``` #### 注意事项: 1. **线程安全**:如果涉及多线程操作窗口,需使用`Invoke`方法 2. **窗口状态**:最小化窗口仍视为打开状态,需显式处理 3. **资源释放**:确保关闭窗口时正确释放资源 4. **用户体验**:建议在状态栏显示当前打开窗口数 > 以上方法适用于WinForms中的非模态窗口管理,模态对话框(`ShowDialog()`)由于会阻塞主线程,通常不需要数量限制[^1][^2]。实际应用中可根据需求选择简单计数器或更精确的窗口列表管理方式。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值