20

There are tons of questions on StackOverflow asking how to hide Form1 and show Form2. And, usually, a few different answers crop up:

1)

// Program.cs
Application.Run(new Form1());
// Form1.cs
Form2 form2 = new Form2();
form2.Show();
this.Hide();

2)

// Program.cs
Form1 form1 = new Form1();
Form2 form2 = new Form2();
form1.Show();
form2.Show();
Application.Run();

...etc..

I'm not looking for a simple disposable solution like #1. I'm looking for best form management practices. An application with 5-8 forms, opening and closing one another frequently - what's the best way to manage these forms?

My idea was to make each form a (lazy?) Singleton and bury them in a FormsManager class of some sort (like solution #2 but ++). And then individual forms might call something like FormsManager.GetForm<WelcomeDialog>().

But I was wondering what people with more experience used. Again, these solutions shouldn't be quick hacks. They should be design-oriented, maybe architectural, and long-term solutions.

Edits:

This is a pretty generic question (so the requirements are pretty open) for anybody who might have the same trouble. Specific to my situation though, I don't need multiple forms shown at startup. Also, I have no MDI forms. I may have a few modal forms, but they are mostly non-modal.

Jason
  • 6,878
  • 5
  • 41
  • 55
  • What are the requirements? You want multiple forms to be shown at startup? Once the application is started, what is the form show/hide strategy you want (modal forms, multiple forms, MDI forms...)? – ken2k Feb 27 '12 at 09:32
  • So, I don't need multiple forms shown at startup. Once the application is started, the form show/hide strategy is simply multiple forms (non-MDI). There may be one or two modal forms. – Jason Feb 27 '12 at 09:33

5 Answers5

13

In anything other than the most straightforward scenario -- a single main form running for the lifetime of the application, with short-lived child forms -- it is recommended to create a class that inherits from ApplicationContext. It isn't that complicated:

class FormManager : ApplicationContext {
    //When each form closes, close the application if no other open forms
    private void onFormClosed(object sender, EventArgs e) {
        if (Application.OpenForms.Count == 0) {
            ExitThread();
        }
    }

    //Any form which might be the last open form in the application should be created with this
    public T CreateForm<T>() where T : Form, new() {
        var ret = new T();
        ret.FormClosed += onFormClosed;
        return ret;
    }

    //I'm using Lazy here, because an exception is thrown if any Forms have been
    //created before calling Application.SetCompatibleTextRenderingDefault(false)
    //in the Program class
    private static Lazy<FormManager> _current = new Lazy<FormManager>();
    public static FormManager Current => _current.Value;

    //Startup forms should be created and shown in the constructor
    public FormManager() {
        var f1 = CreateForm<Form1>();
        f1.Show();
        var f2 = CreateForm<Form2>();
        f2.ShowDialog();
    }
}

and Application.Run in Program.cs can use the static instance of FormManager:

static class Program {
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main() {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(FormManager.Current);
    }
}

During the lifetime of the application, new forms should be created via CreateForm, in order to register the onFormClosed method with the FormClosed event:

var f3 = FormManager.Current.CreateForm<Form3>();
f3.Show();
var f4 = FormManager.Current.CreateForm<Form4>();
f4.ShowDialog();

If you prefer new Form3(); over calls to FormManager.CreateForm, you can create a RegisterForm method on FormManager:

public void RegisterForm(Form frm) {
    frm.FormClosed += onFormClosed;
}

and call RegisterForm on each new Form:

var f3 = new Form3();
FormManager.Current.RegisterForm(f3);
var f4 = new Form4();
FormManager.Current.RegisterForm(f4);

(NB. If all your forms inherit from some base class, then instead of manually calling RegisterForm for each new instance, you could call it in the base class constructor.)


Note that Application.OpenForms only returns those forms that are currently visible. If the application shouldn't exit as long as there are still hidden forms open, then FormManager will have to use some collection to keep track of all the forms. That collection will determine whether to quit the application or not.

class FormManager : ApplicationContext {
    private List<Form> forms = new List<Form>();

    private void onFormClosed(object sender, EventArgs e) {
        forms.Remove((Form)sender);
        if (!forms.Any()) {
            ExitThread();
        }
    }

    public void RegisterForm(Form frm) {
        frm.FormClosed += onFormClosed;
        forms.Add(frm);
    }

    public T CreateForm<T>() where T : Form, new() {
        var ret = new T();
        RegisterForm(ret);
        return ret;
    }

    private static Lazy<FormManager> _current = new Lazy<FormManager>();
    public static FormManager Current => _current.Value;
}
Zev Spitz
  • 13,950
  • 6
  • 64
  • 136
6

I'm answering in a general manner here.

I don't think a singleton pattern would fit well with form management. Generally, you want to pass some context parameter to the form, and you might want to open multiple instances of the same form. So a singleton doesn't fit well IMO.

I think form management should be simple.

For instance, if you want to display a modal form from another form, I would write something really straightforward:

private void button1_Click(object sender, EventArgs e)
{
    using (ModalForm1 frm = new ModalForm1(myParam))
    {
        frm.ShowDialog();

        if (frm.MyResultProperty == ...)
        {
            // Do some job here
        }
    }
}

Of course you could write some interface/generics syntax to avoid a little code duplication in case you want to display a lot of modal forms:

public interface IFormResult<T>
{
    T Result { get; set; }
}

public class ModalForm1 : Form, IFormResult<string>
{
    public ModalForm1()
    {
        InitializeComponent();

        this.Result = "My result";
    }

    public string Result { get; set; }
}

private void button1_Click(object sender, EventArgs e)
{
    string res = ShowModalForm<ModalForm1, string>();
}

private static T2 ShowModalForm<T1, T2>()
    where T1 : Form, IFormResult<T2>, new()
{
    using (T1 form = new T1())
    {
        form.ShowDialog();

        return form.Result;
    }
}

But honestly, I feel like it's a bit overingeneered.

Second point: if your form doesn't exactly follows this specific behavior (ShowDialog() then a Result property is set), then you must write another Interface...etc.

If this type of syntax (generics, interfaces...etc.) doesn't reduce the number of lines of code written OR the complexity OR the maintainability (and obviously we can't say it's really the case here), then it's pretty useless IMO.


Edit:

Form management really depends on your use case.

  • If you have say 20 forms that can be displayed at the same time, then you should think of a FormManager concept (or better: think about how to improve the user experience by reducing the number for possible opened forms)
  • If you have something relatively simple (2-3 modeless forms at the same time + 3-4 possible modal forms), I wouldn't write complex code to manage those forms.

Generally, the form that is used to start the application (i.e. the form that stops the program when closed, which is the form that is a parameter of Application.Run()) is responsible of other forms. You have one main form, and multiples child forms. If your case is really different, then there is probably something smarter to write, but it'll depend on your case. I don't think one can provide a general good answer to the general problematic of form management.

Honestly, if you want something really maintainable, try to reduce (as much as possible) the number of forms that can be shown at the same time. Multiple displayed modeless forms at the same time doesn't offer a good user experience in most cases, and form lifetime management can be problematic if forms are dependent on each other.

ken2k
  • 48,145
  • 10
  • 116
  • 176
  • Perhaps I've got the form responsibilities wrong, thinking that there should be some central controller. Instead, they should manage themselves? I was thinking of, via Reflection, discovering all Form classes and adding them to a typemap Dictionary and calling Get() or something. The maintainability problem I'm trying to solve is writing spaghetti form code, in which I have to hop from class to class just to remember which form opens which form. – Jason Feb 27 '12 at 10:23
  • Note that WinForms has a built-in base class for the general problem of form management: [`ApplicationContext`](https://msdn.microsoft.com/en-us/library/system.windows.forms.applicationcontext(v=vs.110).aspx) – Zev Spitz Dec 19 '16 at 07:52
3

I use this trick. Lets say form1 is the main form:

private void button1_Click(object sender, EventArgs e)
{
     LoadForm(new Form2());
}

private void LoadForm(Form frm)
{
    frm.FormClosed += new FormClosedEventHandler(frm_FormClosed);
    this.Hide();
    // Here you can set a bunch of properties, apply skins, save logs...
    // before you show any form
    frm.Show();
}

void frm_FormClosed(object sender, FormClosedEventArgs e)
{
    this.Show();
}

Therefore; when you open any form closed (except form1), form1 will reappear again.

Update

using (Form2 frm = new Form2())
{
    if (frm.ShowDialog() = DialogResult.ok)
    {
        //Do some things...
    }
}

In this case there is no need to hide previous form.

Amen Ayach
  • 4,288
  • 1
  • 23
  • 23
  • 1
    Hmm, this seems a bit difficult to maintain, especially with 5 forms each calling different forms of each other. – Jason Feb 27 '12 at 09:42
  • No, no need to do this in all other forms, other form could send messages, dialogresult, data to mainform that can handles to load right forms, unless "Showdialog" : see update – Amen Ayach Feb 27 '12 at 09:47
  • I don't understand what you mean. It seems like your code re-opens the hidden Form1 once Form2, Form3, or whichever Form is closed. But that's limiting. Form1 may need to open Form5. Form5 may need to open Form3. Form3 may need to open Form7. I don't believe your solution is very extensible. – Jason Feb 27 '12 at 09:51
  • 1
    I use Form2 in my code as example brother, you can load any Form: LoadForm(new someForm()); – Amen Ayach Feb 27 '12 at 09:53
  • Would you use your solution in an enterprise application? – Jason Feb 27 '12 at 09:59
  • With many additional stuff: Form enums, skin, user right, user trace...code will be in special layer, special classes...i just give you an example – Amen Ayach Feb 27 '12 at 10:04
1

Depending on the size of your application, Id say have a look at the Microsoft Enterprise library and specifically the CAB block.

That should give you a good start.

Steoates
  • 3,058
  • 5
  • 27
  • 43
-1
public partial class Form1 : Form
{
    private static Form1 inst;
    public static Form1 GetForm
    {
        get
        {
            if (inst == null || inst.IsDisposed)
            {
                inst = new Form1();
            }
            return inst;
        }
    }
    public Form1()
    {
        InitializeComponent();
        inst = this;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        Form2.GetForm.Show();
        this.Hide();
    }
}

public partial class Form2 : Form
{
    private static Form2 inst;
    public static Form2 GetForm
    {
        get
        {
            if (inst == null || inst.IsDisposed)
                inst = new Form2();
            return inst;
        }
    }
    public Form2()
    {
        InitializeComponent();
    }
    private void button1_Click(object sender, EventArgs e)
    {
        Form1.GetForm.Show();
        this.Hide();
    }
    private void Form2_FormClosed(object sender, FormClosedEventArgs e)
    {
        Form1.GetForm.Show();
    }
}

if you have more than two form then create another form as From2

Jagdish
  • 129
  • 1
  • 8