小菜最近遇到了一个问题:就是弄了个按钮,点击按钮弹出一个窗体,但是,每点击一次,生成一个窗体,这样就产生了好多同样的窗体。小菜的预期是:点击按钮显示窗体,再次点击的时候,还是显示那个窗体,如果最小化了,那就还原显示出来;如果点击X号关闭了,那就重新弹出一个新的窗体。
需求:以两个窗体为例:窗体1设为主窗体(main函数里面创建的),在窗体1中设一个按钮,点击按钮弹出窗体2,然后再次点击按钮,依旧是显示那个窗体2,而不是生成一堆的窗体2,并且窗体2显示在最前面;如果最小化了,点击窗体1中的按钮,那个窗体2就会还原,如果选择关闭窗体2,点击按钮就会重新弹出窗体2。
首先,基本操作:窗体1中弹出窗体2,核心代码如下:
private void btnShowForm2_Click(object sender, EventArgs e)
{
nextForm2 fm2 = new nextForm2();
fm2.Show();
}
这样窗体1中的按钮就能弹出窗体2了,但是
一、问题来了
只要点击按钮,窗体2就会无限制的弹出:
小菜希望是:只显示唯一的窗体2*
分析一下,就是每点击一次按钮,都会创建一个新的窗体2对象,这样就会有很多个窗体2被Show出来。
小菜就想了,试着去只用一个窗体2对象,于是选择将创建窗体2对象的语句拿到外面来。
nextForm2 fm2 = new nextForm2();
private void btnShowForm2_Click(object sender, EventArgs e)
{
//nextForm2 fm2 = new nextForm2();
fm2.Show();
}
这样确实点击按钮只显示唯一窗体了,但是
二、又有新问题了
窗体2并没有随着按钮的点击而显示在最前面。当把窗体2对象的创建写在事件外面,而且是再次点击按钮弹出窗体2时,窗体2未显示在最前面,这时小菜想到了焦点问题:
小菜试着设置一下窗体2的焦点。
曾尝试直接在窗体2里面操作:未成功
private void nextForm2_Load(object sender, EventArgs e)
{
//this.Focus();//未成功
}
private void nextForm2_Shown(object sender, EventArgs e)
{
//this.Focus();//未成功
}
于是选择在窗体1里面操作
private void btnShowForm2_Click(object sender, EventArgs e)
{
//nextForm2 fm2 = new nextForm2();
fm2.Show();
fm2.Focus();
}
这样完成了窗体2随着按钮的点击显示到了最前面。但是
三、新问题来了
窗体2一旦最小化,就不能再还原了,小菜意识到,即使弄好焦点,过了显示在最前面那一关,还有最小化、最大化等问题在等待解决。
为啥会有这个想法呢,小菜曾见过,某电脑软件,如某管家,点击某某管理按钮,就会进入新窗口,单击最小化之后再点击某某管理按钮依旧会进入那个界面。对于关闭,也是这样,也能再次弹出该页面。而且小菜还发现,窗体2的位置也固定,大小也固定,感觉位置和窗体1一样,感觉在屏幕中间呢。。。。
**接下来是将已经最小化的窗体2再次弹出来,小菜尝试一下哈:
**
public partial class mainForm : Form
{
public mainForm()
{
InitializeComponent();
}
nextForm2 fm2 = new nextForm2();
private void btnShowForm2_Click(object sender, EventArgs e)
{
//nextForm2 fm2 = new nextForm2();
fm2.Show();
fm2.Focus();
//fm2.Activate();
//当窗体最大化或最小化之后能够还原
fm2.WindowState = FormWindowState.Normal;
//fm2.Focus();
}
}
这样,窗体最小化或最大化之后,点击窗体1中的按钮,窗体2能够还原,但是呢,
四、问题依然有
***关闭窗体2之后,点击按钮,报错“无法访问已经释放的对象”***用了系统提供的属性: fm2.WindowState = FormWindowState.Normal; 解决了最大化、最小化问题之后,小菜又遇关闭问题。
那么,进行到这里,看来需要加个判断了,毕竟窗体2关闭的时候,连带着依托它所创建的对象也被释放掉了。小菜尝试一下哈:
nextForm2 fm2 = new nextForm2();
private void btnShowForm2_Click(object sender, EventArgs e)
{
if (fm2 !=null)
{
fm2.Show();
fm2.Focus();
fm2.WindowState = FormWindowState.Normal;
}
}
但是,还是那个问题:
无法访问已释放的对象
此刻,网络搜索求助,发现与C#垃圾回收机制有关,特别感谢那篇文章:
C# WinForm:无法访问已释放的对象
采用它判断窗体对象是否释放的方式当然可以,即:
nextForm2 fm2 = new nextForm2();
private void btnShowForm2_Click(object sender, EventArgs e)
{
if (fm2 != null)
{
if (fm2.IsDisposed)
{
fm2 = new nextForm2();
fm2.Show();
fm2.Focus();
fm2.WindowState = FormWindowState.Normal;
}
else//如果窗体2没有被释放掉
{
fm2.Show();
fm2.Focus();
fm2.WindowState = FormWindowState.Normal;
}
}
}
到此,问题基本解决。
其实,对于问题三、问题四,小菜想到了另一种办法:
利用静态资源共享这一特性,新建静态类,在它里面声明一个窗体2类型的静态字段,让它来保存依托于窗体2而创建的对象。
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace _03_点击按钮显示唯一窗体2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//Form2 fm2 = new Form2();
private void btnShowForm2_Click(object sender, EventArgs e)
{
//判断窗体2对象是否存在,存在则删除旧对象
if (Test.fm2 != null)
{
Test.fm2.Close();
}
//这样每次点击按钮,窗体2对象都被刷新过
Form2 fm2 = new Form2();
fm2.Show();
}
}
}
amespace _03_点击按钮显示唯一窗体2
{
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
/// <summary>
/// 当窗体2加载的时候,保存窗体2对象
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Form2_Load(object sender, EventArgs e)
{
Test.fm2 = this;
}
}
public static class Test
{
public static Form2 fm2;
}
}
这样做的话,就是在按钮产生效果(窗体2弹出)之前,如果窗体2 对象存在就关闭,然后弄一个新的窗体2对象,就实现了点击按钮显示唯一窗体。
还有,窗体1和窗体2的位置也想设置一下,直接在窗体属性面板中设置,或者在合适的位置写控制窗体位置的代码
还有一点,在设置窗体第一次出现时的位置的时候,小菜曾经尝试直接用代码操控,但是未起作用。回顾自己的操作,Load、Shown事件里面都用过,然而看从窗体属性面板设置好之后的Form1.Designer.cs里面的代码,看到控制窗体位置的代码是在Load和Show之前的,那小菜萌生一种念头,再试一次,这次自己写在Load和Show的前面。结果可行:窗体1与窗体2都显示在了屏幕中间。
public Form1()
{
InitializeComponent();
//设置窗体1出现在屏幕中间,这个写在Load、Shown里面皆不起作用
this.StartPosition = FormStartPosition.CenterScreen;
}
private void btnShowForm2_Click(object sender, EventArgs e)
{
//判断窗体2对象是否存在,存在则删除旧对象
if (Test.fm2 != null)
{
Test.fm2.Close();
}
//这样每次点击按钮,窗体2对象都被刷新过
Form2 fm2 = new Form2();
fm2.StartPosition = FormStartPosition.CenterScreen;
fm2.Show();
//当前只能在窗体属性面板中设置了--不,要写在Show之前。
//fm2.StartPosition = FormStartPosition.CenterScreen;
}
至此,小菜的任务完成。
时隔几天后,小菜忽然想:试试模式对话框怎么样,但是结果显然是不理想的:
/// <summary>
/// 测试,采用模式对话框弹出窗体也能显示唯一窗体
/// 虽然满足显示唯一、满足关闭后能打开(当然打开的新new的,不再是原来的
/// 但从视觉上看没啥变化)
/// 但不能满足小菜的预期:
/// 最小化连带着主窗体一起最小化;最大化之后想去主窗体,必须先关闭了
/// 子窗体才能操作主窗体(这里就深深地感受到了模式对话框的局限性)。
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnShowForm3_Click(object sender, EventArgs e)
{
Form fm3 = new Form();
fm3.ShowDialog();
}
这正如小菜在注释里面写的那样:尽管使用模式对话框弹出窗体也实现显示唯一窗体,但这里是不合要求的:
虽然满足显示唯一、满足关闭后能打开(当然打开的新new的,不再是原来的,但从视觉上看没啥变化) 等需求,但是–不能满足小菜的预期:
-
子窗体最小化连带着主窗体一起最小化;其最大化之后想去主窗体操作,必须先关闭了这个模式子窗体
-
只有子窗体关闭了,才能操作主窗体(这里就深深地感受到了模式对话框的局限性)。
再会,小菜。