0

I have a Order form. Once a order is complete, I use a thread to email the order to the supplier. The Thread is use to prevent the system hanging while the order is exported to pdf and sent.

The Problem: I would like to place an message on the MDIParent Toolstripstatuslabel once the threat completes without error to confirm the order was sent. But I get an error: "System.NullReferenceException: Object reference not set to an instance of an object". Which I could be wrong, refers to the Child windows disposed the toolstripstatuslabel reference on the parent form when it closed, so the threat cant access it anymore. I know the easy solution would be to use a MessageBox to confirm all went good and well...but why make it easy if you can do it elegant?

So my question: How can I reference a control in the parent form from the threat? I tried looking at invoke, but not sure how to implement it or if it is actually the correct direction.

EDIT:

My code from the childform

public partial class frm_n_order : Form
{
     .
     .

private void bProcess_Click(object sender, EventArgs e)
{
     .
     .
     .

    new Thread(new ThreadStart(delegate
    {
        fExportOrder(strOrderNo);
        fSendMailv2(strPlant, strSupCode, strOrderNo);                                
    })).Start();

    this.close();
}

private void fExportOrder(string strOrderNo)
{
    //export order to pdf
}

private void fSendMailv2(string strPlant, string strSupCode, string strOrderNo);
{
    // get pdf
    // get email address

    try
    {
        // send email
        ((MDIParent1)MdiParent).tsslMain.Text = "Order No:" + strOrderNo + " was successfully send to " + strEmails;  //here I need to find a different way of accessing the Toolstripstatuslabel in the parent form
    }
    catch
    {
        MessageBox.Show("Email did not send");
    }
}

}

EDIT:

Ok, so after spending more than a day trying to figure out how to use Invoke, I realize while it seems like good practice when working with threads, its not my answer. My problem is directly related to the childform closing disposing all the controls so it looses its reference to the MdiParent. To solve the problem I did the following:

In my child class I added:

public static Form IsFormAlreadyOpen(Type FormType)
{
    foreach (Form OpenForm in Application.OpenForms)
    {
    if (OpenForm.GetType() == FormType)
        return OpenForm;
    }
    return null;
 }

I dont think it is the most elegant solution but the theory is that my Parent form will always be open when I need to access the Toolstripstatuslabel. So I basically loop through all the open forms to find the reference to the active MdiParent instance which then gets passed back to the caller. In the thread I then use the following code.

 MDIParent1 fm = null;
 if ((fm = (MDIParent1)IsFormAlreadyOpen(typeof(MDIParent1))) != null)
 {
      fm.Toolstripstatuslabel1.Text = "Order No:" + strOrderNo + " was successfully send to " + strEmails;
 }

I'm still looking for a better approach, but for now this works.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
user1339124
  • 33
  • 1
  • 4
  • 1
    If it's closing and, in effect, gone by the time you're trying to use it, how will the user ever see the update? –  Jun 29 '17 at 15:25
  • 1
    @Will i think he means to change a toolstrip status label on the *parent* window of the one he just closed. can we see some actual code though? it's kinda hard to figure out what is going wrong exactly. – Timothy Groote Jun 29 '17 at 15:29
  • @user1339124 my attempt at a solution from your description, is that you should pass a reference to the parent window to your thread, then use `App.Current.Dispatcher.Invoke(() => { /* update the status label here using the reference you passed to the thread */ });` – Timothy Groote Jun 29 '17 at 15:31
  • does this question give you a solution? https://stackoverflow.com/questions/661561/how-to-update-the-gui-from-another-thread-in-c?rq=1 – Dionisio Lamim Jul 05 '17 at 00:19
  • 1
    @TimothyGroote that is correct, you understand what I would like to do. But too be honest I feel a bit out of my depth with Dispatcher. I have been busy reading up on it trying to figure out how to use it. For one, it sounds like working with Winforms, I should rather look at BeginInvoke. – user1339124 Jul 05 '17 at 08:15
  • Sorry, i missed that you were talking about *winforms* :| you're absolutely right about `BeginInvoke` – Timothy Groote Jul 05 '17 at 08:42
  • Please don't even try to access (or update) UI elements from a non-UI thread. There be dragons with that approach. – Enigmativity Jul 06 '17 at 14:15
  • @Enigmativity Yeah, I know its less than ideal. Any ideas how I can better approach the problem - update the status label on latest action? Should I rather pass the message back to the Parent Form and from there update the label? – user1339124 Jul 07 '17 at 11:39
  • @user1339124 - I think you're missing my point. It's not "less than ideal". You can't access the UI from an non-UI thread. It doesn't matter if you pass messages to a parent form. You **must marshal calls to update the UI to the UI thread**. – Enigmativity Jul 07 '17 at 12:10

