4
votes

I am trying to close a formular when opening a new one. When closing an formular, I want to process some special logic in the closing event. But the closing event is never called, neither in the FormClosing nor in the Closing event, nor in the abstract base class nor in the given manual attached event form_FormClosing.

When I manual close the form by clicking the x all events are fired fine. Invoking the Close() method fails.

Do you have some recmmendations to fix my issue?

MdiParent:

private Form _currentForm;
private void ShowForm<T>() where T : Form
{
    if (_currentForm != null && !_currentForm.IsDisposed)
    {
        _currentForm.Hide();
        _currentForm.Close();
    }

    var form = MdiChildren.FirstOrDefault(f => f.GetType() == typeof(T));
    if (form == null)
    {
        form = _formFactory.CreateForm<T>();
        form.MdiParent = this;
        form.WindowState = FormWindowState.Maximized;
        form.FormClosing += form_FormClosing;
        _currentForm = form;
        MdiBackground.Hide();
        form.Show();
    }
    else
    {
        ActivateMdiChild(form);
        form.Activate();
    }
}

void form_FormClosing(object sender, FormClosingEventArgs e)
{
    // will not be called
}

Abstract generic mdi child form:

public abstract partial class BaseForm<TEntity> : Form where TEntity : class, IEntity
{
    protected override void OnClosing(CancelEventArgs e)
    {
        // wil not be called
        if (EditMode == EditModes.Editable)
        {
            MessageBox.Show(this, "Please commit or abort your changes");
            e.Cancel = true;
        }
        base.OnClosing(e);
    }
 }
4

4 Answers

6
votes

This misbehaves because the native Windows MDI implementation doesn't support hiding MDI child windows. Winforms uses a trick to still support Hide(), it actually destroys the native window and re-creates it when you call Show() again. This has a side-effect however, the Close() call no longer raises the FormClosing/Closed events since the native window was already destroyed by the Hide() call. This is a bug, not uncommon in Winforms.

The workaround is simple, you don't need Hide() when you call Close(), just remove it.

1
votes

Well I kept fighting and found the solution

if (_currentForm != null && !_currentForm.IsDisposed)
{
    // This call prevents calling the closing event -> _currentForm.Hide();
    _currentForm.Close();
}

This is Windows Forms ._.

0
votes

You can try this:

form1.Closing += delegate 
{
  // your logic
};
0
votes

This post has been useful to me also, although my case was slightly different.

In this case avoiding _currentForm.Hide(); works fine because the code does a form-switch. I found that the problem also originates with a MDIChild that has been hidden by a different MDIChild that is on top.

Here's a workaround that works also in that case based on the fact that Dispose is always called.

It can be done preparing something like this:

public abstract class FormExtenderClass : Form{
    private bool formClosingFired = false;
    private bool formClosedFired = false;

    protected override void OnFormClosing(FormClosingEventArgs e) {
        base.OnFormClosing(e);
        formClosingFired = !e.Cancel;
    }

    protected override void OnFormClosed(FormClosedEventArgs e) {
        base.OnFormClosed(e);
        formClosingFired = true;
    }

    protected override void Dispose(bool disposing) {
        if (!formClosingFired) OnFormClosing(new FormClosingEventArgs(CloseReason.UserClosing, false));
        if (!formClosedFired) OnFormClosed(new FormClosedEventArgs(CloseReason.UserClosing));
        base.Dispose(disposing);
    }
}

Then in the code of MDIChildren just change the first row from

public partial class AutoForm : Form {

to

public partial class AutoForm : FormExtenderClass {

Consider that is in any case a worarkound. The main difference is that set e.Cancel=true will have no effect in the case that FormClosing is called from Disposed as backup.