0

Quick question: Is it possible to showDialog/Show form from a new thread that has been started from within Load event of a different form?

EDIT: Why is it not possible to showDialog/Show form from a new thread that has been started from within Load event of a different form?

The supposed requirement is to show a loader form /spiral thingy with loading text, and possibly more/ while MDI child form is loading thus hanging the whole application.

The requirement states that the "Loader form" must not hang. Thus it requires a new thread.

code summary, that I have implemented so far: MDI Parent:

delegate void ManipulateJob();
public void StartJob()
{
    Cursor.Current = Cursors.WaitCursor;

    if (this.InvokeRequired) 
    //case when the Loader is started from a different thread than the main we need to invoke
    {
        System.Diagnostics.Debug.WriteLine("-> MainForm.StartJob  InvokeRequired");
        ManipulateJob callback = new ManipulateJob(StartJob);
        this.Invoke(callback, new object[] { });
    }
    else
    {
        tasks_running++;
        System.Diagnostics.Debug.WriteLine("-> MainForm.StartJob  InvokeNotRequired");

        if ((this.t == null || !this.t.IsAlive)&&tasks_running == 1)
        {
            System.Threading.ThreadStart ts = new System.Threading.ThreadStart(StartDifferent);
            this.t = new System.Threading.Thread(ts);
            this.t.Name = "UI Thread";

            System.Diagnostics.Debug.WriteLine(" **starting thread");
            this.t.Start();
            while (_form==null||!_form.IsHandleCreated) 
            //do not continue until the loader form has been shown when this is enabled
            //the whole program hangs here when StartJob is called within Load event
            {
                System.Threading.Thread.Yield();
            }
        }

    }

    System.Diagnostics.Debug.WriteLine("<- MainForm.StartJob");
}

private static frmLoading _form;
public void StartDifferent()
{
    System.Diagnostics.Debug.WriteLine(" **thread started");
    _form = new frmLoading();
    System.Diagnostics.Debug.WriteLine(" **loader created");

    _form.Icon = this.Icon;
    System.Diagnostics.Debug.WriteLine(" **loader icon set");

    _form.ShowDialog();


    System.Diagnostics.Debug.WriteLine(" **thread terminating");
}

public void StopJob()
{
    if (this.InvokeRequired) //in case this is called from a different thread
    {
        System.Diagnostics.Debug.WriteLine("-> MainForm.StopJob  InvokeRequired");
        ManipulateJob callback = new ManipulateJob(StopJob);
        this.Invoke(callback, new object[] { });
    }
    else
    {
        System.Diagnostics.Debug.WriteLine("-> MainForm.StopJob  InvokeNotRequired");
        if (tasks_running>0&&--tasks_running == 0)
        {
            StopDifferent();
        }
    }
    System.Diagnostics.Debug.WriteLine("<- MainForm.StopJob");

    Cursor.Current = Cursors.Default;
}

delegate void CloseLoadingForm();
public void StopDifferent()
{
    System.Diagnostics.Debug.WriteLine("-> MainForm.StopDifferent");

    try
    {
        if (_form != null && _form.IsHandleCreated)
        {
            CloseLoadingForm callback = new CloseLoadingForm(_form.Close); 
            //_form itself is always on a different thread thus, invoke will always be required
            _form.Invoke(callback);
        }
    }
    finally
    {
        try
        {
            if (this.t != null && this.t.ThreadState == System.Threading.ThreadState.Running)
                this.t.Join();
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine("     MainForm.StopDifferent t.Join() Exception: " + ex.Message);
        }
    }
    System.Diagnostics.Debug.WriteLine("<- MainForm.StopDifferent");
}

child form example:

private void frmPricingEvaluationConfig_Load(object sender, EventArgs e)
{
    //taking out the following and putting it before 
    //Form frm = new Form();
    //frm.Show(); works
    if (this.MdiParent is IThreadedParent)
    {
        ((IThreadedParent)this.MdiParent).StopJob();
    }

    // the loader form is not displayed yet

    System.Diagnostics.Debug.WriteLine(" loading pricingEvaluationConfig");
    loadPricingClasses();
    loadEvaluationClasses();

    if (this.MdiParent is IThreadedParent)
    {
        ((IThreadedParent)this.MdiParent).StopJob();
    }
}

