Nice Threads!

I could really use some help here...

I am using C# 2005 and have created a splash screen that runs on a seperate thread. I can invoke the splash screen in the Main() method, but then I can't get rid of it! I am attempting to put a line in the Form1_Load (or maybe Form1_Shown) that calls a static method in the splash screen to close that form. But, when I do so, it complains about an invalid cross-thread operation. How can I get this screen to close

Here are the relevant parts to my splash screen:

// Static Public Method to launch Splash Screen
static public void ShowSplash()
{
if (ms_frmSplash != null) return;
ms_Thread = new Thread(new ThreadStart(frmSplash.ShowForm));
ms_Thread.IsBackground = true;
ms_Thread.SetApartmentState(ApartmentState.STA);
ms_Thread.Start();
}

// Static Private Method to Display Splash Screen
static private void ShowForm()
{
ms_frmSplash = new frmSplash();
Application.Run(ms_frmSplash);
}

// Static Public Method to Close Splash Screen
static public void CloseForm()
{
if (ms_frmSplash != null) ms_frmSplash.Close();
}



Answer this question

Nice Threads!

  • Guy_B

    You can call Application.Run only once per thread. Theoretically you could call it more than once but you'll most probably mess up the message pump. You can find documentation on how this whole thing works by searching for "Splash Screen" and "ApplicationContext". There are a couple of articles on it. The source code is also a good reference. Nevertheless it seems that people are fascinated with multiple UI threads these days and I can't figure out why. People seem to think that if you want multiple forms open at the same time or if they each are going to react to their own input that you need multiple threads and this simply isn't true. Multithreading is complex enough without adding the overhead of multiple UI threads to the mix. Keep it simple. Use a single thread for your UI. Your splash screen can be shown in the same thread as your main form. You simply need to ensure that you control the creation order to ensure that when the splash screen goes away the app doesn't go with it.

    Michael Taylor - 2/5/06


  • Ramji P

    Jon is correct. Else blocks map to the nearest, preceding If statement in the same block. However your inner If statement serves no purpose anyway. If ms_frmSplash is null then the first If statement will through an exception. You should change your loop to be this (if you ever need to worry about the var being null to begin with).

    if (ms_frmSplash != null)
    {
    if (ms_frmSplash.InvokeRequired)
    {
    ms_frmSplash.Invoke(new CloseDelegate(CloseForm), null);
    } else
    {
    ms_frmSplash.Close();
    };
    };

    If ms_frmSplash is never null then you can remove the outer if all together.

    Michael Taylor - 2/2/06

  • zuo lan

    Well, if ms_frmSplash is null, then you haven't got a reference to it to close it in the first place.

    You haven't shown the code used to create the splash screen in the first place, only the closing code. Is it possible that you've created the splash screen but not set the value of ms_frmSplash

    If you could post a short but complete program that demonstrates the problem, that would help a lot... See http://www.pobox.com/~skeet/csharp/complete.html for more information.

    Jon



  • JMathews

    Unfortunately, that's not actually complete - there's no Main method, and frmSplash is only a partial class - we'd need the rest of the code. I'd suggest ditching the UI designer for this and hand-crafting the code at least until we've worked out what's wrong.

    When it fails with a NullReferenceException, where does the stack trace show it occurring

    Jon


  • mac314

    There's a few problems with your code. Firstly you are assuming that when you call ms_thread.Start() that it will immediately run and that is not necessarily true. It is possible that your call to CloseForm() is occuring before ShowForm() is ever called. Threads are funny that way. Secondly you can't call Application.Run() on your splash screen. The problem is that Windows Forms only supports a single top-level window. When the top-level form goes away so does the subsystem supporting it. The net effect is that the app will go away. That is why you don't see very many splash screen implementations that actually permit you to do work in the background. It is difficult. Most splash screen impls display a screen for a few seconds and then go away. This works because the main form is the one that displays the splash screen. You could theoretically get a splash screen to work by creating the main form and running it through Application.Run(). Within the OnLoad for your form display the splash screen modeless and then later on after the form finishes initialization close the splash screen.

    There really isn't much of a benefit in using a separate thread when you can just bring up the form modeless. I'd remove that code all together if possible.

    Michael Taylor - 2/3/06


  • LVictor

    I just added the code changes suggested, but it still does not close the splash screen. I am about to try spending some time "Stepping into" the code and see if I can figure out why.

    It seems like I have to use the if (ms_frmSplash != null) statement though... otherwise it generates a NullReferenceException was unhandled error. The help files suggest that I either 'Use the "new" keyword to create an object instance' (Figured that didn't apply here) or 'Check to determine if the object is null before calling the method.' I admit that I am new to C#, but it seemed like that statement was what I needed.

    Thanks!


  • jonchicoine

    Here is my latest "short but complete" code. I am calling the frmSplash.ShowSplash() method from Main() and the frmSplash.CloseForm() method from the Form1 Shown event. Currently, it seems to work about one out of every three or four times I run it from the IDE The rest of the time it fails with the NullReferece Exception. However, if I add the "if" statement to check for null, then the splash screen will not close at all. Again thanks for taking the time to look at this!


    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    using System.Threading;

    namespace MyApp
    {
    public partial class frmSplash : Form
    {
    public frmSplash()
    {
    InitializeComponent();
    }

    static frmSplash ms_frmSplash = null;
    static Thread ms_Thread = null;

    private delegate void CloseDelegate();

    // Static Public Method to launch Splash Screen
    static public void ShowSplash()
    {
    if (ms_frmSplash != null) return;
    ms_Thread = new Thread(new ThreadStart(frmSplash.ShowForm));
    ms_Thread.IsBackground = true;
    ms_Thread.SetApartmentState(ApartmentState.STA);
    ms_Thread.Start();
    }

    //Static Private Method to display Splash Screen
    static private void ShowForm()
    {
    ms_frmSplash = new frmSplash();
    Application.Run(ms_frmSplash);
    }

    //Static Public Method to Close Splash Screen
    static public void CloseForm()
    {
    if (ms_frmSplash.InvokeRequired)
    {
    ms_frmSplash.Invoke(new CloseDelegate(CloseForm), null);
    }
    else
    {
    ms_frmSplash.Close();
    }
    }
    }
    }


  • vivekanand

    The problem is that your code is logically:

    static public void CloseForm();
    {
    if (ms_frmSplash.InvokeRequired)
    {
    if (ms_frmSplash != null)
    {
    ms_frmSplash.Invoke(new CloseDelegate(CloseForm), null);
    }
    else
    {
    ms_frmSplash.Close();
    }
    }
    }


    whereas you want it to be:

    static public void CloseForm();
    {
    if (ms_frmSplash.InvokeRequired)
    {
    if (ms_frmSplash != null)
    {
    ms_frmSplash.Invoke(new CloseDelegate(CloseForm), null);
    }
    }
    else
    {
    ms_frmSplash.Close();
    }
    }


    That's why it's important to use braces...

    Jon



  • S Krishna

    Move your Main() method out to a seperate class - something like this:

    public class formSplash : Form { //Your stuff in here }

    public class MainForm : Form
    {
    fromSplash ms_frmSplash;

    public static void Main()
    {
    ms_frmSplash = new frmSplash();
    ms_Thread = new Thread(new ThreadStart(frmSplash.ShowForm));
    ms_Thread.IsBackground = true;
    ms_Thread.SetApartmentState(ApartmentState.STA);
    ms_Thread.Start();
    Application.Run(new MainForm());
    }

    public static void CloseForm()
    {
    if (ms_frmSplash.InvokeRequired)
    {
    ms_frmSplash.Invoke(new CloseDelegate(CloseForm), null);
    }
    else
    {
    ms_frmSplash.Close();
    }
    }
    }

    Typically you want to show the splash screen while your MainForm is innitialising.



  • Ingo Muschenetz

    Do you have any documentation backing the idea that you can only call Application.Run once As far as I'm aware, Windows Forms can support multiple UI threads, and the way to launch them is to use Application.Run on each one.

    Here's a small example of it working:

    using System;
    using System.Drawing;
    using System.Threading;
    using System.Windows.Forms;

    class Test
    {
    static void Main()
    {
    for (int i=0; i < 5; i++)
    {
    new Thread (new ThreadStart(ShowForm)).Start();
    }
    }

    static void ShowForm()
    {
    Form f = new Form();
    f.Size = new Size(300, 200);
    Application.Run (f);
    }
    }


    Jon

  • vinobaje

    In order to work with a UI component on another thread you'll have to marshal the request across. All UI controls have the Invoke method which will run a method (defined by the parameters) on the thread that created the UI component. You can check to see if this is necessary first using the InvokeRequired property. In your case I'd recommend exposing a method on your splash screen to handle the dirty work.

    //In splash screen form
    private delegate void CloseDelegate ( );

    public void CloseForm ( )
    {
    If (InvokeRequired)
    Invoke(new CloseDelegate(CloseForm), null
    else
    this.Close();
    }

    The above code checks to see if it is running on the appropriate thread. If not then it marshals the request to the appropriate thread and then calls itself again. If it is then it simply calls the standard close method.

    Michael Taylor - 2/1/06


  • Aelij Arbel

    Indeed. Somewhere in your code you probably have the following line:

    SplashScreenForm frm = new SplashScreenForm();
    frm.Show();

    You instead need to modify your code to say:

    ms_frmSplash = new SplashScreenForm();
    ms_frmSplash.Show();

    Now this will only work provided it is in a method of your main form because you declared ms_frmSplash to be in the main form. Honestly this seems like a lot more work then necessary. Instead of wasting a field on the splash screen you can instead use a static method/field in your splash screen form.

    //Sample splash screen form
    class SplashScreenForm : Form
    {
    private SplashScreenForm ( )
    { m_Inst = this; }

    public static SplashScreenForm GetInstance ( )
    {
    if (m_Inst == null)
    m_Inst = new SplashScreenForm();

    return m_Inst;
    }

    public static void ShowForm ( )
    {
    GetInstance().Show();
    }

    public static void CloseForm ( )
    {
    SplashScreenForm frm = GetInstance();

    if (frm.InvokeRequired)
    frm.Invoke(new CloseDelegate(CloseForm), null);
    else
    frm.Close();
    }

    private static SplashScreenForm m_Inst;
    }

    This is the singleton pattern at work. Basically you push all the work to the splash screen class. In your code you then only need to do the following:

    //To show the form
    SplashScreenForm.ShowForm();

    //To close it
    SplashScreenForm.CloseForm();

    No fields, no null references, nothing. Of course this works for the splash screen because you display it only once but you can augment the code to handle other cases.

    Michael Taylor - 2/3/06


  • Chris Menegay

    Getting Closer... I no longer get the cross-thread error, but the splash screen still does not close. This is what I tried:

    // Static Public Method to Close Splash Screen
    static public void CloseForm();
    {
    if (ms_frmSplash.InvokeRequired)
    if (ms_frmSplash != null) ms_frmSplash.Invoke(new CloseDelegate(CloseForm), null);
    else
    ms_frmSplash.Close();
    }

    Thanks!


  • Magnus Blomberg

    Yes, you should only call it once per thread - but it was only being called once per thread I believe. It's not particularly unreasonable to have a splash screen running in a separate UI thread from the main form - especially if one of the reasons for having a splash screen is to show a (responsive) UI while the main form is being initialised.

    Jon


  • Nice Threads!