OK guys, I have this Class that shows a "Loading..." Splash Screen. It works great when I call it on Initialize() but not on Form_Load. Instead of showing at the beginning of Form_Load, it shows after all tables are filled and then just hangs there (no lock).
class innerLoad
{
//Delegate for cross thread call to close
private delegate void CloseDelegate();
//The type of form to be displayed as the splash screen.
private static frmLoading splashForm;
static public void ShowSplashScreen()
{
// Make sure it is only launched once.
if (splashForm != null)
return;
Thread thread = new Thread(new ThreadStart(innerLoad.ShowForm));
thread.IsBackground = true;
//Thread.Sleep(100);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
//volatile static public bool isOpen = false;
static private void ShowForm()
{
splashForm = new frmLoading();
splashForm.ShowDialog();
splashForm.Dispose();
}
static public void CloseForm()
{
try
{
if (splashForm == null)
return;
splashForm.Invoke(new CloseDelegate(innerLoad.CloseFormInternal));
}
catch
{
}
}
static private void CloseFormInternal()
{
splashForm.Close();
splashForm = null;
}
}
And here is the Form_Load Code:
private void frmPayGen_Load(object sender, EventArgs e)
{
//th1 = new Thread(LoadingForm);
//th1.Start();
//Thread.Sleep(500);
innerLoad.ShowSplashScreen();
fill();
innerLoad.CloseForm();
//Thread.Sleep(500);
}
I appreciate your help and I love this site... helps me a lot :D
If you set a breakpoint at the start of your Form Load event, and use F11 to step through, you eventually see this exception:
Exceptions in a Form Load event are basically ignored. If an exception is thrown, nothing after the line where the exception was thrown runs, but the Windows Form doesn't crash either. Taking away this line of code should make things work as you wish.
Related
I've been playing around with multithreading and reading up on some of the questions here, but I haven't found an answer that directly addresses my concerns here.
I have an application that runs on a single thread, except for a progress bar in a separate window. Based on my research, I need to create a new thread for that form which will redraw the form's controls as it's properties change. I've reduced the problem to a simple example below:
Here's the 'main' program:
class Program
{
static MyForm form;
static void Main(string[] args)
{
form = new MyForm();
form.Show();
doWork();
form.Close();
}
//arbitrary example of processing that takes some period of time
static void doWork()
{
while (form.Value < 100000)
{
form.ChangeVal();
Thread.Sleep(1);
}
return;
}
}
...And here's the Form. I'm not including the auto-generated stuff from VS.
public partial class MyForm : Form
{
private int val;
public int Value
{
get { return val; }
set { val = value; }
}
public Thread GUIupdater;
public MyForm()
{
InitializeComponent();
this.Refresh();
}
private void MyForm_Load(object sender, EventArgs e)
{
GUIupdater = new Thread(new ThreadStart(GUIupdaterThread));
GUIupdater.Start();
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(killThreadOnClose);
}
public void ChangeVal()
{
val++;
}
private void changeLabel(string s)
{
label.Text = s;
label.Refresh();
}
private delegate void labelChanger(string s);
private void GUIupdaterThread()
{
while (true)
{
Invoke(new labelChanger(changeLabel), new object[]{val.ToString()} );
Thread.Sleep(100); //100 ms
}
}
private void killThreadOnClose(object sender, FormClosingEventArgs e)
{
GUIupdater.Abort();
}
}
So, my intention here is to have the calculations running constantly, with the window's graphics updating reasonably quickly. When I run the program, however, the invoke function is only called once, and the label never actually updates!
Any and all feedback is appreciated. If you want to view the code in an IDE you can download my project from Here
Edits:
When I add Console.WriteLine Statements, I discovered that the GUIupdaterThread (the thing that's meant to update the GUI) loop always 'breaks' on the Invoke statement, never reaching 'Thread.Sleep'. I changed it to 'BeginInvoke', which causes the loop to function properly, but this hasn't changed the fact that the GUI doesn't update.
CLARIFICATIONS:
About my 'actual' project:
The main thread here in 'Program' simulates my software, which is a plugin implementing an interface. My decision to alter val / value in that thread, not in the thread created by the window, was deliberate.
I'm constrained to using .NET 4.0 . any more recent features can't help me
Since in your application you have GUI thread (main thread) - all UI controls will be accessible from this thread only.
There are several approaches how to update controls from other threads.
I would like to recommend you to use one of modern and native approaches based on Progress < T > class (it's native for .Net platform).
I would suggest overriding the form's OnPaint method. Then inside ChangeVal, after you have updated whatever variables/data you need to update, call this.Invalidate which should trigger the form to repaint itself.
Or if you're just updating a single label, call label.Refresh in your ChangeVal method. The form should update correctly. Here's an example that worked for me:
This form has a single label on it.
public partial class ProgressForm : Form
{
private int currentValue = 0;
public ProgressForm()
{
InitializeComponent();
}
public void ChangeValue(int newValue)
{
currentValue = newValue;
lblValue.Text = string.Format("Current value: {0}", currentValue);
lblValue.Refresh(); //Call Refresh to make the label update itself
}
}
static class Program
{
private static ProgressForm progressForm = null;
[STAThread]
static void Main()
{
//Application.EnableVisualStyles();
//Application.SetCompatibleTextRenderingDefault(false);
//Application.Run(new Form1());
progressForm = new ProgressForm();
progressForm.Show();
doWork();
progressForm.Close();
}
//arbitrary example of processing that takes some period of time
static void doWork()
{
int i = 0;
while (i < 100000)
{
progressForm.ChangeValue(i);
Thread.Sleep(1);
i++;
}
return;
}
}
You may use the following instead as you are trying to access UI control other than main thread (from which it is created).
while ( true )
{
Invoke ( ( Action ) (() =>
{
label.Text = val.ToString();
label.Refresh()
Application.DoEvents();
}));
Thread.Sleep( 100 );
}
I recommend you to use "backgroundworker".
First add CheckForIllegalCrossThreadCalls = false; to initialization part otherwise InvalidOperationException occurs.
private void btnDoIt_Click(object sender, EventArgs e)
{
backgroundWorker.RunWorkerAsync();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
Foo();
}
int total = 0;
private void Foo()
{
for (int i = 0; i <= 100000; i++)
{
total += i;
this.Text = i.ToString();
}
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Run next process
}
I have an old application in Windows Forms, which in many places do some searches on database. Sometimes it takes a lot of time, so I decided to create a loading screen in wpf to show the user that something is loading in separate thread. Basically it's just a full transparent window with loading indicator(a turning circle). Everything works fine on My host computer and on my Virtual Machine, but when I'm trying to deploy it to our demo environments its like - it starts loading the indicator is shown and after few seconds it dissapear and application stops responding like forever. My first thought was that it's the problem with GPU acceleration, that it can't process transparency, but it's being shown for few seconds so it can't be the problem. So most likely I did something bad. Below You can see my code, do You notice something which might be wrong/cause deadlock or something ?
public class LoadingManager
{
public LoadingManager()
{ }
public LoadingManager(string LoadingText)
{
loadingText = LoadingText;
}
private string loadingText = "Please wait ..";
private Thread thread;
private bool ThreadReadyToAbort = false;
private BusyIndicatorView loadingWindow;
public void BeginLoading()
{
this.thread = new Thread(this.RunThread);
this.thread.IsBackground = true;
this.thread.SetApartmentState(ApartmentState.STA);
this.thread.Start();
}
public void EndLoading()
{
if (this.loadingWindow != null)
{
this.loadingWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
{ this.loadingWindow.Close(); }));
while (!this.ThreadReadyToAbort) { }; // I also tried to remove this while but it didn't help
}
this.thread.Abort();
}
public void RunThread()
{
this.loadingWindow = new BusyIndicatorView();
loadingWindow.tbLoadingCaption.Text = loadingText;
this.loadingWindow.Closing += new System.ComponentModel.CancelEventHandler(waitingWindow_Closed);
this.loadingWindow.ShowDialog();
}
void waitingWindow_Closed(object sender, System.ComponentModel.CancelEventArgs e)
{
Dispatcher.CurrentDispatcher.InvokeShutdown();
this.ThreadReadyToAbort = true;
}
EDIT.
I noticed that on this machines it usually(sometimes it also fails at first click) works when i click search for the first time. If i click another time it's showing for a second than dissapearing and application stops responding. So it seems like Thread is not beeing shutdown, Dispatcher shutdown failed ? But no exceptions are thrown ...
Your BeginLoading method can be called more than once before it has finished, and so can create more than one wpf window. This messes up all kinds of references. Also do not abort the thread, let it decide for itself. Here are the two changes:
public void BeginLoading()
{
this.thread = new Thread(this.RunThread);
this.thread.IsBackground = true;
this.thread.SetApartmentState(ApartmentState.STA);
this.thread.Start();
while (this.loadingWindow == null) { } // <--- Add this line
}
public void EndLoading()
{
if (this.loadingWindow != null)
{
this.loadingWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
{ this.loadingWindow.Close(); }));
while (!this.ThreadReadyToAbort) { };
}
//this.thread.Abort(); // <-- Remove this line
}
Also if this is all just for a busy screen, I would say there has to be a better and safer way than this. For academic purposes this is an interesting problem, but not production code, especially if some junior developer could fiddle with this in the future.
Edit: Still crashing on my machine if I reduce the delay between repeated callds to BeginLoading and EndLoading. It may be related to how the wpf window closes asynchronously. I removed the Closed event handler and just used a boolean flag to indicated that the window 'isLoaded' or not, and I have not seen any problems with this:
public class LoadingManager2
{
public LoadingManager2()
{ }
public LoadingManager2(string LoadingText)
{
loadingText = LoadingText;
}
private string loadingText = "Please wait ..";
private Thread thread;
private MyWindow loadingWindow;
private bool isLoaded = false;
public void BeginLoading()
{
this.thread = new Thread(this.RunThread);
this.thread.IsBackground = true;
this.thread.SetApartmentState(ApartmentState.STA);
this.thread.Start();
while (!this.isLoaded) { };
}
public void RunThread()
{
this.loadingWindow = new MyWindow();
this.isLoaded = true;
this.loadingWindow.ShowDialog();
}
public void EndLoading()
{
this.loadingWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
{
this.loadingWindow.Close();
this.isLoaded = false;
}));
while (this.isLoaded) { };
}
}
I have form with button and text box. Button is starting thread which is updating value of text box.
public Form1()
{
InitializeComponent();
myDelegate = new UpdateUi(updateUi);
}
private void button1_Click(object sender, EventArgs e)
{
myThread = new Thread(new ThreadStart(ThreadFunction));
myThread.Start();
}
private void ThreadFunction()
{
MyThreadClass myThreadClassObject = new MyThreadClass(this);
myThreadClassObject.Run();
}
private void updateUi(int i)
{
textBox1.Text = i.ToString();
Thread.Sleep(1000);
}
public Thread myThread;
public delegate void UpdateUi(int i);
public UpdateUi myDelegate;
and ThreadClass:
public class MyThreadClass
{
Form1 myFormControl1;
public MyThreadClass(Form1 myForm)
{
myFormControl1 = myForm;
}
public void Run()
{
// Execute the specified delegate on the thread that owns
// 'myFormControl1' control's underlying window handle.
for(int i=0;i<100;i++)
{
if(myFormControl1.InvokeRequired)
{
myFormControl1.Invoke(myFormControl1.myDelegate,i);
}
}
}
}
As You can see there is nothing special in my code but sometimes the code freeze.
eg it goes 1->2->3->freeze->16->17 and so on.
I took code from HERE with little modifications
The issue is you are delaying the UI thread not the the process itself so what happens is you issue all the update commands but since it all runs on the same thread it gets clogged because the Thread.Sleep stops the UI thread so it runs a bunch of textBox1.Text = i.ToString(); then it stops for all the time of all the Thread.Sleep(1000); probably the number of 1->2->3... you see is equal to the number of cores in your machine.
When you stop the run method what happens is you issue one update command that runs immediately and wait for one second until you issue the next command witch I think its what you are trying to accomplish.
Working on a C# project which I would like to implement a "waiting" (throbber) indicator in a separate form. After much research and trial and error it appears as the suggested method of doing this is to load a form using a separate thread from the one from the current form/thread.
The reason I went with this method was because initially using the Show() method on the throbber form produced a transparent form. I cannot use ShowDialog because I need to run some code after the throbber is displayed, after which that completes I would like to close the throbber form.
Anyway .. after trying many different methods to load the throbber form in a separate thread I still get an error about trying to access it from a thread which is different from the one it was created in. Here is a skelton version of the project code that should shed some light on my issue:
the example I was working off of for multithreading was this popular link for creating your own spashscreen in a separate thread ... http://www.codeproject.com/Articles/5454/A-Pretty-Good-Splash-Screen-in-C
public class Main
{
public void CheckData()
{
try
{
ProgressBar pb = new ProgressBar();
pb.ShowProgressBar();
//do data checking here
pb.CloseForm()
}
catch(Exception e)
{
}
}
}
public partial class ProgressBar : Form
{
static Thread ms_oThread = null;
public bool shouldStop = false;
static ProgressBar ms_ProgBar = null;
public ProgressBar()
{
InitializeComponent();
//DoWork();
}
public void ShowForm()
{
ms_ProgBar = new ProgressBar();
Application.Run(ms_ProgBar);
}
public void CloseForm()
{
ms_ProgBar.Close();
}
public void ShowProgressBar()
{
// Make sure it is only launched once.
if (ms_ProgBar != null)
return;
ms_oThread = new Thread(new ThreadStart(ShowForm));
ms_oThread.IsBackground = true;
ms_oThread.SetApartmentState(ApartmentState.STA);
ms_oThread.Start();
while (ms_ProgBar == null || ms_ProgBar.IsHandleCreated == false)
{
System.Threading.Thread.Sleep(1000);
}
}
}
You are creating your ProgressBar twice. Once in your main function, and once in your new thread. You are also calling your CloseWindow method from your main function (and on the window that is never shown), rather than on your new thread window.
You only want to create ProgressBar and show it using your new thread. Make your static ProgressBar field public so you can call close on it directly from Main, but make sure to use Invoke to do it since it's not on that Window's GUI thread.
Also, ShowProgressBar should be static.
Here's a rewrite attempt:
public class Main
{
public void CheckData()
{
try
{
ProgressBar.ShowProgressBar();
//do data checking here
ProgressBar.CloseForm();
}
catch(Exception e)
{
}
}
}
public partial class ProgressBar : Form
{
static ProgressBar _progressBarInstance;
public ProgressBar()
{
InitializeComponent();
//DoWork();
}
static void ShowForm()
{
_progressBarInstance = new ProgressBar();
Application.Run(ms_ProgressBar);
}
public static void CloseForm()
{
_progressBarInstance.Invoke(new Action(_progressBarInstance.Close));
_progressBarInstance= null;
}
public static void ShowProgressBar()
{
// Make sure it is only launched once.
if (_progressBarInstance != null)
return;
var ms_oThread = new Thread(new ThreadStart(ShowForm));
ms_oThread.IsBackground = true;
ms_oThread.SetApartmentState(ApartmentState.STA);
ms_oThread.Start();
}
}
Since I added a splash screen my main form will sometimes (about 1 every 20 times) disappear like it's minimized (it will be invisible but it will be still on the task bar and if I click it it reappears). Here is my code:
static class Program
{
private static SplashScreen splashScreen = null;
private static ManualResetEvent splashScreenWaiter = null;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
ShowSplashAsync();
BuilderForm2 builderForm2 = new BuilderForm2();
builderForm2.Shown += new EventHandler(builderForm2_Shown);
Application.Run(builderForm2);
}
private static void HideSplash()
{
if (splashScreenWaiter != null)
{
splashScreenWaiter.WaitOne();
splashScreen.Invoke(new Action(splashScreen.Close));
splashScreenWaiter = null;
splashScreen = null;
}
}
private static void builderForm2_Shown(object sender, EventArgs e)
{
HideSplash();
}
private static void ShowSplashAsync()
{
splashScreenWaiter = new ManualResetEvent(false);
Thread splashThread = new Thread(ShowSplash);
splashThread.IsBackground = true;
splashThread.SetApartmentState(ApartmentState.STA);
splashThread.Start(splashScreenWaiter);
}
private static void ShowSplash(object resetEvent)
{
splashScreen = new SplashScreen((ManualResetEvent)resetEvent);
Application.Run(splashScreen);
}
}
And this is SplashScreen code:
public partial class SplashScreen : Form
{
private ManualResetEvent ResetEvent;
bool handleCreated = false;
bool formShown = false;
public SplashScreen(ManualResetEvent resetEvent)
{
ResetEvent = resetEvent;
HandleCreated += new EventHandler(SplashScreen_HandleCreated);
InitializeComponent();
}
private void SetResetEventIfReady()
{
if(handleCreated && formShown) ResetEvent.Set();
}
private void SplashScreen_Shown(object sender, EventArgs e)
{
formShown = true;
SetResetEventIfReady();
}
void SplashScreen_HandleCreated(object sender, EventArgs e)
{
handleCreated = true;
SetResetEventIfReady();
}
}
Nothing jumps out. There is however a very serious race condition in your code. It is related to the SystemEvents class. That class provides important notifications to controls so they can respond to the user changing the Windows theme. That class needs a hidden notification window to receive messages about the changes the user made.
This goes very wrong if your program's first window is created on a worker thread instead of the UI thread. That makes the SystemEvents class create that notification window on the wrong thread (not your worker thread btw). And the events it raises will be called from that thread. Getting the event on that wrong thread creates havoc, controls are not thread-safe. The most typical outcome is that you'll have odd painting problems or the form deadlocks when you lock the workstation. I can imagine what you see going wrong could be explained by this as well.
The .NET framework already has excellent and time-tested support for splash screens. I recommend you use it instead of spinning your own. Check this answer for the code.
If you want to keep your own then you can work around the race problem by pasting this line of code into your Main method, before the ShowSplashAsync call:
Microsoft.Win32.SystemEvents.UserPreferenceChanged += delegate { };