C# 如何避免在.NET Windows窗体中创建重复的窗体?

C# 如何避免在.NET Windows窗体中创建重复的窗体?,c#,.net,winforms,menu,C#,.net,Winforms,Menu,我正在使用.NET Windows窗体。我的MDI父窗体包含菜单。如果单击菜单,将显示表单。到目前为止没有问题 UserForm uf = new UserForm(); uf.Show(); uf.MdiParent = this; 如果我再次单击菜单,将创建表单的另一个副本。如何解决此问题?您可以将表单设置为单例: public class MyForm : Form { private MyForm _instance = null; private object _lo

我正在使用.NET Windows窗体。我的MDI父窗体包含菜单。如果单击菜单,将显示表单。到目前为止没有问题

UserForm uf = new UserForm();
uf.Show();
uf.MdiParent = this;

如果我再次单击菜单,将创建表单的另一个副本。如何解决此问题?

您可以将表单设置为单例:

public class MyForm : Form
{
    private MyForm _instance = null;
    private object _lock = new object();

    private MyForm() { }


    public static MyForm Instance
    {
        get
        {
            if (_instance == null)
            {
                lock (_lock)
                {
                    if (_instance == null)
                    _instance = new MyForm();
                }
            }
            return _instance;
        }
    }
}
然后,您的呼叫将类似于:

MyForm.Instance.Show();
MyForm.Instance.MdiParent = this;
您应该创建一个单例类来管理表单实例:

public class FormProvider
{
   public static UserForm UserForm
   {
       get
       {
          if (_userForm== null || _userForm.IsDisposed)
          {
            _userForm= new UserForm ();
          }
          return _userForm;
       }
   }
   private static UserForm _userForm;
}
注意,这是一个非常简单的单例模式。要获得正确的模式使用方法,请使用此选项

然后,您可以按如下方式访问表单:

FormProvider.UserForm.Show();
FormProvider.UserForm.MdiParent = this;
首次访问
FormProvider.UserForm
时,将创建它。
FormProvider.UserForm
属性上的任何后续get都将返回第一次访问时创建的表单。这意味着表单将只创建一次。

选项:

  • UserForm
    设置为单例:
  • 单击按钮或菜单项时禁用它:
    menuItem.Enabled=false
通常,禁用按钮可以正常工作,并且从用户的角度来看更有意义。如果您需要为其他内容启用按钮,Singleton可以工作


如果表单可以关闭,并且以后需要一个新实例,那么单例可能不是一个好的解决方案。

与这里现有的答案不同,我不建议为此使用单例。单例模式被过度使用,通常是一种“代码气味”,表明您的总体设计出了问题。单例变量通常与全局变量放在同一个“桶”中:您最好有一个真正有力的理由来使用它

最简单的解决方案是在主窗体上创建一个实例变量来表示所讨论的窗体,然后使用它来显示它

public class MainMdiForm : Form
{
    ...

    UserForm userForm;

    ...

    private void ShowUserForm()
    {
        if(userForm == null || userForm.IsDisposed)
        {
            userForm = new UserForm();
            userForm.MdiParent = this;
        }

        userForm.Show();
        userForm.BringToFront();
    }
}

您可以只检查宿主窗体的MdiChildren属性,以确定其中是否存在UserForm的实例

UserForm myForm = null;
foreach (Form existingForm in this.MdiChildren)
{
    myForm = existingForm as UserForm;
    if (myForm != null)
        break;
}

if (myForm == null)
{
    myForm = new UserForm();
    myForm.MdiParent = this;

    myForm.Show();
}
else
    myForm.Activate();

这将为您的UserForm创建一个新实例,如果它不存在,它将切换到创建的实例(如果它确实存在)。

最干净的方法是简单地跟踪表单实例的生存期。为此,请订阅FormClosed事件。例如:

    private UserForm userFormInstance;

    private void showUserForm_Click(object sender, EventArgs e) {
        if (userFormInstance != null) {
            userFormInstance.WindowState = FormWindowState.Normal;
            userFormInstance.Focus();
        }
        else {
            userFormInstance = new UserForm();
            userFormInstance.MdiParent = this;
            userFormInstance.FormClosed += (o, ea) => userFormInstance = null;
            userFormInstance.Show();
        }
    }

