Introduction
I believe every application developer has experienced the problem with slow loading user interface because loading data takes too much time.
While for Web applications, it might seem more natural because of generating the page on the fly, using slow internet connection or overloaded server, it is not the case for Windows Forms applications. They run locally on your machine and it is expected to be more responsive. Still too often, you can see applications that take too much time to update the user interface and display the data.
This article will try to show you how to make a Windows Forms application more responsive by showing the user interface while waiting for the data and updating the interface when data is available.
The article demonstrates few things:
- How to build a splash form to be displayed during a time consuming operation
- Performing the time consuming operation in a separate thread
- Center the splash form relative to main form
- Move the splash screen when the main form is moved to keep it centered to it
- Hide / show the splash screen when the main form gets / loses focus
The solution consists of two forms:
MainForm
- This is the main form as the name suggests
SplashFrm
- The splash form
MainForm
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Threading;
namespace SplashScreen
{
public class MainForm : System.Windows.Forms.Form
{
private System.ComponentModel.Container components = null;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.StatusBar statusBar1;
private SplashForm frmSplash;
public delegate void UpdateUIDelegate(bool IsDataLoaded);
[STAThread]
static void Main()
{
Application.Run(new MainForm());
}
private void MainForm_Load(object sender, System.EventArgs e)
{
UpdateUI(false);
frmSplash = new SplashForm(this);
Thread t = new Thread(new ThreadStart(DoSomeWork));
t.IsBackground = true;
t.Start();
}
private void DoSomeWork()
{
System.Threading.Thread.Sleep(10000);
Invoke(new UpdateUIDelegate(UpdateUI), new object[] { true });
}
private void UpdateUI(bool IsDataLoaded)
{
if (IsDataLoaded)
{
this.statusBar1.Text = "Done.";
if (this.frmSplash != null) {
frmSplash.Close();
}
}
else
{
this.statusBar1.Text = "Loading data ...";
}
}
}
}
The code is very straightforward. At the time the main form is loaded, the UpdateUI()
method is invoked to update the status bar. Then the splash form is shown and immediately after that, DoSomeWork()
method is executed in a separate thread.
By using this technique, the main form is displayed without any delay and the splash form is telling the user that the data is loading. If we do not use a separate thread to load the data, it might take a few seconds for the main form to appear. It is all about the user experience.
When the data is loaded, we have to update the user interface - change the status bar text and close the splash form. Since loading of the data happened in a separate thread, we are not supposed to update the main form directly. Instead, we are going to use the main form's Invoke
method passing the delegate to UpdateUI()
method with a parameter telling the data loading is complete.
Now let's have a look at the splash form.
SplashForm
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
namespace SplashScreen
{
public class SplashForm : System.Windows.Forms.Form
{
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.Label label1;
private System.ComponentModel.Container components = null;
private Form MainForm;
private bool IsClosed = false;
#region Constructors
public SplashForm()
{
InitializeComponent();
}
public SplashForm(Form mainForm):this() {
MainForm = mainForm;
MainForm.Deactivate += new System.EventHandler(this.MainForm_Deactivate);
MainForm.Activated += new System.EventHandler(this.MainForm_Activated);
MainForm.Move += new System.EventHandler(this.MainForm_Move);
this.ShowInTaskbar = false;
this.TopMost = true;
this.StartPosition = FormStartPosition.Manual;
this.Visible = false;
AdjustLocation();
}
#endregion
#region Methods
private void MainForm_Deactivate(object sender, EventArgs e)
{
if (!this.IsClosed)
{
this.Visible = false;
}
}
private void MainForm_Activated(object sender, EventArgs e)
{
if (!this.IsClosed)
{
this.Visible = true;
}
}
private void MainForm_Move(object sender, EventArgs e)
{
AdjustLocation();
}
private void SplashForm_Closed(object sender, System.EventArgs e)
{
this.IsClosed = true;
}
private void AdjustLocation()
{
int dx = (MainForm.Width - this.Width) / 2;
int dy = (MainForm.Height - this.Height) / 2;
Point loc = new Point(MainForm.Location.X, MainForm.Location.Y);
loc.Offset(dx, dy);
this.Location = loc;
}
#endregion
}
}
In addition to the default constructor, we have another one that accepts as a parameter a reference to the main form. We are using this one inside MainForm_Load()
:
frmSplash = new SplashForm(this);
frmSplash.Show();
We need the main form reference in order to be able to adjust the position of splash form and its visibility when those change for the main form.
That's all about it.
Conclusion
I would be glad if this article helps someone trying to provide a better user experience for WinForm applications. I am open to any suggestions or questions.
Known Issues
The solution is still not perfect. If you start another application, the main form will lose focus and the splash form will be hidden. However, if you minimize that other application and main form gets visible, the splash form will not show unless you click the main form giving the focus to it in this way.
History
- 29th March, 2006 - Initial version
- 7th April, 2006 - Show/hide splash form when main form gets / loses focus; splash form follows main form when the latter is moved across the screen