Search code examples
c#winformsstreamwriterformclosing

The FormClosing event seems to run after a different Form is started


I did a lot of research about this problem and tried methods but none of them worked. First I will show you what is in my application and what I want it to be with some pictures.

Picture 1

As you can see in the first picture, I open new childForms using the buttons on the main form. One of them is the form named "Price". This form saves the prices entered by the user to the json file. I do the saving process in the FormClosing event. (Yes i know but I don't want to use the "Save" button.)

Picture 2

As you can see in the second picture, the "Result" form did some mathematical operations by reading the values entered by the user in the "Price" form over json.

Now let's come to the problem, I open the "Price" form, change the values and press the result button. Results are coming, everything is great! But the results are not correct because when the "Result" Form was run it didn't wait for the current values to be saved in json. So it didn't wait for the "Price" form to close (i.e. it didn't wait for the Price.FormClosing event to complete).

To avoid this error, I open the results form after opening a different form, but this is amateurish.

I hope I was able to explain my problem clearly. Thanks in advance for your answers and thoughts.

Code to Write Current Values to JSON File:

private void ProductPricesForm_FormClosing(object sender, FormClosingEventArgs e)
{
    string jsonString = JSONOperations.getItemsAsString(products);
    File.WriteAllText(JSONOperations.productsJSONPath, jsonString);
}

Code to Read JSON File:

private static void getItems()
{
    using (StreamReader r = new StreamReader(JSONOperations.productsJSONPath))
    {
        string json = r.ReadToEnd();
        products = JSONOperations.getItemsAsClass<Product>(json);
    }
}

Form Opening Code:

    private void buttonResult_Click(object sender, EventArgs e)
    {
        openChildForm(new ResultForm());
    }

    private void buttonProductPrices_Click(object sender, EventArgs e)
    {
        openChildForm(new ProductPricesForm());
    }

    private Form activeForm;
    private void openChildForm(Form childForm)
    {
        if (activeForm == null)
        {
            startChildForm(childForm);
        }
        else
        {
            if (String.Equals(activeForm.Name, childForm.Name))
            {
                return;
            }
            else if (!String.Equals(activeForm.Name, childForm.Name))
            {
                activeForm.Close();
                startChildForm(childForm);
            }
        }
    }
    private void startChildForm(Form childForm)
    {
        childForm.TopLevel = false;
        childForm.FormBorderStyle = FormBorderStyle.None;
        childForm.Dock = DockStyle.Fill;
        panelChildForm.Controls.Add(childForm);
        panelChildForm.Tag = childForm;
        childForm.BringToFront();
        childForm.Show();
        activeForm = childForm;
    }

Solution

  • You can simply Hide the dialog instead of close.

    First, create your childs types:

    private enum FormType
    {
        Result = 0,
        Products = 1,
        //...
    }
    

    An a method to create each form:

    private Form CreateChildForm(FormType formType)
    {
        switch (formType)
        {
            case FormType.Result:
                return new ResultForm();
            case FormType.Products:
                return new ProductPricesForm();
            default:
                return null;
        }
    }
    

    Now, in your form, add this fields:

    private int _activeChildIndex = -1;
    
    private readonly Form[] _childsForms = new Form[Enum.GetNames(typeof(FormType)).Length];
    

    _childsForms will store each type of ChildWindow that you create. _activeChildIndex indicates the _childsForms which is active now.

    private void ShowChildWindow(FormType formType)
    {
        var index = (int)formType;
        if (this._activeChildIndex == index)
        {
            return;
        }
    
        if (this._activeChildIndex >= 0)
        {
            var activeChild = this._childsForms[this._activeChildIndex];
            // TODO: Hide form
            this.OnHideForm(this._activeChildIndex);
        }
    
        this._activeChildIndex = index;
    
        if (this._childsForms[index] != null)
        {
            // TODO: Reset to default (or leave with last state, as you prefer)
            // TODO: And show
        }
        else
        {
            // TODO: Create child form
            var childForm = this.CreateChildForm(formType);
            if (childForm != null)
            { 
                this._childsForms[index] = childForm;
            }
        }
    
        this.OnShowForm(this._activeChildIndex);
    }
    

    This method create a child form if not exists and show when was previously created.

    In your ProductsForm, add a method to get the JSON:

    public string GetProductsJson()
    {
        return JSONOperations.getItemsAsString(products);
    }
    

    Now, you can use OnShowForm in this way:

    private void OnShowForm(int index)
    {
        var formType = (FormType)index;
        if (formType == FormType.Result)
        {
            var productsForm = this._childsForms[(int)FormType.Products];
            if (productsForm != null)
            {
                var json = productsForm.GetProductsJson();
            }
        }
    }
    

    When you activate your result form, search your products form. If was previously created, get the Json. You don't need save to file the json to use here. Save it if you need for other reason.

    Do some changes in FormClosing:

    private void ProductPricesForm_FormClosing(object sender, FormClosingEventArgs e)
    {
        // Do this if you need
        string jsonString = GetProductsJson();
        File.WriteAllText(JSONOperations.productsJSONPath, jsonString);
    
        // TODO: You need a flag to know when are you closing the app. 
        // In that case, don't cancel
        e.Cancel = true;
    }
    

    UPDATE

    I don't like this solution but may be that you are waiting for:

    First, add a method to get the Json in your Product form:

    public string GetJson()
    {
        return JSONOperations.getItemsAsString(products);
    }
    

    And a field in your main form:

    private string _productsJson;
    

    Instead of a file, use this string. You also can save to file if you need for other purposes.

    Change this other method:

    private void buttonProductPrices_Click(object sender, EventArgs e)
    {            
        var form = new ProductPricesForm();
        form.FormClosing += (sender2, e2) => this.OnProductsPrices(form.GetJson());
            
        openChildForm(form);
    }
    

    It do the same as you was doing and also get the closing event to get the Json and invoke to OnProductsPrices with that string.

    private void OnProductsPrices(string json)
    {
        if (_productsJson != json)
        {
            _productsJson = json;
            var form = activeForm as ResultForm;
            if (form != null)
            {
                form.DoSomethingWithTheJson(json);
            }
        }
    }
    

    If the Json doesn't change, you don't need to do anything. When it's changed, you update your _productsJson and check if current form is ResultForm. In this case, you load the Json or do whatever you do with the Json in that Form.

    Also, you must use _productsJson when you create the ResultForm, like know, that I suppose you get from file. In this way, you use the json always when you create ResultForm or later, when the ProductsForm is closed.

    private void buttonResult_Click(object sender, EventArgs e)
    {
        var form = new ResultForm();
        openChildForm(form);
    
        if (!string.IsNullOrEmpty(_productsJson))
            form.DoSomethingWithTheJson(_productsJson);
    
        // Or do the same in the constructor and manage inside the form, calling to DoSomethingWithTheJson
        //var form = new ResultForm(_productsJson);
    }