//loader form displays here, but is unable to close because the closing has been called

please don't mind the namings and the fact that there are 2 matching sets of start and stop.

the question is whenever i call Startjob from within a form1_Load(object sender, EventArgs e) the _form (AKA loader) is not displayed until the Load method finishes.

EDIT: In other words, the Loader does not display until the actual form is displayed, the only time the Loader is "Shown" (displayed on screen) is when the actual form is finished the onLoad method. :ENDEDIT

if i take out the Startjob out of the load handler method and put it before I declare and Show() then everything works the way it supposed to

EDIT:
I assume, without any proof, that Form's CreateHandle() method that is called within ShowDialog() and Show() methods accesses a static property somewhere checking whether any handles are already being created, and if so it stacks its own creation after the current Handle Creation, because during this whole thing somewhere the code tried to Close() the Loader/Splash and threw an error saying "Cannot Close() a form that is Creating Handle." So I assumed the CreateHandle() method has somewhere something similar to my

while() { Thread.Yield(); } 

thus creating 2 threads /1 of which is the Loader\Splash creator thread/ which continuously Yield to one another.

Basically, I don't understand why all of this is happening, if my assumptions are correct why should Forms should queue after one another? :ENDEDIT

I will gladly answer all questions and concerns about the code, please ask if anything I'm doing here is vague, or you don't quite understand the need of it.

ochitos
  • 172
  • 1
  • 14
  • Looking a little deeper, this "problem" has something to do with MDI child parent relationship, because whenever I comment out the statement `//frm.MdiParent = this;` everything works they way it supposed to – ochitos Mar 14 '12 at 07:47

1 Answers1

1

It seems like a more appropriate solution is to perform the long-running initialization task in a separate thread, and enable the MDI child window once that initialization is complete (so allow MDI child window creation to complete, but disable or keep the window invisible until a separate loader thread completes. Have your splash screen enable or make visible the child window once loading tasks are done).

I'm not sure whether I'm following your current code correctly, but it seems that you're blocking the OnLoad event. That will prevent the windows message queue, which in turn would block any UI rendering for the application.

You can then use any WinForms splash screen solution until the thread performing the load completes, for example

http://www.codeproject.com/Articles/5454/A-Pretty-Good-Splash-Screen-in-C

Eric J.
  • 139,555
  • 58
  • 313
  • 529
  • Thank you for going through my messy code and understanding it. Yes, I am blocking the queue, but only until the Splash displays which is on a different thread, which is started during the OnLoad. So I assumed, since the different thread the UI rendering will be done on that other thread for the Splash? I have read that article, it's pretty high up there on Google. The problems are Application.Run() this makes the Splash either not on Top || Always on Top of all windows, and does not cover my particular problem of either Showing the Form before the task completes. – ochitos Mar 14 '12 at 06:48
  • Yes, I completely agree with you on the loading things on a separate thread and just running a marquee progress bar. But unfortunately 1 of the requirements is to add a loader without traditional VS programming breaking, because loading things in a separate thread to make UI more friendly is too much work apparantly and hard to understand or something like that. – ochitos Mar 14 '12 at 06:49
  • UI renderring is done on 1 thread no matter which thread the form is created on? where could I find reading material regarding this? – ochitos Mar 14 '12 at 06:54
  • I think [this](http://msdn.microsoft.com/en-us/library/86faxx0d%28VS.80%29.aspx) is what I'm looking and should read. – ochitos Mar 14 '12 at 06:59
  • @fuximusfoe: You can create one message pump per thread, therefore having different windows run on different threads without blocking each other, but that is an unnecessarily complicated architecture that others will find confusing to maintain. See http://stackoverflow.com/questions/1566791/run-multiple-ui-threads – Eric J. Mar 14 '12 at 16:32
  • I tried an example, having 3 forms 1 main, 1 child, and 1 splash, everything worked fine initially, just as I assumed, the splash form displayed OnLoad and disappeared right before OnLoad finished. But when I set child's MdiParent to be main form things go weird. This Problem has something to do with Mdi architecture, I'm guessing it has something to do with the Owner of the splash form. – ochitos Mar 15 '12 at 01:35