How to register Windows Forms with Simple Injector

Ideally, you would want to register your forms as Singleton. In my experience, however, this will result in hard to debug errors, especially when you use a BindingSource for binding your data to whatever.

A second problem with using Singleton as the lifestyle is that if your application uses modeless windows, this windows will throw an ObjectDisposedException when opened a second time, because the Windows Forms Application framework will dispose the Form on the first close, while Simple Injector should be in charge of that. So Simple Injector will create one–and exactly one–instance, if registered as Singleton. If somebody else (e.g. your application, the windows forms framework) will dispose the object, it won’t be recreated.

The most easy solution, which is also easy to understand, is to register your forms as Transient. And yes, you need to suppress the diagnostic warnings. The reason for this diagnostic warning according to the documentation:

A component that implements IDisposable would usually need deterministic clean-up but Simple Injector does not implicitly track and dispose components registered with the transient lifestyle.

Simple Injector is unable to dispose a transient component because it is unable to determine when the object should be disposed. This means, however, that forms that are opened in a modal fashion with a call to .ShowDialog() will never be disposed! And because a windows forms application typically runs for a long time, maybe even a week or month, this will eventually result in a ‘Win32Exception’ with a message: “Error Creating Window Handle”. Which essentially means you exhausted all resources of the computer.

Disposing of the forms is therefore important. And although Simple Injector is able to do this job if you would use a Scope, this is with Windows Forms not so easy to implement. So you yourself have to take care of disposing the closed Forms which have been shown using ShowDialog().

Depending on your specific use case there are several ways to implement a FormOpener or NavigationService. One way to do it:

public interface IFormOpener
{
    void ShowModelessForm<TForm>() where TForm : Form;
    DialogResult ShowModalForm<TForm>() where TForm : Form;
}

public class FormOpener : IFormOpener
{
    private readonly Container container;
    private readonly Dictionary<Type, Form> openedForms;

    public FormOpener(Container container)
    {
        this.container = container;
        this.openedForms = new Dictionary<Type, Form>();
    }

    public void ShowModelessForm<TForm>() where TForm : Form
    {
        Form form;
        if (this.openedForms.ContainsKey(typeof(TForm)))
        {
            // a form can be held open in the background, somewhat like 
            // singleton behavior, and reopened/reshown this way
            // when a form is 'closed' using form.Hide()   
            form = this.openedForms[typeof(TForm)];
        }
        else
        {
            form = this.GetForm<TForm>();
            this.openedForms.Add(form.GetType(), form);
            // the form will be closed and disposed when form.Closed is called
            // Remove it from the cached instances so it can be recreated
            form.Closed += (s, e) => this.openedForms.Remove(form.GetType());
        }

        form.Show();
    }

    public DialogResult ShowModalForm<TForm>() where TForm : Form
    {
        using (var form = this.GetForm<TForm>())
        {
            return form.ShowDialog();
        }
    }

    private Form GetForm<TForm>() where TForm : Form
    {
        return this.container.GetInstance<TForm>();
    }
}

This class must be registered as Singleton:

container.RegisterSingleton<IFormOpener, FormOpener>();

And can be used by injecting this service in for example your root form of the application:

public partial class RootForm : Form
{
    private readonly IFormOpener formOpener;

    public RootForm(IFormOpener formOpener)
    {
        this.formOpener = formOpener;
        this.InitializeComponent();
    }

    private void ShowCustomers_Click(object sender, EventArgs e)
    {
        this.formOpener.ShowModelessForm<AllCustomersForm>();
    }

    private void EditCustomer_Click(object sender, EventArgs e)
    {
        var result = this.formOpener.ShowModalForm<EditCustomerForm>();
        // do something with result
    }
}

Leave a Comment