I'm developing a C# WinForms application; this application will have many windows to show some reports. These windows are forms created from some DLLs by reflection, so the code is not so simple to change.
The problem is that when a form is getting data or processing some info it freeze/locks all the application, so users can't navigate to other forms in the application. So, if the user wants a report that takes 5 minutes, the app freeze 5 minutes and the user can't navigate to another window.
I have tried to call the new forms with Invoke((MethodInvoker)delegate () {}); but it freezes anyway. I tried to use MDI from Windows Forms, but also freezes. I tried to use Document Manager or Tab View from DevExpress, but the same problem.
I also have begun reading the problems to create a form in a new thread with the UI from the main thread, but I think this is not my problem because it's ok for the form to be freeze, but not to freeze all the application and user can navigate other forms.
If I try to create the forms in other process the application loads too slow because all the controls it has, so ideally it must be in the same process.
Is there any way I can call a new form in a new thread, that won't freeze all application while processing? Is there any opensource/paid control that can help me with this problem?
Thanks in advance, regards.
Simpliest way to do asynchronous programming in winforms and C# is async/wait logic.
Try something like this :
using System.Threading;
using System.Threading.Tasks;
private async void btnDoReport_Click(object sender, EventArgs e)
{
Task<bool> reportTask = new Task<bool>(ReportFunction); // if your function is void you can create it as Task reportTask = new Task(ReportFunction)
trainTask.Start(); // here your task - report is executed asynchronously and GUI does not freeze
bool reportResult = await reportTask; // wait on resposne from your reportTask
}
private bool ReportFunction()
{
// do what ever you need to in this function
return true;
}
You can also use BackgroundWorker class which is also working asynchronously and even you can raise event from your asynchronously running thread. After that this event you can catch in main thread - GUI thread, so you will be able to safely access controls of your GUI applications without errors.
using System.ComponentModel;
private BackgroundWorker bWork = new BackgroundWorker();
this.bWork.DoWork += BWork_DoWork;
this.bWork.ProgressChanged += bwork_ProgressChanged;
private void btnReport_Click(object sender, EventArgs e)
{
// this will run your code asynchronously
this.bWork.RunWorkerAsync();
}
private void BWork_DoWork(object sender, DoWorkEventArgs e)
{
// here you can create your report
// if you want you can raise event like this :
this.bWork.ReportProgress(this)
// and after raising of above event function : bWork_ProgressChanged is
//called where you can safely access main thread controls
}
private void bwork_ProgressChanged(object sender,
System.ComponentModel.ProgressChangedEventArgs e)
{
/* here you can access main thread GUI elements for example progress bar
without worrying of some problems because this function is called in
main thread space*/
}
Related
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'm creating a Excel VSTO using c#. My operation is easy, just right-click on the cell and click on "Update" and a winform that shows progress status will prompt out and launch the controls on the form is tied to a User Process Controller.
The problem is now that the process has launched and executed before the form is fully load, is there a way that I can block the user process controller from executing before all the control on the progress status form is fully shown and loaded? The image below depict my condition.
I have tried to put my User Process Controller call in Form Activated, Shown, Loaded, and nothing works.
This is the first stage the form loaded. Note : The two line of text has shown that the user control process has been executed.
This is the second stage the form loaded.
This is third stage
And finally it is fully loaded.
I have discovered a "hack" to overcome this issue. I add in a backgroundworker and done the follow code on the Form Constructor.
public SummaryStatus()
{
InitializeComponent();
backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += backgroundWorker_DoWork;
backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
backgroundWorker.RunWorkerAsync();
}
And on the DoWork event of BackgroundWorker
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
System.Threading.Thread.Sleep(2000);
}
and finally, I added the following code in Run Worker Completed
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
UpdateUPC upc = new UpdateUPC();
upc.txtUpdateSummary = txtUpdateProgress;
upc.updateProgressBar = UpdateProgressBar;
upc.UpdateStatusOnItem();
}
I understand the above might not be a acceptable solution but it might just provide a workaround for the issue. However, if anyone who has a better solution, do feel free to drop in your suggestion.
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've put a loading message like this on my form :
public void myFunc()
{
lbl_status.Text = "Loading ... Please Wait";
// Some Database Works
lbl_status.Text = "Done";
}
but there is a problem. Some times when I click on the button ( Which does myFunc method ) my application doesn't show the Loading message. It just does the database work than it will show Done message.
I know that sometimes the database work is very fast so Loading message won't show but sometimes it is not that fast, like the fist time I open my app. At that time my application seems to be disabled and no buttons and no textBoxes and ... works and after the database work it will be OK and show Done message and never shows Loading message again!
You can do your db work in background thread:
public void myFunc()
{
lbl_status.Text = "Loading ... Please Wait";
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += bw_DoWork;
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
bw.RunWorkerAsync();
}
EDIT: oops DB works should be in the DoWork event handler :)
void bw_DoWork(object sender, DoWorkEventArgs e)
{
// Some Database Works
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
lbl_status.Text = "Done";
}
Your application, like all Windows GUI application, needs to process the GUI events. It is these events that do things like refresh the labels and make your application seem 'responsive'. See Window Messages. Your winforms application runs the message loop when you call Application.Run:
Begins running a standard application message loop on the current thread
If you block the processing while waiting for database work then it will stop refreshing and it will be unresponsive (not respond to clicks or keyboard). So you have to do your database w/o blocking the main loop. There are several options:
use a BackgroundWorker.
use ThreadPool.QueueUserWorkItem.
use the async database methods like SqlCommand.BeginExecuteReader and completion callbacks
use await methods like SqlCommand.ExecuteReaderAsync
Each method has pros and cons, the easiest to start with is probably the first one. Be aware that from a background thread, or from a completion callback, you must use the Control.Invoke when interacting with the main GUI (ie. when updating the form or any element on it).
Any updates to UI objects must be done on the UI thread.
You should look into Control.Invoke to provide you a way to put your call on the Display thread the Control is on.
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invoke%28v=vs.110%29.aspx
for further clarification, this also means that your heavy updates should be done on a background thread.
WorkerThread may be a good solution for you.
I am attempting to update the progress bar on a main form with the work being done in a different class. For example:
private void transfer_Click(object sender, EventArgs e)
{
Guid aspnetUserId = Guid.Parse(System.Configuration.ConfigurationSettings.AppSettings["ASPNetUserID"]);
WC1toWC2Transfer transfer = new WC1toWC2Transfer(aspnetUserId);
backgroundWorker1.RunWorkerAsync(transfer);
}
And then in the background method actually call the method:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
WC1toWC2Transfer transfer = e.Argument as WC1toWC2Transfer;
transfer.WC1ToWC2EmployerTransfer(log, wc1ConnStr, wc2ConnStr, progressBar1);
}
In the WC1ToWC2EmployerTransfer method, I'm setting the progress bar maximum and updating the value everytime something is transferred to the database in this method, but when I do this nothing happens. There's code inside the method that runs a stored procedure in a database, but as soon as it hits that portion of the code, it stops debugging and I have to run the process again.
Am I doing something wrong here? Do I need to rewrite what I have so the actual methods are in the main form and not in a different class? I'm a junior developer (just started a few months ago) so forgive me if I'm doing something blatantly wrong or if I didn't explain this well enough.
Any help would be greatly appreciated.
You cant alter the UI unless you are on the main thread, which you BackgroundWorker will not be.
What you need to do is create an event handler in the main form to handle the backgroundworker's ProgressChanged event.
eg
// this method should be in your main form.
private void backgroundworker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// update your progress bar here.
}
In your background worker, you call the ReportProgress method which will fire the ProgressChanged event.
There is a nice example here.
I think the reason is that you get an IllegalCrossThreadException, because you're attempting to access the control from a different thread than it was created. The BackgroundWorker provides a ReportProgress method and a ProgressChanged event that is typically used for such updating and which will be executed on the UI thread. When accessing the progressbar from another thread than the UI thread, do it like this:
if(progressBar1.InvokeRequired) {
progressBar1.Invoke(new MethodInvoker(delegate { progressBar1.Maximum = someValue; }));
}
As a sidenote: It's not very good design to pass the progressbar to your worker class. The form could close, it could get disposed and the worker would not know anything about it, eventually failing with an ObjectDisposedException that probably isn't caught. Additionally, you're making the worker dependent on System.Windows.Forms when it probably doesn't need to. Rather let your worker report progress through an event and pass that on to your progressbar from the class that created the worker.