Updating the UI from a Secondary Thread

B

BG

We're having trouble writing the code to update a UI control (label.Text)
from a secondary thread. We're using C# with Windows Forms.



We have a main form named MainForm, a splash screen form named SplashScreen,
and a C# class library named BackgroundProcess.



On application start, we simply want to show the splash screen form, kick
off the background processing on a separate thread, and, at different points
in the background processing, update a label on the splash screen to inform
the user of what the application is doing. When the processing is done, the
splash screen form goes away and the main form appears.



In the main form's form load event we have:



private void MainForm_Load(object sender, System.EventArgs e)

{

// Create and show the splash screen

SplashScreen frmSS = new SplashScreen();

frmSS.Show();



// Create the object for background processing and set an initial
property

BackgroundProcess bp = new BackgroundProcess();

bp.StartingDirectory = @"C:\";



// Create a new thread for the background processing and start it

System.Threading.Thread activeThread = new System.Threading.Thread(new
System.Threading.ThreadStart(bp.StartProcess));

activeThread.Start();



while (activeThread.IsAlive)

{

Application.DoEvents();

}



// Clean up

activeThread = null;

bp = null;

frmSS.Close();

frmSS = null;

}



This code works, and the code in our class library (i.e., the background
processing code) works, but we still can't get the splash screen's label to
update.



We've trying using delegates/events, etc. and have not been successful in
getting the label updated from the secondary thread that's doing all the
"behind-the-scenes" processing.



Any suggestions?



Thanks.
 
J

John Murray

I am probably missing something here, but I suspect that the problem is
your background thread is calling a delegate in your splash screen that
attempts to update the label in the same thread as the delegate was
called on.

You cannot (well, I guess technically you can, but it can get ugly)
update any forms elements except on the thread that they were created
on. Instead, in the recipient of the call to update the label (in your
splash screen) check to see if InvokeRequired is true, and then use
invoke on that control to have it execute on the proper thread.

John
 
W

William Stacey [MVP]

Here is one way that loads the spash form and kicks the worker and waits
till close, then starts the main form using Application.Run(new MyForm());
as normal. The worker calls private methods in the form that make sure the
calls are done on the forms thread. Using this pattern, you can update any
controls on your form.

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

namespace CSharpExample.MyApp
{
/// <summary>
/// Summary description for SpashForm.
/// </summary>
public class SplashForm : System.Windows.Forms.Form
{
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label lblText;
private System.Windows.Forms.Button btnClose;
private System.Windows.Forms.ProgressBar progressBar1;

/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;

public SplashForm()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();

//
// TODO: Add any constructor code after InitializeComponent call
//
}

[STAThread]
static void Main()
{
SplashForm splashForm = new SplashForm();
splashForm.ShowDialog();
splashForm.DialogResult = DialogResult.OK;
if ( splashForm.DialogResult == DialogResult.Cancel )
return;
Application.Run(new MyForm());
}

/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}

#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.label1 = new System.Windows.Forms.Label();
this.lblText = new System.Windows.Forms.Label();
this.btnClose = new System.Windows.Forms.Button();
this.progressBar1 = new System.Windows.Forms.ProgressBar();
this.SuspendLayout();
//
// label1
//
this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F,
System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point,
((System.Byte)(0)));
this.label1.Location = new System.Drawing.Point(16, 16);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(264, 23);
this.label1.TabIndex = 0;
this.label1.Text = "Welcome";
//
// lblText
//
this.lblText.Location = new System.Drawing.Point(16, 56);
this.lblText.Name = "lblText";
this.lblText.Size = new System.Drawing.Size(264, 23);
this.lblText.TabIndex = 1;
this.lblText.Text = "Loading XYZ...";
//
// btnClose
//
this.btnClose.Location = new System.Drawing.Point(208, 128);
this.btnClose.Name = "btnClose";
this.btnClose.TabIndex = 2;
this.btnClose.Text = "Cancel";
this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
//
// progressBar1
//
this.progressBar1.Location = new System.Drawing.Point(16, 96);
this.progressBar1.Name = "progressBar1";
this.progressBar1.Size = new System.Drawing.Size(264, 23);
this.progressBar1.Step = 1;
this.progressBar1.TabIndex = 3;
//
// SplashForm
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 159);
this.Controls.Add(this.progressBar1);
this.Controls.Add(this.btnClose);
this.Controls.Add(this.lblText);
this.Controls.Add(this.label1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.Name = "SplashForm";
this.Text = "Loading MyApp...";
this.Load += new System.EventHandler(this.SpashForm_Load);
this.ResumeLayout(false);

}
#endregion

private void SpashForm_Load(object sender, System.EventArgs e)
{
Thread t = new Thread(new ThreadStart(WorkerStart));
t.IsBackground = true;
t.Start();
}

private delegate void UpdatePBarDelegate(int step);
private void UpdatePBar(int step)
{
if ( InvokeRequired )
{
UpdatePBarDelegate ud = new UpdatePBarDelegate(UpdatePBar);
Invoke(ud, new object[]{step});
}
else
{
this.progressBar1.Value = this.progressBar1.Value + step;
}
}

private void Done()
{
if ( InvokeRequired )
{
Invoke(new MethodInvoker(Done));
}
else
{
this.lblText.Text = "Done";
this.Close();
}
}

private void WorkerStart()
{
for(int i = 0; i < 100; i++)
{
this.UpdatePBar(1); // Update progress bar 1 step.
Thread.Sleep(50); // Fake some delay - your processing goes here.
}
this.Done(); // Do any other updates on the splash form and load main and
quit.
}

private void btnClose_Click(object sender, System.EventArgs e)
{
this.DialogResult = DialogResult.Cancel;
//this.Close();
}
}
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top