I got to load some data out of a db4o database which takes 1 or 2 seconds at the startup of my app, the rest has to wait because first of all the data has to be loaded. doing this in an own thread would mean that the rest has to wait for the thread-finishing. I'd like to do a splash screen or something during the data is loaded for what also need an own thread, right? how would you do?
I'm using csharp, .net 3.5 and winforms
Showing a splash screen at startup is easy to do. In your application's Main() method (in Program.cs), put something like this before the Application.Run(...) line:
SplashForm splashy = new SplashForm();
splashy.Show();
Application.Run(new MainForm(splashy));
Modify the code and constructor for your main form so that it looks something like this:
private SplashForm _splashy;
public MainForm(SplashForm splashy)
{
_splashy = splashy;
InitializeComponent();
}
Then at the end of your MainForm's Load event (which presumably contains the database code), put this code:
_splashy.Close();
_splashy.Dispose();
If you choose to do your database access with a separate Thread or BackgroundWorker, then you don't really need a splash screen so much as you need some sort of progress indicator form that appears while the BackgroundWorker is doing its thing. That would be done differently from my answer here.
One way, probably better ways though. Create a new dialog form that will be your progress window/splash screen. Throw a bitmap or whatever on it as the only item. Instantiate the dialog from your main program. Override the Load event for the progress form and from there launch the new thread that will do the background processing work for loading up the data. This way you can just call ShowDialog from your main app.
if you use System.ComponentModel.BackgroundWorker then you can easily wire up events for when the thread completes and automaticaly exit the dialog from that event. Control is returned back to the calling application and you're done.
I've done this sort of thing in an application before and it works fine but I'm sure it's a novice approach. Here's sample code from the Load event in the form that launches the background thread (in my case I'm opening and parsing large files):
private void FileThreadStatusDialog_Load(object sender, EventArgs e)
{
Cursor = Cursors.WaitCursor;
if (m_OpenMode)
{
this.Text = "Opening...";
StatusText.Text = m_FileName;
FileThread = new BackgroundWorker();
FileThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(FileThread_RunWorkerCompleted);
FileThread.DoWork += new DoWorkEventHandler(FileOpenThread_DoWork);
FileThread.WorkerSupportsCancellation = false;
FileThread.RunWorkerAsync();
}
else
{
this.Text = "Saving...";
StatusText.Text = m_FileName;
FileThread = new BackgroundWorker();
FileThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(FileThread_RunWorkerCompleted);
FileThread.DoWork += new DoWorkEventHandler(FileSaveThread_DoWork);
FileThread.WorkerSupportsCancellation = false;
FileThread.RunWorkerAsync();
}
}
And here's what the work completed method looks like which exist the form:
private void FileThread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
FileThread = null;
DialogResult = DialogResult.OK;
Close();
}
Here's how I open up the progress dialog from the main dialog:
FileThreadStatusDialog thread = new FileThreadStatusDialog(m_Engine, dlg.FileName, true);
if (thread.ShowDialog(this) == DialogResult.OK)
{
m_Engine = thread.Engine;
FillTree();
}
One might want to force drawing of splashy in MusiGenesis' answer by adding
Application.DoEvents();
immediately after
splashy.Show();
Related
I am trying to start a winForm from a thread, but when i do so, the form show but none of the labels are loaded ( the background where they should be is white ) and the form is frozen.
I've tried it with some other winForms that i know that work just fine and it still doesn't seem to work ? Has anyone encountered this problem ?
I know the question is vague but there isn't really any specific code that I could give to help understand the problem.
That is because the Message Loop runs on UI thread only. And when a control or window is created in any other thread, it cannot access that message loop. And thus, cannot process user input.
To solve this, try creating a window from UI thread and create a thread from that window to do whatever you want to do in different thread.
UI thread is supposed to be one.
Then, I suggest you to open your form calling a method of your original form thread, like in the example below:
(To test it just create an empty form called MainForm and paste this code in it)
public delegate void OpenFormDelegate(string txt);
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
var button1 = new Button();
button1.Text = "Run for 5 secs and open new window";
button1.Dock = DockStyle.Top;
this.Controls.Add(button1);
button1.Click += new EventHandler(button1_Click);
}
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(Run));
t.Start();
}
public void Run()
{
Thread.Sleep(5000); // sleep for 5 seconds
this.BeginInvoke(new OpenFormDelegate(OpenNewForm), "Hello World !");
}
public void OpenNewForm(string text)
{
Form f = new Form();
f.Text = text;
f.Show();
}
}
It is related to thread access, when the new form is created it will not be able to access the UI thread. Use the main thread to create the form and new thread to process the infomation.
I've got the following program flow in my Windows Forms application (WPF is not a viable option unfortunately):
The GUI Thread creates a splash screen and a pretty empty main window, both inheriting Form.
The splash screen is shown and given to Application.Run().
The splash screen will send an event which triggers an async Event Handler which performs initialization, using the IProgress interface to report progress back to the GUI. (This works flawlessly.)
At some point during the initialization, I need to dynamically create GUI components based on information provided by certain plugins and add them to the Main Window.
At this point I'm stuck: I know I need to ask the GUI thread to create those components for me, but there is no Control I could call InvokeRequired on. Doing MainWindow.InvokeRequired works neither.
The only idea I could come up with was to fire an event which is connected to a factory in the GUI Thread, and then wait for that factory to fire another event which provides the created controls. However I am pretty sure there is a more robust solution. Does anyone know how to achieve this?
Using the comments on my question, especially the note about the continuation method which made me find this very useful question, I achieved the following:
The first part of initialization is performed asynchronously (no change).
The second part of the initialization (which creates the UI elements) is performed afterwards as a Continuation Task, in the context of the UI thread.
Apart from the rather short GUI initialization part, the Splash Screen is responsive (i.e. the mouse cursor does not change to "Waiting" once it hovers the Splash Screen).
Neither of the initialization routines knows the splash screen at all (i.e. I could easily exchange it).
The core controller only knows the SplashScreen interface and does not even know it is a Control.
There currently is no exception handling. This is my next task but doesn't affect this question.
TL;DR: The code looks somewhat like this:
public void Start(ISplashScreen splashScreen, ...)
{
InitializationResult initializationResult = null;
var progress = new Progress<int>((steps) => splashScreen.IncrementProgress(steps));
splashScreen.Started += async (sender, args) => await Task.Factory.StartNew(
// Perform non-GUI initialization - The GUI thread will be responsive in the meantime.
() => Initialize(..., progress, out initializationResult)
).ContinueWith(
// Perform GUI initialization afterwards in the UI context
(task) =>
{
InitializeGUI(initializationResult, progress);
splashScreen.CloseSplash();
},
TaskScheduler.FromCurrentSynchronizationContext()
);
splashScreen.Finished += (sender, args) => RunApplication(initializationResult);
splashScreen.SetProgressRange(0, initializationSteps);
splashScreen.ShowSplash();
Application.Run();
}
It is much easier to manage multiple forms and display one while the other is working or being constructed.
I suggest you try the following:
When application is started you create splash screen form so your Program.cs is like this
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new SplashForm());
}
Inside the splash form constructor, create a new thread (I will use BackgroundWorker but there are other options like tasks) to build your main form.
public SplashForm()
{
InitializeComponent();
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
backgroundWorker1.RunWorkerAsync();
}
Now we need to write the SplashForm member functions to tell background worker what to do
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
// Perform non-GUI initialization - The GUI thread will be responsive in the meantime
// My time consuming operation is just this loop.
//make sure you use worker.ReportProgress() here
for (int i = 1; (i <= 10); i++)
{
if ((worker.CancellationPending == true))
{
e.Cancel = true;
break;
}
else
{
System.Threading.Thread.Sleep(500);
worker.ReportProgress((i * 10));
}
}
SetVisible(false);
MainForm mainForm = new MainForm();
mainForm.ShowDialog();
//instead of
//this.Visible = false;
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.progressBar1.Value = e.ProgressPercentage;
}
You might have noticed by now, I am using another member function to hide the splash screen. It is because you are now in another thread and you can't just use this.visible = false;. Here is a link on the matter.
delegate void SetTextCallback(bool visible);
private void SetVisible(bool visible)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetVisible);
this.Invoke(d, new object[] { visible });
}
else
{
this.Visible = visible;
}
}
When I run this sample project it shows the progress bar and then loads the MainForm windows form after hiding the SplashForm.
This way you can put any controls that you might need inside the MainForm constructor. The part you shortened as // Perform GUI initialization afterwards in the UI context should go into MainForm constructor.
Hope this helps.
I would like to implement a simple popup window in Windows Forms, which will show a simple timer to the user while some slow-running process is executing. The premise is simple; show to the user that something is indeed going on and the application is not frozen. Note that this slow-running process is not a loop, nor is it something that I can tap into.
What I want is a simple popup window, showing some message along the lines "Elapsed time: x seconds", where x is incremented every second.
The basic concept is the following:
public void test()
{
//Some code which does stuff
//Popup window with counter
//Perform long running process
//Close popup window with counter
//Some other code which does other stuff
}
I tried to do it using various ways, including background workers, threads, and of course timers. But I did not manage to make it work as I wanted. And I would prefer not to post any of my code so as not to "lead" the responses to a specific way of doing this.
So what would be the best way to do this work?
Thanks.
UPDATE:
In reply to some comments, since I cannot paste any code in the replies section, I'm editing my original question to accomodate this. One of the implementations that I tried is to spawn the popup window in a separate thread. Although I got no runtime errors, the popup window did not refresh correctly. It indeed poped-up, but no text would show within it, and the counter would not refresh. Here's the code:
private void test()
{
frmProgressTimer ofrmProgressTimer = new frmProgressTimer(); //Instance of popup Form
System.Threading.Tasks.Task loadTask = new System.Threading.Tasks.Task(() => ProgressTimer(ofrmProgressTimer));
loadTask.Start();
//Perform long running process
System.Threading.Tasks.Task cwt = loadTask.ContinueWith(task => EndProgressTimer(ofrmProgressTimer));
}
private void ProgressTimer(frmProgressTimer ofrmProgressTimer)
{
ofrmProgressTimer.Show();
ofrmProgressTimer.Invoke((Action)(() =>
{
ofrmProgressTimer.startTimer();
}));
}
private void EndProgressTimer(frmProgressTimer ofrmProgressTimer)
{
ofrmProgressTimer.Invoke((Action)(() =>
{
ofrmProgressTimer.stopTimer();
ofrmProgressTimer.Close();
}));
}
And here's my popup form code:
public partial class frmProgressTimer : Form
{
private int counter = 0;
private Timer timer1;
public frmProgressTimer()
{
InitializeComponent();
timer1 = new Timer();
timer1.Interval = 1000;
timer1.Tick += new EventHandler(timer1_Tick);
}
public void startTimer()
{
timer1.Start();
}
public void stopTimer()
{
timer1.Stop();
}
private void timer1_Tick(object sender, EventArgs e)
{
counter += 1;
labelText.Text = counter.ToString();
}
}
This is actually quite easy to do. Create your dialog, define your long running operation to take place in a non-UI thread when it is shown, add a continuation to that operation which closes the dialog when the task finishes, and then show the dialog.
MyDialog dialog = new MyDialog();
dialog.Shown += async (sender, args) =>
{
await Task.Run(() => DoLongRunningWork());
dialog.Close();
};
dialog.ShowDialog();
The code to have the ticking over time should be entirely contained within the dialog, and based on the question it seems you already have that well under control with a simple Timer.
Make a new form, which will pop up, and show a timer. That way it won't be interrupted with all the work on your main form, and the timer will work continuously.
Remember when showing a new from to use newForm.ShowDialog() not newForm.Show(). Your can google the differences
I would simply start your work on a separate thread. Launch a modal form with your timer output. To display the timer use an actual timer instance set to update every second. When the timer event fire update your dialog.
Finally once you're thread completes close the dialog so your main form is active again.
First of all you need to make it not closeable by the user (as if modal dialogs weren't annoying enough) but closeable by your code. You could accomplish this by subscribing to the FormClosing event of the form. Let's say your popup form's name is Form2:
private bool mayClose = false;
public void PerformClose()
{
this.mayClose = true;
this.Close();
}
private void Form2_FormClosing(object sender, FormClosingEventArgs e)
{
if (!this.mayClose)
e.Cancel = true;
}
Create a Timer, provide a Tick event handler, enable it and set its Interval to 500 milliseconds:
Create a label to host your desired text. Let's call it label1.
Within and surrounding your Tick event handler do something like this:
private DateTime appearedAt = DateTime.UtcNow;
private void timer1_Tick(object sender, EventArgs e)
{
int seconds = (int)(DateTime.UtcNow - this.appearedAt).TotalSeconds;
this.label1.Text = string.Format(#"Ellapsed seconds: {0}", seconds);
}
Make sure your long running process is happening on a background thread, not on the GUI thread.
Say your long running process can be thought of as the execution of a method called MyProcess.
If that is the case, then you need to call that method from a secondary thread.
// PLACE 1: GUI thread right here
Thread thread = new Thread(() =>
{
// PLACE 2: this place will be reached by the secondary thread almost instantly
MyProcess();
// PLACE 3: this place will be reached by the secondary thread
// after the long running process has finished
});
thread.Start();
// PLACE 4: this place will be reached by the GUI thread almost instantly
Show the form right before the long running process starts. This can be done in any of the 2 places (marked in the previous section of code) called PLACE1 or PLACE2. If you do it in PLACE2 you will have to marshal a call back to the GUI thread in order to be able to interact with the WinForms framework safely. Why am I bringing this up ? It's because maybe the long running process is not started from within the GUI thread at all and you absolutely need to do this.
Close the form right after the long running process finishes. This can be done only in PLACE3 and you absolutely need to marshal a call.
To wrap the earlier 2 bullets and the answer, you could do this:
private void DoIt()
{
Form2 form2 = new Form2();
Action showIt = () => form2.Show();
Action closeIt = () => form2.PerformClose();
// PLACE 1: GUI thread right here
Thread thread = new Thread(() =>
{
form2.BeginInvoke(showIt);
// PLACE 2: this place will be reached by the secondary thread almost instantly
MyProcess();
form2.BeginInvoke(closeIt);
// PLACE 3: this place will be reached by the secondary thread
// after the long running process has finished
});
thread.Start();
// PLACE 4: this place will be reached by the GUI thread almost instantly
}
Finally I've managed to resolve this in the most simplistic manner. And it works like a charm. Here's how to do it:
//Create an instance of the popup window
frmProgressTimer ofrmProgressTimer = new frmProgressTimer();
Thread thread = new Thread(() =>
{
ofrmProgressTimer.startTimer();
ofrmProgressTimer.ShowDialog();
});
thread.Start();
//Perform long running process
ofrmProgressTimer.Invoke((Action)(() =>
{
ofrmProgressTimer.stopTimer();
ofrmProgressTimer.Close();
}));
You can see the code for the popup window in the original post/question, with the only difference that the tick function changes the label text as:
labelText.Text = string.Format("Elapsed Time: {0} seconds.", counter.ToString());
Thank you to everybody for trying to help me out.
I am trying to start a winForm from a thread, but when i do so, the form show but none of the labels are loaded ( the background where they should be is white ) and the form is frozen.
I've tried it with some other winForms that i know that work just fine and it still doesn't seem to work ? Has anyone encountered this problem ?
I know the question is vague but there isn't really any specific code that I could give to help understand the problem.
That is because the Message Loop runs on UI thread only. And when a control or window is created in any other thread, it cannot access that message loop. And thus, cannot process user input.
To solve this, try creating a window from UI thread and create a thread from that window to do whatever you want to do in different thread.
UI thread is supposed to be one.
Then, I suggest you to open your form calling a method of your original form thread, like in the example below:
(To test it just create an empty form called MainForm and paste this code in it)
public delegate void OpenFormDelegate(string txt);
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
var button1 = new Button();
button1.Text = "Run for 5 secs and open new window";
button1.Dock = DockStyle.Top;
this.Controls.Add(button1);
button1.Click += new EventHandler(button1_Click);
}
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(Run));
t.Start();
}
public void Run()
{
Thread.Sleep(5000); // sleep for 5 seconds
this.BeginInvoke(new OpenFormDelegate(OpenNewForm), "Hello World !");
}
public void OpenNewForm(string text)
{
Form f = new Form();
f.Text = text;
f.Show();
}
}
It is related to thread access, when the new form is created it will not be able to access the UI thread. Use the main thread to create the form and new thread to process the infomation.
EDIT 2
Okay, based on the advice on the answers below I eliminated my thread approach and now my program looks like this:
program.cs
static void Main(){
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
FrmWWCShell FrmWWCShell = null;
var splash = new FrmSplash();
splash.SplashFormInitialized += delegate
{
FrmWWCShell = new FrmWWCShell();
splash.Close();
};
Application.Run(splash);
Application.Run(FrmWWCShell);
}
And FrmSplash.cs like this:
public partial class FrmSplash : Form
{
public FrmSplash()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
splashTimer.Interval = 1;
splashTimer.Tick +=
delegate { if (SplashFormInitialized != null) SplashFormInitialized(this, EventArgs.Empty); };
splashTimer.Enabled = true;
}
public event EventHandler SplashFormInitialized;
}
The problem is that it doesn't work at all now. The splash screen pops up for a split second, the marque progress bar never even initializes, and then disappears while I wait the 10 secs for the dll's and Main Form to show up while staring at nothing....
Color me severely confused now!
ORIGINAL POST--> for reference
I implemented a App Loading splash screen that operates on a seperate thread while all of the dll's are loading and the form is getting "Painted." That works as expected. What is strange is that now when the Splash form exits it sends my main Form to the back, if there is anything else open(i.e. Outlook). I start the thread in Program.cs,
static class Program
{
public static Thread splashThread;
[STAThread]
static void Main()
{
splashThread = new Thread(doSplash);
splashThread.Start();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new FrmWWCShell());
}
private static void doSplash()
{
var splashForm = new FrmSplash();
splashForm.ShowDialog();
}
}
And then I end it once my FrmSearch_Shown event is fired.
private void FrmSearch_Shown(object sender, EventArgs e)
{
Program.splashThread.Abort();
this.Show();
this.BringToFront();
}
I, as you can see, have tried calling a Show() and/or BringToFront() on FrmSearch and it still "jumps" to the back.
What am I missing?
What else can I try?
Am I doing this so horribly ignorant that it is my process that is causing this?
Should I file for early retirement?
Thanks for any insight!
EDIT 1
I tried setting the TopMost Property on my Main Form to TRUE. This keeps my form from hiding but it also keeps the user from looking at any other app. Seems a little narcissistic of me...
First of all, it's very important that UI work is done on the primary application thread. I'm actually kind of surprised that you're not getting more serious errors already by showing the splash screen on a background thread.
Here's a technique I've used:
Use Application.Run on your splash form rather than your "real" form.
In your splash form, have an initialized event:
public event EventHandler SplashFormInitialized
Create a timer that fires in one millisecond, and triggers that event.
Then in your application run method you can load your real form, then close your splash form and do an Application.Run on the real form
var realForm = null;
var splash = new SplashForm();
splash.SplashFormInitialized += delegate {
// As long as you use a system.windows.forms.Timer in the splash form, this
// handler will be called on the UI thread
realForm = new FrmWWCShell();
//do any other init
splash.Close();
}
Application.Run(splash); //will block until the splash form is closed
Application.Run(realForm);
The splash might include:
overrides OnLoad(...)
{
/* Using a timer will let the splash screen load and display itself before
calling this handler
*/
timer.Interval = 1;
timer.Tick += delegate {
if (SplashFormInitialized != null) SplashFormInitialized(this, EventArgs.Empty);
};
timer.Enabled = true;
}
Try calling Application.DoEvents() right after show.
Warning: do not call DoEvents very often, but this is one of those time.
EDIT: Clyde noticed something I did not: you are threading this. Don't run any UI on another thread. Take out the thread, leave in the Application.DoEvents().