2 Answers2

0

Ok, so after spending more than a day trying to figure out how to use Invoke, I realize while it seems like good practice when working with threads, its not my answer. My problem is directly related to the childform closing disposing all the controls so it looses its reference to the MdiParent. To solve the problem I did the following:

In my child class I added:

public static Form IsFormAlreadyOpen(Type FormType)
{
    foreach (Form OpenForm in Application.OpenForms)
    {
    if (OpenForm.GetType() == FormType)
        return OpenForm;
    }
    return null;
 }

I dont think it is the most elegant solution but the theory is that my Parent form will always be open when I need to access the Toolstripstatuslabel. So I basically loop through all the open forms to find the reference to the active MdiParent instance which then gets passed back to the caller. In the thread I then use the following code.

 MDIParent1 fm = null;
 if ((fm = (MDIParent1)IsFormAlreadyOpen(typeof(MDIParent1))) != null)
 {
      fm.Toolstripstatuslabel1.Text = "Order No:" + strOrderNo + " was successfully send to " + strEmails;
 }

I'm still looking for a better approach, but for now this works.

user1339124
  • 33
  • 1
  • 4
0

It's hard for me to overlook someone saying "but why make it easy if you can do it elegant?"

Awesome!

I figure if we can do something elegantly, then in the future it should be easy.... right?

Anyways, hoping you find the below helpful.

A note: It looked to me like you were declaring your thread as a local variable and not storing it outside the local scope. If we want something to live past the end of the scope, it's a good idea to store a reference to it (which is done using a private Task field in the below example).

Sure, the thread would get added to the threadpool and stored somewhere in the framework even if it's just a local variable, so I think it wouldn't abort due to garbage collection as you have it, but I don't like the idea of instances floating around that I don't have references to.

public class MyChildForm : Form
{
    private Task longRunningTask;
    private Task closeTask;

    public string ResultOfTimeConsumingOperation { get; private set; }

    protected override Dispose(bool disposing)
    {
        if (disposing)
        {
             longRunningTask?.Dispose();
             closeTask?.Dispose();
        }
        base.Dispose(disposing);
    }    

    private void TimeConsumingOperation1()
    {
        Thread.Sleep(TimeSpan.FromSeconds(8));
        ResultOfTimeConsumingOperation = "Hooray we finished the work lol";
        this.closeTask =
            Task.Factory.FromAsync(
                BeginInvoke(new Action(Close)),
                EndInvoke);
    }

    protected override void OnLoad()
    {
        base.OnLoad();
        this.longRunningTask =
            Task.Run(TimeConsumingOperation1);
    }
}

public class MyParentForm : Form
{
    private List<Form> childForms;

    public MyParentForm() : base()
    {
        childForms = new List<Form>();
    }

    protected override void OnLoad()
    {
        base.OnLoad();
        RunChildForm();
    }

    private void RunChildForm()
    {
        var childForm = new MyChildForm();
        childForms = childForms.Append(childForm).ToList();
        childForm.FormClosing += ChidlForm_FormClosing;
        childForm.Show();
    }

    private void ChildForm_FormClosing(object sender, FormClosingEventArgs e)
    {
        var childForm = sender as MyChildForm;
        childForm.FormClosing -= ChildForm_FormClosing;
        if (childForms.Contains(childForm))
            childForms =
                childForms.
                    Except(new Form[] { childForm }).
                    ToList();

       // tada
       myStatusLabel.Text = childForm.ResultOfLongRunningProcess;
    }

    // main window is closing
    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        // let's close any windows we left open
        var localForms = childForms.ToList();
        childForms = new List<Form>();
        foreach (var form in localForms)
            form.Close();
    }
}
  • Barry, this question is from 2017 and the user profile says this person was "Last seen more than 4 years ago"... – Idle_Mind Nov 10 '21 at 18:39
  • And more overdue than it's ever been!! XD .... yes, you make a reasonable point and yes, I hadn't noticed. – Barry Smith Nov 10 '21 at 19:03