views:

626

answers:

2

Hi there. Ok, I know this has been asked a million times before (and people also start off their StackOverflow question in the very same way XD), but I would like to know how to achieve the following:

  1. The application first launches a login box
  2. If the login is successful, then the splash screen must show (on a separate thread).
  3. While the splash screen is showing, a class object must be filled (that adheres to the Singleton pattern) with copious amounts of user-specific data from the DB, whilst displaying back to user what it is doing (eg. initializing...loading data...loading preferences...rendering workspace...done!)
  4. The splash screen must also wait for the main form to finish initializing on the main thread, before finally being disposed of.

That is the desire flow for the application. Upon closing the main form, the user should be returned to the login box.

I must state upfront that I am not all that clued up on alot of winforms stuff, but through asking these kind of questions, I am slowly learning. I have been doing some reading up on threading, and have learned that the splash screen should be spawned in its own thread, and feed status updates using delegates (to cater for cross-thread updates to the UI) from the main thread, and that this should all be done in the Program.cs "Main()" subroutine.

I am reaching out here, as I don't even know where to begin, due to the additional requirement of having the login form show first (and then last when the Main form is closed). I would certainly value any assistance in this regard.

Much thanks! sha

+1  A: 

Try this post right here:

http://stackoverflow.com/questions/392787/problem-with-splash-screen-c-vs2005

Mark P Neyer
+2  A: 

Here's a simple example of how to do this. The trick is to make the login box your main form since it's the one that opens first and closes last.

For this example, the LoginScreen form has one button, an OK button that invokes the OnOK() method when clicked.

public partial class LoginScreen : System.Windows.Forms.Form
{
    ApplicationWindow window;

    public LoginScreen()
    {
        InitializeComponent();
    }
    private void OnFormClosed( object sender, FormClosedEventArgs e )
    {
        this.Show();
    }
    private void OnOK( object sender, EventArgs e )
    {
        this.Hide();

        window = new ApplicationWindow();
        window.FormClosed += OnFormClosed;
        window.Show();
    }
}

The ApplicationWindow form would equate to what you referred to as your "main" form. It is what launches the SplashForm.

public partial class ApplicationWindow : System.Windows.Forms.Form
{
    public ApplicationWindow()
    {
        SplashForm.Show( 500 );

        InitializeComponent();
    }

    private void OnLoad( object sender, EventArgs e )
    {
        // Simulate doing a lot of work here.
        System.Threading.Thread.Sleep( 1000 );

        SplashForm.Hide();

        Show();
        Activate();
    }
}

And here's a copy of the SplashForm I use. It will fade in and fade out based on the number of milliseconds you specify in the static Show() method.

public partial class SplashForm : Form
{
    #region Public Methods

    /// <summary>
    /// Shows the splash screen with no fading effects.
    /// </summary>
    public new static void Show()
    {
        Show( 0 );
    }

    /// <summary>
    /// Shows the splash screen.
    /// </summary>
    /// <param name="fadeTimeInMilliseconds">The time to fade
    /// in the splash screen in milliseconds.</param>
    public static void Show( int fadeTimeInMilliseconds )
    {
        // Only show the splash screen once.
        if ( _instance == null ) {
            _fadeTime = fadeTimeInMilliseconds;
            _instance = new SplashForm();

            // Hide the form initially to avoid any pre-paint flicker.
            _instance.Opacity = 0;
            ( ( Form ) _instance ).Show();

            // Process the initial paint events.
            Application.DoEvents();

            if ( _fadeTime > 0 ) {
                // Calculate the time interval that will be used to
                // provide a smooth fading effect.
                int fadeStep = ( int ) Math.Round( ( double ) _fadeTime / 20 );
                _instance.fadeTimer.Interval = fadeStep;

                // Perform the fade in.
                for ( int ii = 0; ii <= _fadeTime; ii += fadeStep ) {
                    Thread.Sleep( fadeStep );
                    _instance.Opacity += 0.05;
                }
            } else {
                // Use the Tag property as a flag to indicate that the
                // form is to be closed immediately when the user calls
                // Hide();
                _instance.fadeTimer.Tag = new object();
            }

            _instance.Opacity = 1;
        }
    }

    /// <summary>
    /// Closes the splash screen.
    /// </summary>
    public new static void Hide()
    {
        if ( _instance != null ) {
            // Invoke the Close() method on the form's thread.
            _instance.BeginInvoke( new MethodInvoker( _instance.Close ) );

            // Process the Close message on the form's thread.
            Application.DoEvents();
        }
    }

    #endregion Public Methods

    #region Constructors