如果您知道表格的名称:

    if (Application.OpenForms["FormName"] == null)
       {
           Form form = new Form();
           form.MdiParent = this;
           form.Show();
       }
       else
           Application.OpenForms["FormName"].Focus(); 

这是我在ShowForm()中的解决方案,并在aboutToolStripMenuItem\u Click()中调用示例:


修正了你的代码。您没有返回任何内容,也没有添加get访问器。@GenericTypeTea-Heh。在一次消防演习中被卡住了,无法编辑代码。谢谢@Adam-singleton模式有不正确的用法。一、 但是,请相信这是正确使用单例的一个特殊实例。您所做的实际上与singleton中发生的事情是一样的,只是您最终会得到messier代码。@泛型:仅创建一个实例并不是作为singleton的对象的同义词,也不是表示singleton模式是合适的。@Adam-好的,所以从等式中去掉singleton这个词。我的示例仍然更易于管理,与您提供的示例相同,只是我的表单变量是静态的,而您的表单变量包含在另一个表单中。我们的两个例子都非常有效,我只是碰巧认为我的更干净。@Generic:虽然我不同意你的更容易管理(你现在有另一个你必须管理的类型,它的逻辑——特定于另一个类型——在另一个位置),从功能和解决问题的角度来看,它确实是有效的。我只是争辩说这不是“正确”的方式,不管它值多少钱。@Adam-我想理解你反对它的理由(我和其他很多人一样,是为了学习,我只在我认为我知道正确答案时才提供答案)。如果我不应该推荐这种方法,事实上,为什么不呢?在表单实例周围添加一个
lock
,并没有增加任何安全性,只是增加了一种错觉。只允许从创建表单的线程访问表单,并且线程必须是STA线程才能创建表单。添加同步可能比不添加更危险,因为它给用户留下的印象是,您实际上正在做必要的工作,以使多线程兼容。此外,这不是一个需要单例的场景。最后,如果窗体关闭,这将导致错误,因为您将访问已处理的对象。在使用静态窗体时,请特别注意内存泄漏。签出以了解如何处理窗体的事件。@Adam-如果窗体关闭,这不会导致错误。我有2/3的紧凑框架应用程序,从来没有出现过问题。不过,我同意您关于线程安全的看法,所以我删除了该代码段。@泛型:我不确定compact framework与此特定场景有何关系,但在窗体上调用
Show
(在winforms中,如问题所涉及)已经关闭的将导致
ObjectDisposedException
@Adam-更新我的代码片段以考虑ObjectDisposedException,谢谢。我将保持按钮处于启用状态,因为(在我见过的大多数应用程序中)将显示表单,或者如果表单已经打开,则将其带到前面。
    private void ShowForm(Type typeofForm, string sCaption)
    {
        Form fOpen = GetOpenForm(typeofForm);
        Form fNew = fOpen;
        if (fNew == null)
            fNew = (Form)CreateNewInstanceOfType(typeofForm);
        else
            if (fNew.IsDisposed)
                fNew = (Form)CreateNewInstanceOfType(typeofForm);

        if (fOpen == null)
        {
            fNew.Text = sCaption;
            fNew.ControlBox = true;
            fNew.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
            fNew.MaximizeBox = false;
            fNew.MinimizeBox = false;
            // for MdiParent
            //if (f1.MdiParent == null)
            //    f1.MdiParent = CProject.mFMain;
            fNew.StartPosition = FormStartPosition.Manual;
            fNew.Left = 0;
            fNew.Top = 0;
            ShowMsg("Ready");
        }
        fNew.Show();
        fNew.Focus();
    }
    private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
    {
        ShowForm(typeof(FAboutBox), "About");
    }

    private Form GetOpenForm(Type typeofForm)
    {
        FormCollection fc = Application.OpenForms;
        foreach (Form f1 in fc)
            if (f1.GetType() == typeofForm)
                return f1;

        return null;
    }
    private object CreateNewInstanceOfType(Type typeofAny)
    {
        return Activator.CreateInstance(typeofAny);
    }

    public void ShowMsg(string sMsg)
    {
        lblStatus.Text = sMsg;
        if (lblStatus.ForeColor != SystemColors.ControlText)
            lblStatus.ForeColor = SystemColors.ControlText;
    }
    public void ShowError(string sMsg)
    {
        lblStatus.Text = sMsg;
        if (lblStatus.ForeColor != Color.Red)
            lblStatus.ForeColor = Color.Red;
    }