I have a windows form application which is supposed to show a splash screen with a label field that I want to update as the main form (called welcome.cs) loads in the background. The splash screen shows & hides just fine, but the label doesn't update.
I've done a lot of research but haven't quite found the solution.
Program.cs
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
using (new SingleGlobalInstance(1000))
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
SplashScreen splashscreen = new SplashScreen();
splashscreen.ShowSplashScreen();
Welcome welcome = new Welcome(splashscreen); //Takes some time to load
splashscreen.CloseForm();
Application.Run(welcome);
}
}
Splashscreen.cs
public partial class SplashScreen : Form
{
//Delegate for cross thread call to close
private delegate void CloseDelegate();
private delegate void UpdateStatusDelegate(string status);
private static SplashScreen splashScreen;
private Thread thread = null;
public SplashScreen()
{
InitializeComponent();
}
public void ShowSplashScreen()
{
// Make sure it is only launched once.
if (splashScreen != null)
return;
thread = new Thread(ShowForm);
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
static private void ShowForm()
{
splashScreen = new SplashScreen();
Application.Run(splashScreen);
}
public void CloseForm()
{
splashScreen.Invoke(new CloseDelegate(CloseFormInternal));
}
static private void CloseFormInternal()
{
splashScreen.Close();
}
public void UpdateStatus(string status)
{
splashScreen.Invoke(new UpdateStatusDelegate(UpdateStatusInternal), status);
}
private void UpdateStatusInternal (string status)
{
if (splashScreen != null && splashScreen.IsHandleCreated)
{
lblStatus.Text = status;
}
}
}
Welcome.cs
public Welcome(Splashscreen splashscreen)
{
InitializeComponent();
//Code to log the user into the system
splashScreen.UpdateStatus("Logging in...");
//my expectation is that UpdateStatus call will update the label displayed on the splash screen but it doesn't.
//Do more stuff.....
}
Does it have something to do with multi-threading or is it because im creating a new instance of splashscreen in welcome.cs before calling UpdateStatus? How would I get around this?
You could do the following
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
string[] args = Environment.GetCommandLineArgs();
// Creates the Splash
splash = new FrmSplash();
//Opens the Splash in a new Thread, this way any gifs, progress bars, lablels changes will work because the main thread isnt blocked
var t = Task.Factory.StartNew(() =>
{
splash.ShowDialog();
});
while (!splash.Created) // wait the splash screen form load process
System.Threading.Thread.Sleep(300);
UpdateSplashMessage("Loading the program... Please wait");
// Some slow initialization code.
// ...
//Close splash screen
CloseSplash();
Application.Run(args);
}
static void CloseSplash()
{
splash.Invoke(new MethodInvoker(() =>
{
splash.Close(); // Closes the splash that is running in the other thread
}));
}
static void UpdateSplashMessage(string msg)
{
splash.Invoke(new MethodInvoker(() =>
{
splash.AtualizarMensagem(msg);
}));
}
Note that you will need to create a method called AtualizarMensagem(string str) in your splash screen form, like this
public void AtualizarMensagem(string novaMsg)
{
lblCarregando.Text = novaMsg;
}
I have this code in my "useful snnipets" folder, it always works for me.
Hope this helps.
Related
I've got a WPF RichTextBox that I'd like to get working as a log output for the app.
I have a static class Log with method to write to the WPF RTB. Of course, this doesnt work when a background thread call the method.
I've tried using BeginInvoke, which works until the app gets closed throwing an error 'System.Windows.Application.Current.get returned null'
What is the proper approach to updating WPF RichText from other threads. And further, I dont think this background thread is disposing properly, any recommendations?
public partial class MainWindow : Window
{
Worker worker = new Worker();
public MainWindow()
{
InitializeComponent();
Log.rtb_control = rtbLog; // pass RTB ref to Log
worker.Start();
}
}
public static class Log
{
public static RichTextBox rtb_Control;
public static void Add(string Text)
{
App.Current.Dispatcher.BeginInvoke((Action)(() =>
{
rtb_Control.AppendText($"{Text}\r");
}
}
}
public class Worker
{
bool _Enabled = false;
public Worker()
{
_Manager = new Thread(new ThreadStart(Thread_Manager));
_Manager.Start();
}
public void Start()
{
_Enabled = true;
}
void Thread_Manager()
{
while(true)
{
if(_Enabled) { Log.Add("Inside Thread"); }
Thread.Sleep(10000);
}
}
}
I've been scratching my head on this one for a while now and despite looking for solutions, im not quite understanding the implementations (several answers on stack overflow have already been looked at)
My program loads a splash page when it is opened, during which it checks for a database connection. If there is a connection, the splash page closes and the main form loads, otherwise it provides an error message then closes completely.
public partial class StartupSplash : Form
{
ThreadStart th;
Thread thread;
public StartupSplash()
{
InitializeComponent();
th = new ThreadStart(DbAvaliable);
thread = new Thread(th);
thread.Start();
}
private void DbAvaliable()
{
Boolean result = false;
using (var connectiontest = new SqlConnection("myConnString"))
try
{
connectiontest.Open();
result = true;
}
catch (Exception ex)
{
result = false;
}
if (result)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainWindow());
}
else
{
MessageBox.Show("Unable to establish database connection. Please check your data connection and try again.");
}
}
}
I understand that I can't simply call this.Close() due to cross thread issues. I've read something about invoking methods, but im not too clear how to achieve the result above.
Initially I tried to use form load/shown events instead of alternate threads, but the image on the forms failed to load until after the messagebox had shown the error (rather than displaying, then running the connection check)
Could you set up an event to fire on Form2 with the results of the db check? Subscribe to the event on Form1 and tell it to close if the conditions warrant.
Not sure if it would work or not, but something like:
public Form2 : Form
{
public delegate void DbCheckHandler(object sender, DbEventArgs e);
public event DbCheckHandler DbCheckComplete;
//check the db
DbCheckComplete(this, new DbEventArgs { ShouldClose = true; });
}
public Form1 : Form
{
Form2 form2 = new Form2();
form2.DbCheckComplete += new DbCheckHandler(CheckDbResult);
form2.Show();
private void CheckDbResult(object sender, DbEventArgs e)
{
if(e.ShouldClose)
{
this.Close();
}
}
}
With some help from previous answers posted by Hans Passant (here and here), My solution was as follows:
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
new MyApp().Run(args);
}
class MyApp : WindowsFormsApplicationBase
{
protected override void OnCreateSplashScreen()
{
this.SplashScreen = new StartupSplash();
}
protected override void OnCreateMainForm()
{
Boolean result = false;
using (var connectiontest = new SqlConnection("myConnectionString"))
try
{
connectiontest.Open();
result = true;
}
catch (Exception ex)
{
result = false;
}
// pause not needed while checking for db connection as that takes its own amount of time to complete.
if (result)
{
System.Threading.Thread.Sleep(3000); //pause moved here to give the splash some time on screen if db connection available
this.MainForm = new MainWindow();
}
else
{
MessageBox.Show("Unable to connect to the database");
Environment.Exit(1); // shuts down the program if database connection not avaliable.
}
}
}
I'm having problems with the invokerequired method from time to time and wanted to aks if someone knows that problem or can it explain to me. I have some code:
splashScreen splashObj = splashScreen.GetInstance();
if (thrd == null)
{
thrd = new Thread(new ThreadStart(loadingScreenStart));
thrd.IsBackground = true;
thrd.Start();
}
else
{
if (splashObj.InvokeRequired)
{
splashObj.Invoke((MethodInvoker)delegate()
{
splashObj.Show();
}
);
}
else
{
splashObj.Show();
}
}
if (splashObj.loadLabel.InvokeRequired)
{
splashObj.loadLabel.Invoke((MethodInvoker)delegate()
{
splashObj.loadLabel.Text = "Checking Username...";
}
);
}
else
{
splashObj.loadLabel.Text = "Checking Username...";
}
public void loadingScreenStart()
{
splashScreen splashObj = splashScreen.GetInstance();
Application.Run(splashScreen.GetInstance());
}
the splashscreen code:
public partial class splashScreen : Form
{
public Form1 form1;
public splashScreen()
{
InitializeComponent();
//form1 = frm1;
}
public splashScreen(Form1 frm1)
{
form1 = frm1;
}
private void splashScreen_Load(object sender, EventArgs e)
{
}
private static splashScreen m_instance = null;
private static object m_instanceLock = new object();
public static splashScreen GetInstance()
{
lock (m_instanceLock)
{
if (m_instance == null)
{
m_instance = new splashScreen();
}
}
return m_instance;
}
}
So I'm showing a splashscreen while the rest of the code is loading in the background. Messages are getting showed like "Checking Username...", "Checking Password...", "Loading..." and so on.
This code works fine but from time to time it seems like the code execution is faster than the thread and then I get an error that the method was called from an other thread than the one it was created on. Why does this happen? Is there any solution for this kind of problem? That happens maybe once in twenty executions.
Multi-threading is hard, so you should try making it as simple as possible. One, there's no reason to have any of the splashscreen code outside of the splashscreen class itself. Second, you should always know what you're doing, so InvokeRequired is just a code smell that says "I have no idea who's going to call this method".
void Main()
{
SplashScreen.ShowText("Loading 1");
Thread.Sleep(1000);
SplashScreen.ShowText("Loading 2");
Thread.Sleep(2000);
SplashScreen.Done();
Thread.Sleep(2000);
SplashScreen.ShowText("Loading 3");
}
// Define other methods and classes here
public partial class SplashScreen : Form
{
private static SplashScreen instance;
private static readonly ManualResetEvent initEvent = new ManualResetEvent(false);
Label loadLabel;
private SplashScreen()
{
// InitializeComponent();
loadLabel = new Label();
Controls.Add(loadLabel);
Load += (s, e) => initEvent.Set();
Closing += (s, e) => initEvent.Reset();
}
private static object syncObject = new object();
private static void InitializeIfRequired()
{
// If not set, we'll have to init the message loop
if (!initEvent.WaitOne(0))
{
lock (syncObject)
{
// Someone initialized it before us
if (initEvent.WaitOne(0)) return;
// Recreate the form if it was closed
instance = new SplashScreen();
var thread = new Thread(() => { Application.Run(instance); });
thread.Start();
// Wait until the form is ready
initEvent.WaitOne();
}
}
}
public static void ShowText(string text)
{
InitializeIfRequired();
instance.Invoke((Action)(() =>
{
if (!instance.IsDisposed) instance.loadLabel.Text = text;
}
));
}
public static void Done()
{
// Is it closed already?
if (!initEvent.WaitOne(0)) return;
lock (syncObject)
{
// Someone closed it before us
if (!initEvent.WaitOne(0)) return;
instance.Invoke((Action)(() => { instance.Close(); }));
}
}
}
This is a snippet for LINQPad, you'll want to remove the comment on InitializeComponent as well as the loadLabel control (which should be on the designer instead).
This way, you have the logic of the splash screen completely isolated from the rest of the application. To show a splashscreen text, just call SplashScreen.ShowText. To make the splashscreen go away, call SplashScreen.Done.
Note how there's no need to use InvokeRequired - there's no way any legitimate call of SplashScreen.ShowText (or Done) would ever not need marshalling to the UI thread of the splash screen.
Now, this is not perfect. It's something I wrote in about 10 minutes. But it's (probably :)) thread-safe, follows best practices a lot better than the original, and is much easier to use.
Also, there's cases where I would use higher-level constructs, e.g. Lazy<T> and Task - but since that wouldn't really help here (starting the new messaging loop, having to recreate the form if it was closed...), I opted for the simpler solution instead.
Note that I'm using Invoke rather than BeginInvoke or similar - this is quite important, because otherwise it would be possible to queue a close, followed by a ShowText that would work on a disposed form. If you intend to call ShowText from multiple threads, it would be safer to also lock around the whole ShowText body. Given the usual use case for splash screens, there's some thread-safety that's unnecessary, but...
Ok so this problem still exists, it can happen when your thread is calling the form before it shows; both InvokeRequired and BeginInvoke may fail.
A solution is to start your thread on form event Shown:
/// <summary>
/// Testing form
/// </summary>
public partial class MyForm : Form
{
/// <summary>
/// Work work
/// </summary>
private Thread _MyThread;
/// <summary>
/// New test form
/// </summary>
public MyForm()
{
// Initialize components
this.InitializeComponent();
}
/// <summary>
/// First time form is show
/// </summary>
private void MyForm_Shown(object sender, EventArgs e)
{
// Create and start thread
this._MyThread = new Thread(this.MyThreadWork);
this._MyThread.Start();
}
/// <summary>
/// Doing some work here
/// </summary>
private void MyThreadWork()
{
// Counting time
int count = 0;
// Forever and ever
while (true)
{
this.PrintInfo((count++).ToString());
Thread.Sleep(1000);
}
}
/// <summary>
/// Printing info into form title
/// </summary>
private void PrintInfo(string info)
{
// Needs to sync?
if (this.InvokeRequired)
{
// Sync me
this.BeginInvoke(new Action<string>(this.PrintInfo), info);
}
else
{
// Prints info
this.Text = info;
}
}
/// <summary>
/// Good bye form, don't miss the work work
/// </summary>
private void MyForm_FormClosed(object sender, FormClosedEventArgs e)
{
// Kill worker
this._MyThread.Abort();
}
}
Some will just add a Thread.Sleep before first PrintInfo but common, what sleep time do you would use? 1, 10, 100? Depends on how fast your form will show.
I´m uses a thread to show wait screen. User click on button process, system create a thread to show the wait screen and the process continue and i need to show the methods that the process has invoked.
public partial class Frm_Muestra_Consulta : Form
{
private void Btn_Procesa_Click(object sender, EventArgs e)
{
Cls_Generales.Iniciar_Proceso(this);//calls the warning screen processing
Cls_Consultas.Consultando(); //running process
Cls_Generales.Terminar_Proceso(this); //Close the warning screen processing
}
}
public class Cls_Generales
{
public static void Inicia_Proceso(Form _Form)
{
Pantalla_Proceso = new Thread(new
ThreadStart(Invoca_Pantalla_Proceso));
Pantalla_Proceso.Start();
_Form.Enabled = false;
}
public static void Invoca_Pantalla_Proceso()
{ Application.Run(new Frm_Procesando()); }
public static void Termina_Proceso(Form _Form)
{ Pantalla_Proceso.Abort(); }
}
public class Cls_Consultas
{
public static DataTable Consultando()
{
//Methos to exemple
//What I want is to tell the user when the system enters and
//leaves this method
Limpiar_Tabla();
Calcular_Espacio(); //and next
return Realizar_Consulta(); //and next
}
}
How do I make the yarn see or read these methods?
My Solution:
So I managed to find another tutorial http://www.codeproject.com/KB/dotnet/Yet_Another_Splash_Screen.aspx and the sourcecode seemed to make more sense to me. Here is the code i'm using now. Main() is left untouched.
Splash.cs
`
public partial class Frm_Splash : Form
{
delegate void ProgressDelegate(int percent);
delegate void SplashShowCloseDelegate();
/// <summary>
/// To ensure splash screen is closed using the API and not by keyboard or any other things
/// </summary>
bool CloseSplashScreenFlag = false;
/// <summary>
/// Base constructor
/// </summary>
///
public Frm_Splash()
{
InitializeComponent();
progress_Splash.Show();
this.ClientSize = this.BackgroundImage.Size;
}
public void ShowSplashScreen()
{
if (InvokeRequired)
{
// We're not in the UI thread, so we need to call BeginInvoke
BeginInvoke(new SplashShowCloseDelegate(ShowSplashScreen));
return;
}
this.Show();
Application.Run(this);
}
/// <summary>
/// Closes the SplashScreen
/// </summary>
public void CloseSplashScreen()
{
if (InvokeRequired)
{
// We're not in the UI thread, so we need to call BeginInvoke
BeginInvoke(new SplashShowCloseDelegate(CloseSplashScreen));
return;
}
CloseSplashScreenFlag = true;
this.Close();
}
/// <summary>
/// Update text in default green color of success message
/// </summary>
/// <param name="Text">Message</param>
public void Progress(int percent)
{
if (InvokeRequired)
{
// We're not in the UI thread, so we need to call BeginInvoke
BeginInvoke(new ProgressDelegate(Progress), new object[] { percent });
return;
}
// Must be on the UI thread if we've got this far
progress_Splash.Value = percent;
// Fade in the splash screen - looks pro. :D
if (percent < 10)
this.Opacity = this.Opacity + .15;
}
/// <summary>
/// Prevents the closing of form other than by calling the CloseSplashScreen function
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SplashForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (CloseSplashScreenFlag == false)
e.Cancel = true;
}
}`
Form1.cs
public partial class Frm_Main : Form
{
Frm_Splash frm_Splash = new Frm_Splash();
public Frm_Main()
{
this.Hide();
Thread splashthread = new Thread(new ThreadStart(frm_Splash.ShowSplashScreen));
splashthread.IsBackground = true;
splashthread.Start();
InitializeComponent();
CenterToScreen();
}
private void Frm_Main_Load(object sender, EventArgs e)
{
if (PassedAll() == true)
FillMovieLB();
if (FillMovieProgress == 100)
{
//Throw in this sleep so the user can see the progress bar reach all the way to the end.
Thread.Sleep(1000);
this.Show();
frm_Splash.CloseSplashScreen();
this.Activate();
}
}
Original Question
G'day all,
I'm very new to programming in C# and i'm having a problem with the http://www.codeproject.com/KB/cs/prettygoodsplashscreen.aspx tutorial and implementing it within my application. I'm finding it a little difficult to understand what the problem is. I know there is alot of stuff about getting this splash screen to work but I can't get my head around it.
When I start the program, the Frm_Main will display, you can see the listbox being populated, because i've placed it in BackgroundWorker.DoWork(), and then afterwards my frm_Splash will show after the work is done. Obviously, the way it should be working is, frm_Splash will show during the work being done on Frm_Main, and the progress bar will show the progress of the loading (this part I haven't implemented yet).
Edit: I may not have been clear, but the question is: How can I get my splashscreen to display while the work is being done and before the main form is displayed?
Thanks everybody. :)
Here is my code:
static Frm_Splash frm_Splash = new Frm_Splash();
public delegate void ShowFormDelegate();
public void ShowForm()
{
frm_Splash.Show();
}
public Frm_Main()
{
InitializeComponent();
CenterToScreen();
if (PassedAll() == true)
{
back_loadprog.RunWorkerAsync();
}
}
private void back_loadprog_DoWork(object sender, DoWorkEventArgs e)
{
Invoke(new ShowFormDelegate(ShowForm));
Invoke(new FillMovieLBDelegate(FillMovieLB));
}
Here, have some code... Works for me.
Splash Form:
namespace Screens.Forms
{
public partial class Splash : DevExpress.XtraEditors.XtraForm
{
public Splash()
{
InitializeComponent();
}
string RandomLoadingMessage()
{
string[] lines ={
"Pripremam warp pogon",
"Moj drugi ekran za učitavanje je brži, probaj njega",
"Verzija programa koju imam u testiranju imala je smiješnije poruke"
};
return lines[new Random().Next(lines.Length)];
}
public void RandomizeText()
{
lblMessage.Text = RandomLoadingMessage();
}
private void Splash_Load(object sender, EventArgs e)
{
RandomizeText();
}
private static Splash _splash;
private static bool _shouldClose;
static void ThreadFunc()
{
_splash = new Splash();
_splash.Show();
while (!_shouldClose)
{
Application.DoEvents();
Thread.Sleep(100);
if (new Random().Next(1000) < 10)
{
_splash.Invoke(new MethodInvoker(_splash.RandomizeText));
}
}
for (int n = 0; n < 18; n++)
{
Application.DoEvents();
Thread.Sleep(60);
}
if (_splash != null)
{
_splash.Close();
_splash = null;
}
}
static public void ShowSplash()
{
_shouldClose = false;
Thread t = new Thread(ThreadFunc);
t.Priority = ThreadPriority.Lowest;
t.Start();
}
internal static void RemoveSplash()
{
_shouldClose = true;
}
internal static void ShowSplash(List<string> fromTwitterMessages)
{
ShowSplash();
}
}
}
Show it with:
Splash.ShowSplash();
Do the work you need, then when done:
Splash.RemoveSplash();
You need to take this a step further back to your Main() function of the application.
In general you could do this:
Create a ManualResetEvent or better ManualResetEventSlim if you are on .NET 4
Start a new thread displaying your SplashScreen, use Application.Run
In your SplashScreen you should create a time which polls the created ManualResetEvent
frequently, a nice animation could be placed here also
If the event is set you should close the form
Back in your Main() do your stuff like creating forms etc.
When finish set the event, so that the SplashScreen can be closed
To be sure that your MainForm is not shown before your SplashScreen is closed you can use another event