    /// <summary>
    /// Initializes a new instance of the SplashForm class.
    /// </summary>
    public SplashForm()
    {
        InitializeComponent();

        Size = BackgroundImage.Size;

        // If transparency is ever needed, set the color of the desired
        // transparent portions of the bitmap to fuschia and then
        // uncomment this code.
        //Bitmap bitmap = new Bitmap(this.BackgroundImage);
        //bitmap.MakeTransparent( System.Drawing.Color.Fuchsia );
        //this.BackgroundImage = bitmap;
    }

    #endregion Constructors

    #region Protected Methods

    protected override void OnClosing( CancelEventArgs e )
    {
        base.OnClosing( e );

        // Check to see if the form should be closed immediately.
        if ( fadeTimer.Tag != null ) {
            e.Cancel = false;
            _instance = null;
            return;
        }

        // Only use the timer to fade if the form is running.
        // Otherwise, there will be no message pump.
        if ( Application.OpenForms.Count > 1 ) {
            if ( Opacity > 0 ) {
                e.Cancel = true; // prevent the form from closing
                Opacity -= 0.05;

                // Use the timer to iteratively call the Close method.
                fadeTimer.Start();
            } else {
                fadeTimer.Stop();

                e.Cancel = false;
                _instance = null;
            }
        } else {
            if ( Opacity > 0 ) {
                Thread.Sleep( fadeTimer.Interval );
                Opacity -= 0.05;
                Close();
            } else {
                e.Cancel = false;
                _instance = null;
            }
        }
    }

    #endregion Protected Methods

    #region Private Methods

    private void OnTick( object sender, EventArgs e )
    {
        Close();
    }

    #endregion Private Methods

    #region Private Fields

    private static SplashForm _instance = null;
    private static int _fadeTime = 0;

    #endregion Private Fields
}

The SplashForm is just a blank form with the following property values:

  • BackgroundImage = (the image of your choice)
  • BackgroundImageLayout = Center
  • DoubleBuffered = true
  • FormBorderStyle = None
  • ShowInTaskbar = False
  • StartPosition = CenterScreen
  • TopMost = true

It also contains a System.Windows.Forms.Timer control named fadeTimer with the default properties. The Tick event is configured to invoke the OnTick() method.

What this does not do is update the status of the loading process. Perhaps someone else can fill in that portion for you.

Matt Davis
This doesn't run the Splash Form in a separate thread, but the other link should help with that.
Kathy Van Stone
To communicate with the Splash Screen, either it listens for events on ApplicationWindow, or the loading process periodically calls methods on the splash screen. Just remember to use BeginInvoke if the splash screen is in a separate thread.
Kathy Van Stone
You're right. It's been a while since I've looked at this code. I'll update my post to be correct. Thanks.
Matt Davis
I think if you make an underlying controller, like an mvc-ish pattern then the splash screen could just be displayed as a part of your loading process. By doing this you really wouldn't need a background worker running since you don't want to display your main app until the loading is complete, yes?
DataDink
Matt, thank u most sincerely! Just gimme a few minutes to understand your code. Just by glancing at it, am I correct in assuming that the login form will be the startup form in *Main()* sub - "Application.Run(frmLogin);"? That mean that the main application form window (in my case: frmMain) will call the SplashScreen, and contains the OnLoad event, where I can "fill" my Singleton object. So this happens in the main UI thread, right? Cos the Singleton object will be accessed from the main UI thread, and should NOT be modified by any other threads. (Sorry, if Im confusing matters here!)
Shalan
Hi Kathy, thanks for the additional input!
Shalan
@Shalan: Yes, frmLogin will be your startup form. Once the login has occurred, frmLogin hides itself and then 'launches' frmMain. At that point, you can load your Singleton object however you wish - in the main thread, in a background thread, via an asynchronous delegate, etc.
Matt Davis
@Matt: Thanks! So with your architecture, all data loading happens in the Main Form's *Load()* event, and not in the *Main()* sub - correct? I find this ideal, as I also have additional load logic in Main Form, so the splash screen can still show while the Singleton object is being filled AND while the Main Form is loading. I ask this, as I have seen some people in other forums mention that its more advisable to do all loading in *Main()* rather than *frmMainWindow_Load()* - but does it really make a difference?
Shalan
I'm not aware of any technical reason to "load" in the ApplicationWindow's constructor vs. its Load event handler. The SplashForm.Show() method will return once the splash screen is fully visible. From that point until you call the SplashForm.Hide() method, you can do whatever loading is necessary, whether in the constructor or the Load event handler.
Matt Davis
Thanks Matt! I got everything working perfectly and using making use of delegates to send status update messages to the Splash Screen! Thanks for all of your time and help!
Shalan