I use c# in winform.
Before a very heavy function, I want to show a waiting form to prevent the user. The form opens, but the controls in it are not drawn.
In the following code, waitingForm is a little form, with only a textbox and a progressbar
using (WaitingForm waitingForm = new WaintingForm())
{
waitingForm.Show();
HeavyFunction();
}
I see only two white rectangles, where the controls should appear. Why this happens ?
You can't show the waiting form before the heavy processing and expect the UI to remain responsive. The thread that you're clogging up with HeavyFunction() is the same one that is responsible for drawing forms, controls and maintaining the UI. Use a BackgroundWorker or the ThreadPool to offload the heavy processing to another thread.
EDIT: Also, please consider Ron Beyer's input regarding async/await
Related
Happy New Year all,
I am developing a C# Winforms SDI application that relies heavily on background worker threads to carry out long-running database queries. My main dialog box contains a progress label and progress bar; but also a Toolstrip Menu, which contains two ToolStrip Labels and two Toolstrip Progress Bars. I update the progress labels and progress bars via delegates called by event handlers (OnUpdateProgress(object sender UpdateProgressEventArgs e)).
I am seeking your expert opinion on what approach I should use when invoking the UI delegate with regards to trying to achieve a more "generic" and reusable approach.
Thusfar I have considered 2:
(1) When instantiating a worker thread from the main dialog to carry out a background task, pass in a handle to the Label and Progress Bar controls to be updated when the event handler calls back, and
(2) Pass in the name of the Label and Progress Bar to be updated when the event handler is invoked; and use the Form.Find method to retrieve a Control[] of matching controls.
I cannot decide on which of these approaches to use; or indeed if there is a better way of doing this. I believe that method (1) is a little clunky and inelegant; but that method (2) is prone to failure and notwithstanding that the Form.Find method may introduce unreasonable overhead searching for embedded Toolstrip Menu Controls.
I have searched previously asked/answered questions for a solution.
Your advice if much appreciated!
In my form_load I have a mysql connection that loads data in a datagrid, when starting my application it will first try to load the mysql database and after its done it will load the form/interface with all the controls.
This leaves the user the impression nothing is happening, how can I first load the form/interface with all the controls and then my mysql database?
I would say, 3 approaches
1 - Separate Splash screen
2 - On-Form "splash screen" - first thing you do on form.load, is Me.Show(), then show a message, something like "Please wait while data is loaded". Progress can be shown to users as well using Application.DoEvents which will refresh your screen.
3 - Background worker thread. Using it, you can actually give user progress updates. That will leave your form completely responsive. http://msdn.microsoft.com/en-us/library/cc221403%28v=vs.95%29.aspx
Move your code to Shown event or OnShown overridden method. this will allow your form shown to user before it loads grid.
You should move your "Database calls" to worker threads as well.
protected override void OnShown(EventArgs e)
{
//Delegate the DB calls to worker threads here
//everything will be smooth then..
base.OnShown(e);
}
There are two ways to approach this situation. You need to pick the approach that best suits your needs.
Approach 1: Create a splash screen. A splash screen is a small window that opens up and usually had product info and a message like "Initializing...". You can see this in a lot of applications, such as Microsoft Office. You can see how to make one by looking at this article.
Approach 2: You will need to make your load data happen in a separate thread then your UI. You will need to load your data in the separate thread and bind it to a list of some sort temporarily, then after its done, bind the list to your Datagrid
Use other thread for loading data from mysql. It's loading form first, but not displaying it until code has finished work.
new Thread(new ThreadStart(LoadDataMethodHere)).Start();
You can use Form Shown event.
Alternatively you can use a different thread to connect to database and show data or use a timer.
Is there any way I can pause all UI Update commands in Winforms?
Or I have a slight feeling I'm trying to go about this the completely wrong way, so is there another way around my problem: I basically load a saved state of a control, which loads new controls all over it. However I do some of this in the UI thread, and some of the data loading from another thread, which then fills the UI.
So the effect I have when it is loading is that the user can see a few of the controls appearing in one place, then moving to another place on the form, changing values, etc.
I'd like to get a loading screen instead of this and load the controls in the background. It's quite a large application and its not THAT important so redesigning my code isn't really an option.
Can I simply stop all Update() commands on a control while a method is executing?
You can use the SuspendLayout and ResumeLayout methods to wrap the setup of UI in one operation (without the update of the rendering).
Basically (assuming SomeMethod is in the form class):
private void SomeMethod()
{
this.SuspendLayout();
// all UI setup
this.ResumeLayout();
}
it really depends on your form logic, in general you should not overload the Load or Show method with too much things so that the form can be shown and drawn quickly and always look responsive.
in some cases it could help to use the SuspendLayout and ResumeLayout methods, see here:
Control.SuspendLayout Method
I have a WinForm set up and a process that loops until a button is pressed on the form.
When I try to run my code, the form does not even display. I suspect this is because the code gets stuck in the loop and doesn't get far enough to display the WinForm. How can I get the form to display and the loop to run after that point?
If you're looping because you need to do something with the GUI periodically while waiting for input, I suggest using a Timer control and its Tick event.
If you want to do non-GUI things while waiting, a more traditional timer is better suited to the task,
You should probably run the loop in a background thread. The BackgroundWorker class makes this pretty easy to do.
Don't do that.
Windows Forms (like most modern user interface development toolkits) is an event-driven framework. You should never use a loop that "waits" for something to happen; instead you want to use an event that triggers something to happen.
Essentially what's happening is this: WinForms has a loop running a message pump that listens for events from Windows and triggers C# events in response to them. Your code is executing on the same thread as that message pump (it has to, since in WinForms only one thread is allowed to touch any given control). So if you put that thread into a loop, the WinForms code that should be pumping messages isn't, and your user interface appears to hang, since it isn't responding to any messages from Windows. (If you keep clicking it, you will fill up the message queue and get a dialog box that says "This application has stopped responding, do you want to terminate?" or something like that.)
The correct solution is to do one of the following:
Use a Timer
Use a BackgroundWorker
Use a ThreadPool
Another solution that would work, but is not a good idea is:
Use Application.DoEvents() -- but please don't actually do this
Your form loading is freezing because the UI of a windows form runs in a single thread. And the logic that you put on the Load event of this form is running on that thread.
You can run your loop on a separate thread easily by using a BackgroundWorker component on your windows form. On the event DoWork of your background worker, you place the code that has the loop that should run without block your UI. On the Form.Load event, you can start the background worker component by calling the method RunWorkerAsync. On the event handler of your button, you place a code to stop the background worker by calling CancelAsync method.
The article How to: Implement a Form That Uses a Background Operation shows exactly how to accomplish it.
About your comment on not being able to update the Text of a TextBox from a your background worker component. It happens because it is not allowed to modify the state of a windows forms control from a different thread (your background worker code is running on a separated thread) MSDN documentation says:
Access to Windows Forms controls is not inherently thread safe. If you have two or more threads manipulating the state of a control, it is possible to force the control into an inconsistent state. Other thread-related bugs are possible, such as race conditions and deadlocks. It is important to make sure that access to your controls is performed in a thread-safe way.
A sample of how you can update the state of your windows forms controls from your background thread will be similar to the one below (assuming that the new value is already stored on a String variable named text):
// 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.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.textBox1.Text = text;
}
I borrowed this code snipped from How to: Make Thread-Safe Calls to Windows Forms Controls article. It can provide you more information about how to deal with multi-threaded windows forms.
You can use the form load event to trigger the start of the loop.
So it would handle the event Me.Load
However is it necessary for your loop to be happening inside of the UI?
This happens because your loop is keeping the window function from processing messages, such as those that tell it to repaint itself. Place a call to Application.DoEvents() inside of your loop to allow the UI to continue to function.
However, you need to ask yourself why you're looping like this in the first place. If you're, say, copying a bunch of files, this might make sense. For most tasks, though, responding to a timer tick should do the trick and won't block the UI.
You should run your loop in a background thread using the BackgroundWorker component.
Remember that the background thread cannot directly interact with the UI controls.
To report the progress on the UI, you should call the BackgroundWorker's ReportProgress method in the background thread, and handle the ProgressChanged event to update the UI.
You can call the CancelAsync method when the Button is clicked, and loop until the CancellationPending property is true.
I have a grid and when a row is double clicked a form is loaded. However a lot of data must be loaded, so I'd like to display a simple form with the text 'loading, please wait..'. And when all loading is finished, the form must disappear.
This is what I have right now, but it doesn't work:
Code that invokes the form with lots of data:
FormWithLotData form = new FormWithLotData();
form.ShowDialog(this);
Constructor of FormWithLotData:
// Show load form
FormIsLoading frm = new FormIsLoading();
_CloseLoadForm closeForm = new _CloseLoadForm(frm.Close);
System.Threading.Thread thread = new System.Threading.Thread(frm.Show);
thread.Start();
InitializeComponent();
this.Visible = false;
LoadAllData();
this.Visible = true;
// Close load form
Invoke(closeForm);
Hope you can help me out.
EDIT:
I'd like to show an animated gif on the loading form.
SOLUTION:
I've created a background worker. The DoWork event handles all the loading and by using the invoke() method I add nodes to the treeview. Now, the GUI doesn't hang and the user don't have the idea that the application is hanging.
You need to reverse your code.
The constructor of FormWithLotData is running in the UI thread. This is the thread that must show your FormIsLoading form. So instead of trying to display this form using the new Thread, do your data loading with it.
The DoEvents way others have suggested is the easiest to implement and (possibly? never done it myself) may work well.
The better pattern to use is to do your data loading on a worker thread. Before you show your FormWithLotData, Begin loading data on a background thread and show your Loading dialog. The method that loads the data should have a callback method into the Loading dialog to signal when it should Close(). Once it closes you can then construct a new FWLD, pass it the already loaded data, and Show it.
Trying to load your data after the form has already been invoked mixes your UI with your data operations, forcing your form to not only be in charge of the UI but also be in charge of data retrieval. Bad for KISS and Single Responsibility, imho.
After your update, it seems like DoEvents is going to be the only real answer to your question, but with some caveats.
You will not be able to show another form MODALLY while you construct your tree. You will still have to do your heavy work within your form's constructor. You will still have to hide your main form and Show() (not ShowDialog) your loading form. You will also have to call DoEvents at every single possible moment while constructing your tree. Its not exactly an elegant solution, but it will probably be your best bet at this point.
how about ...
FormIsLoading frm = new FormIsLoading();
frm.Show();
Application.DoEvents();
// ... load data ...
frm.Close();
In the Form_Load event, add the following:
this.Visible = true;
Application.DoEvents();
before any other processing occurs. The Application.DoEvents caused the UI to show the form at the current state, where normally the UI thread is locked while you other processing is taking place.
Don't do your LoadAllData() directly in a UI thread, instead start up a background thread to do it. The in your Form_Loaded event handler, use an AutoResetEvent and wait till it becomes signalled by the background data retrieving thread. Once it is signalled you can then continue to do whatever you need to do with the data, like bind it into the UI.
This method is still a little clunky for various reasons, but it will get you started.
Edit: i was being lazy with my answer above... a better option is to pass a delegate (callback) to the background thread, when the delegate is invoked (upon completion of the data retrieval) it marshals itself back on to the UI thread, and starts doing the required work with the data.
Inside your form, you can use a Timer control to simulate loading
using ProgressBar when it reaches 100 it unloads the form or any
kind of animation.
for progress-bar code just add the control from the toolbox and then
write the following code:
ProgressBar1.Value = ProgressBar1.Value+1;
if(ProgressBar1.Value == 100)
{
timer1.Enabled = false; this.Hide();
MessageBox.Show("Complete Loading...");
}