Before my main form loads it asks the user to check for updates. When they click ok i make the main form show and make a panel that contains some labels and a picture box with an animated gif.
The animated gif is not moving which normally is because the main thread is busy but I have threaded the thread doing the work and no luck getting the animation to play.
Here is what I have.
Thread CheckVersion = new Thread(new ThreadStart(VersionCheck));
this.Show(); //bring up the main form
this.BringToFront();
pCheckingVersions.Visible = true; //this contains the animated gif
Application.DoEvents(); //refresh ui so my box
CheckVersion.Start(); //start thread
CheckVersion.Join(); //wait for thread to exit before moving on
pDownloading.Visible = false;
The problem is that Thread.Join() is going to block the calling thread until the thread you are waiting on completes.
Instead you should use an asynchronous model for this kind of activity. A BackgroundWorker would be ideal here:
class MyForm
{
private BackgroundWorker _backgroundWorker;
public Myform()
{
_backgroundWorker = new BackgroundWorker();
_backgroundWorker.DoWork += CheckVersion;
_backgroundWorker.RunWorkerCompleted += CheckVersionCompleted;
// Show animation
// Start the background work
_backgroundWorker.DoWork();
}
private void CheckVersion()
{
// do background work here
}
private CheckVersionCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// hide animation
// do stuff that should happen when the background work is done
}
}
This is just a rough example of an implementation, but similar to many I have done in the past.
The CheckVersion.Join() call is making your UI thread wait for the CheckVersion thread to complete, which blocks. That makes the GIF animation pause.
Try using the BackgroundWorker class, and use the RunWorkerCompleted event to signal to your UI thread that the background operation is done.
Related
I am currently trying to update a progress bar that is defined in another window that is created by a different thread.
I need to send progress updates from the main thread in order to have the correct value displayed on progress bar.
Any help is appreciated, thanks in advance. Below is the code:
private void StartLoadingWindow(object sender, RoutedEventArgs e)
{
var t = new Thread(ThreadLoadingWindow);
t.SetApartmentState(ApartmentState.STA); //Mandatory
t.Start();
}
private void ThreadLoadingWindow()
{
var w = new LoadingWindowsControl();
w.Closed += (sender, args) =>
{
//Exit Dispatcher when Window closes
Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.SystemIdle);
Dispatcher.Run();
};
w.ShowDialog();
}
Inside the LoadingWindowsControl I have created a simple mvvm progress bar.
The typical way would be to use a Progress<T> object. If this is created on the UI thread it will invoke the ProgressChanged on the UI thread. Attach an event handler to this that updated your progress bar.
It looks however like you are creating multiple UI threads, and this is probably not a good idea. If you want to do something in the background you should use Task.Run, await the result, and update the UI. If you need continuous update you should follow the Progress<T> example. Create an object that you inform of the updates, and let it post the update work to the the UI thread.
at the moment I got this
UPDATED: Thanks for all the answers.
private void Form1_Load(object sender, EventArgs e)
{
//hide() doesnt help
Thread init = new Thread(InitApplication);
init.IsBackground = true;
init.Start();
}
InitApplication takes at least 5+ seconds to complete and write in all the settings. I want my splashScreen to stay up until then.
ADDED:
private readonly SplashScreen _splash;
public Form1(SplashScreen splashy)
{
_splash = splashy;
InitializeComponent();
}
and I got
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
SplashScreen splashy = new SplashScreen();
splashy.Show();
Application.DoEvents();
Application.Run(new Form1(splashy));
}
It is doing what its suppose to do, However, Now I see form1 on top of the splashform. Where can I add the hide and show method so it only the splash screen is shown and form1 popups when its fully loaded.
Try loading the splash screen from the UI thread, then use a background worker to execute InitApplication(). The background worker can communicate progress to the UI thread.
BackgroundWorker Class on MSDN
The BackgroundWorker class allows you to run an operation on a separate, dedicated thread. Time-consuming operations like downloads and database transactions can cause your user interface (UI) to seem as though it has stopped responding while they are running. When you want a responsive UI and you are faced with long delays associated with such operations, the BackgroundWorker class provides a convenient solution.
You can use Async Await, Background worker or Thread for initializing your app etc. These are written in the sequence of easy to use and the general pattern being followed.
Use a normal windows form and there use progress bar, gif image etc. what you like most there. On the form load or form shown event start the background task and when it finishes, close the normal form and load your main application form.
If your InitApplication has any call that deals with GUI, you will have to change that call and Invoke the desired action on GUI thread.
I have a main UI that doing some time-consuming work. When it is executing, I would like to open a second form with a progress bar (marquee style) to indicate "working on it".
I have seen people putting the time-consuming task in the BackgroundWorker, however, I would like to run in the main UI thread.
The time-consuming task will be executed in MainForm. I would like to reuse the progress bar for various process, so I am writing a second form ProgressBarForm with BackgroundWorker in it, that would start the _mainWork at the same time as showing progress bar, and will stop and close the ProgressBarForm when _mainWork is done.
Because forms are modals, I am thinking of showing ProgressBarForm in the BackgroundWorker in order not to block MainForm.
Please note that I am not running mainForm in BackgroundWorker. My backgroundWorker just show the form and perhaps report a timer.
public partial class ProgressBarFom : UControl
{
public delegate void MainWork();
private MainWork _mainWork;
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//progressBar.Hide();
this.OnClose(sender, e);
//
backgroundWorker.Dispose();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//show this ProgressBarForm
this.ShowDialog();
//stop backgroundWorker
//calling this.Close() in RunWorkerComplete
if (backgroundWorker.CancellationPending == true)
{
e.Cancel = true;
return;
}
}
public void CallProgressBar(object sender, EventArgs e)
{
//progressBar.Show();
backgroundWorker.RunWorkerAsync();
_mainWork();
if (backgroundWorker.IsBusy)
backgroundWorker.CancelAsync();
}
}
In MainForm, I am passing mainwork and call ExecWithProgressBar
private void ExecWithProgressBar()
{
ProgressBarFom .MainWork mainWork = new ProgressBarFom .MainWork(ProgressBarMainWork);
ProgressBarFom prBar = new ProgressBarFom (mainWork);
prBar.CallProgressBar(null, null);
}
Some problems I encoutered
Inside DoWork, the same modal issue occurs. ShowDialog() will block the thread and therefore I never get to check CancellationPending to close ProgressBarForm.
ProgressBarForm starts later then the mainWork. I thought when I called CallProgressBar, the backgroundWorker should start well before my mainWork.
Is worker.Dispose() necessary in RunWorkerComplete?
Would it be a better choice to run mainWork in Worker? And why? I decided to let the main thread run this to not disturb the normal flow, what in Main thread will remain in Main thread, Progress bar is like an addon. If we bring it to the worker, would we need another thread to for progress bar itself?
Unless you do some very ugly hacks (like running more than one message loop inside your application) you cannot display a dialog if the thread running the main window is busy. All dialogs use the same thread to do the display update stuff in WinForms. In fact, they even must be running on the same thread.
There's one (sometimes acceptable) hack using Application.DoEvents(), but I wouldn't use it either, because it gets you into a lot of problems as well.
So the simple answer is: This doesn't work. Use a background worker to do lengthy processing.
I'm writing a forms application. I'm adding a piece that allows you to double click on a row of a datagridview control to open a new form with more details. These additional details are pulled from a database, which takes a bit of time to finish.
If I run the DB query from the form's load event, the form doesn't show up until everything in the load event has completed, which can take several seconds.
I want the form to show up instantly when you double click, and all of the fields to be populated once the data is ready.
Is there an event I should be using other than Load?
The standard way to accomplish this is to use a background worker thread and disable the button until the worker thread completes. There is a complete event you can subscribe to on the background worker.
You should use threading. Kick off a thread to do the data retrieval in the form's load event. Introduction to threading
You should use a BackgroundWorker to load the data in a background thread without freezing the UI.
If you really want to load on the UI thread, you should handle the Shown event.
This is an c# example using BackgroundWorker as the other posts metioned that loads unit definitions from .xml an file and changes the status label when it finishes. I stuck in the form intializer, but maybe it is better to start it in an OnLoad() override.
public MainForm()
{
InitializeComponent();
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = false;
bw.WorkerSupportsCancellation = false;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
unitsToolStripLabel.Text = "Loading Units";
bw.RunWorkerAsync();
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
...
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
unitsToolStripLabel.Text = string.Format("{0} Units Loaded", Units.UnitLibrary.WorkingSet.Count);
unitsToolStripLabel.LinkBehavior = LinkBehavior.HoverUnderline;
unitsToolStripLabel.Click += new EventHandler(unitsToolStripLabel_Click);
}
Please explain a little more on why you do not want to use threading/backgroundworker?
Whilst the correct way to do this is the BackgroundWorker thread, a quick and dirty method is to start a timer on the Load event and get the data when the timer expires. Say 10ms is enough for the form to be painted, then you can disable the controls and set the cursor busy while you get the data. But this is still going to lock up the UI thread while the database is busy leading to repainting artifacts eg if part of the window is covered, and doesn't allow you to display progress using a progress bar.
You can let the load event finish, then start another method to pull data from your database. The initialization of the UI can be done after the form has completed loading, but make sure your UI controls are disabled while you're initializing them.
In a WPF app, I am using a BackgroundWorker to periodically check for a condition on the server. While that works fine, I want to pop a MessageBox notifing the users if something fails during the check.
Here's what I have:
public static void StartWorker()
{
worker = new BackgroundWorker();
worker.DoWork += DoSomeWork;
worker.RunWorkerAsync();
}
private static void DoSomeWork(object sender, DoWorkEventArgs e)
{
while (!worker.CancellationPending)
{
Thread.Sleep(5000);
var isOkay = CheckCondition();
if(!isOkay)
MessageBox.Show("I should block the main window");
}
}
But this MessageBox does not block the main window. I can still click on my WPF app and change anything I like with the MessageBox around.
How do I solve this? Thanks,
EDIT:
For reference, this is what I ended up doing:
public static void StartWorker()
{
worker = new BackgroundWorker();
worker.DoWork += DoSomeWork;
worker.ProgressChanged += ShowWarning;
worker.RunWorkerAsync();
}
private static void DoSomeWork(object sender, DoWorkEventArgs e)
{
while (!worker.CancellationPending)
{
Thread.Sleep(5000);
var isOkay = CheckCondition();
if(!isOkay)
worker.ReportProgress(1);
}
}
private static void ShowWarning(object sender, ProgressChangedEventArgs e)
{
MessageBox.Show("I block the main window");
}
Replace
MessageBox.Show("I should block the main window");
with
this.Invoke((Func<DialogResult>)(() => MessageBox.Show("I should block the main window")));
that will cause the message box to be on the main thread and will block all access to the UI until it gets a response. As a added bonus this.Invoke will return a object that can be cast in to the DialogResult.
It doesn't only not block the main window, it is also very likely to disappear behind it. That's a direct consequence of it running on a different thread. When you don't specify an owner for the message box then it goes looking for one with the GetActiveWindow() API function. It only considers windows that use the same message queue. Which is a thread-specific property. Losing a message box is quite hard to deal with of course.
Similarly, MessageBox only disables windows that belong to the same message queue. And thus won't block windows created by your main thread.
Fix your problem by letting the UI thread display the message box. Use Dispatcher.Invoke or leverage either the ReportProgress or RunWorkerCompleted events. Doesn't sound like the events are appropriate here.
Call ReportProgress and pass this to MessageBox.Show.
As Stephen and Hans have said, use the ReportProgress event, and pass data to the UI thread. This is especially important if you want to do anything other tha a MessageBox (for isntance, update a control) because the background thread can't do this directly. You'll get a cross-thread exception.
So whatever you need to do (update progress bar, log messages to the UI, etc.), pass data to the UI thread, tell the UI thread what needs to be done, but let the UI thread do it.
I modified it like this and worked fine for me
return Application.Current.Dispatcher.Invoke(() => MessageBox.Show(messageBoxText, caption, button, icon));