Can not click button when a method running - c#

private void AddMyScrollEventHandlers()
{
VScrollBar vScrollBar1 = new VScrollBar();
}
private void button1_Click(object sender, EventArgs e)
{
while (true)
{
if (vScrollBar1.Value + 1 < vScrollBar1.Maximum)
{
vScrollBar1.Value = vScrollBar1.Value + 1;
label1.Text = vScrollBar1.Value.ToString();
}
else
{
break;
}
System.Threading.Thread.Sleep(200);
}
}
private void button2_Click(object sender, EventArgs e)
{
// vScrollBar1.Scroll
}
I am new in C#. I was working on scroll. What I wanted here is, if anyone click button1 then scroll automatically move to the end and I wanted to show gradual value in label1. Also when someone click button2 scrolling stop.
Now the problem is label1 do not show gradual change in value. It shows value once when the scrolling stop.
Also when scrolling continue i,e when while loop is working I can not click on button2. Actually I can not click on the form even.
Someone please give me some idea how to do this.

This happens because the thread that is performing the task is busy, and it's the same thread that updates the UI. You can use a multithreading solution. Take a look at
BackgroundWorker

All the UI events run in the main thread of the application, so the application can only process one event at a time. When the application is processing an event, no other event will be processed.
Since you are doing a UI related work periodically, the best option is to use the Timer class:
Drop Timer from the toolbox into the form.
In the properties window, set the interval to 200.
Double click the timer object to create the Tick event handler.
Put this code in the newly created timer1_Tick method:
if (vScrollBar1.Value + 1 < vScrollBar1.Maximum)
{
vScrollBar1.Value = vScrollBar1.Value + 1;
label1.Text = vScrollBar1.Value.ToString();
}
else
{
timer1.Stop();
}
Change your methods as below:
private void AddMyScrollEventHandlers()
{
timer1.Start();
}
private void button2_Click(object sender, EventArgs e)
{
timer1.Stop();
}
Now you're done.

I would recommend using BackgroundWorker control, as suggested by Agustin Meriles. However, one more important thing to note is that You should use Control.Invoke(...) method to update controls from another thread.
I've modified Your code, tested it in a sample application and it seems to work correctly.
First, add a new BackgroundWorker control to Your form and assign backgroundWorker1_DoWork to its DoWork event.
Then, You can use the code below:
private void button1_Click(object sender, EventArgs e)
{
//code from here is moved to BackgroundWorker control
backgroundWorker1.RunWorkerAsync();
}
private void button2_Click(object sender, EventArgs e)
{
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//while (true)
//the condition directly in the while looks more clear to me
while (vScrollBar1.Value + 1 < vScrollBar1.Maximum)
{
//update controls using Invoke method and anonymous functions
vScrollBar1.Invoke((MethodInvoker)delegate() { vScrollBar1.Value += 1; });
label1.Invoke((MethodInvoker)delegate() { label1.Text = vScrollBar1.Value.ToString(); });
//when called inside BackgroundWorker, this sleeps the background thread,
//so UI should be responsive now
System.Threading.Thread.Sleep(200);
}
}
If You have any problems when using this code, please let me know.
Update
As mentioned in the comments, You could also use ProgressChanged event of the BackgroundWorker. It requires some more changes in the code, but is more suitable in this case. You can find some information about it here: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.progresschanged.aspx.
If You are not going to add any other code with more processing in the while loop, You can also use Timer control, as suggested by MD.Unicorn in his answer.

Related

C# Before closing application how to let background worker finish saving application state

In a C# desktop application, a backgroundworker responsible for saving application state is being called in 2 situations. Once while the application is running. That works fine. Other when application is being closed, backgroundworker is called to save the application state but before it starts saving, the application is closed and nothing gets saved.
I tried to solve it by using the AutoReset event class in DoWork and RunWorkerCompleted but didnt work because application closed before backgroundworker could save any thing.
Question is - how can I make the main thread wait until backgroundworker finishes saving?
private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
{
this.saveAHToolStripMenuItem_Click(this, e);
}
private void saveAHAsToolStripMenuItem_Click(object sender, EventArgs e)
{
this.backgroundWorkerMain1.RunWorkerAsync(args);
}
private void backgroundWorkerMain1_DoWork(object sender, DoWorkEventArgs e)
{
saveMethod();
}
private void backgroundWorkerMain1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
showResultOfSaving();
}
Is it WinForms?
Maybe you could register to the OnClosing event.
Within it, set a private property IsClosing to true.
Mark the eventhandler e as e.Handled = true.
Register to the BackgroundWorker event RunWorkerCompleted. Within it, check if the IsClosing property is set and in that case MainForm.Close() the application.
Edit:
using System;
using System.Threading;
using System.Windows.Forms;
using System.ComponentModel;
namespace BgWorker
{
public partial class Form1 : Form
{
BackgroundWorker _bgWorker;
bool _iNeedToCloseAfterBgWorker;
public Form1()
{
InitializeComponent();
}
void Form1_Load(object sender, EventArgs e)
{
_bgWorker = new BackgroundWorker();
_bgWorker.DoWork += _bgWorker_DoWork;
_bgWorker.RunWorkerCompleted += _bgWorker_RunWorkerCompleted;
}
void _bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Done!");
if (_iNeedToCloseAfterBgWorker)
Close();
}
void _bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Do long lasting work
Thread.Sleep(2000);
}
void btnWorkIt_Click(object sender, EventArgs e)
{
// Note how the Form remains accessible
_bgWorker.RunWorkerAsync();
}
void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (_iNeedToCloseAfterBgWorker || _bgWorker.IsBusy) return;
e.Cancel = true;
_iNeedToCloseAfterBgWorker = true;
_bgWorker.RunWorkerAsync();
}
}
}
It would be cleaner to place the SaveState code in a separate method, which is called from the background worker and (synchronously) when you close the application.
Either that, or you can block the application from closing based on a manual reset event (ManualResetEvent-class).
Just throwing an idea out there.
Perhaps a volatile DateTime property on the MainForm.
(Note - DateTimes cannot be volatile it appears, so you could use the string representation instead)
public volatile string _lastUpdated;
This property gets updated everytime the save event is carried out on the background worker thread.
OnClose will check the Time Difference between DateTime.Now and the stored date time.
Eg:
var timeDiff = DateTime.Now - _lastSavedTime;
If the OnClose detects that the timeDiff.TotalSeconds is < 30 (less than 30 seconds)
You can then trigger the save event manually from the main thread, prior to the close event being completed.
This however won't protect against Process.Kill - very little can protect against that.
All I can suggest is that you manage your saves in a smart way.
Eg: Save to a new File each time, keeping the last 5 saves.
When the 6th save is made, delete the oldest save etc
This is to account for the potential corruption that may happen from a Process.Kill scenario. Means you will still have at least a 60 second backup, in case the 30 second backup fails.
My solution to this problem is to wait untill backgroundworker is fnished by adding following after backgroundworker async call.
while (this.backgroundWorkerMain1.IsBusy)
{
Application.DoEvents();
}

Progress bar c# - loading like process [duplicate]

This question already has answers here:
How to use a BackgroundWorker?
(2 answers)
Closed 1 year ago.
Hello I have a function which takes time to load. that's why i'm planning to put a progress bar on my winform so that the user would know that my program is still running. however, I do not know how I'm gonna solve it. is there someone here who could help guide me.
Here's what I'm planning to do:
private void btnProcess_Click(object sender, EventArgs e)
{
//function which takes time because it contacts to a server
}
I want to have a progressbar which increments and ends after my process has finished. Should I use a backgroundworker for this?
***I've followed this tutorial http://www.codeproject.com/Tips/83317/BackgroundWorker-and-ProgressBar-demo but it does not wait for a specific function or event to finish like a loading screen.
***My progressbar does not end after my buttonclick event has finished executing all its functions.
I've created:
private void myBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i <= 100; i++)
{
myBackgroundWorker.ReportProgress(i);
System.Threading.Thread.Sleep(100);
}
}
private void myBackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
myProgressBar.Value = e.ProgressPercentage;
}
private void btnProcess_Click(object sender, EventArgs e)
{
myBackgroundWorker.RunWorkerAsync();
//function which takes time because it contacts to a server
}
How Would I know when will my buttonclick event gonna end? so that my progress bar will end also?
These are the two really good examples
http://www.dreamincode.net/forums/topic/112547-using-the-backgroundworker-in-c%23/
http://www.dreamincode.net/forums/topic/246911-c%23-multi-threading-in-a-gui-environment/
Hope that helps
EDIT:
public partial class Form1 : Form
{
//--------------------------------------------------------------------------
public Form1()
{
InitializeComponent();
//Initialize backgroundworker
Shown += new EventHandler(Form1_Shown);
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.ProgressChanged +=
new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
//counter
YourObject.Counter = 100;
}
//--------------------------------------------------------------------------
void btnClick_Clicked(object sender, EventArgs e)
{
//Trigger the background process
backgroundWorker1.RunWorkerAsync();
}
//--------------------------------------------------------------------------
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//Your freakishly long process,
//which needs to be run in the background
//to make it simple, I'll just do a for loop
for (int i = 0; i < 100; i++)
{
//The counter will keep track of your process
//In your case, it might not be a for loop
//So you will have to decide how to keep track of this counter
//My suggestion is to decrease/increase this counter
//right after importants action of your process
backgroundWorker1.ReportProgress(YourObject.Counter--);
}
}
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//The counter is updated and displayed with a progress bar
YourObject.Counter = e.ProgressPercentage;
}
}
Yeah, you should. It's pretty straightforward.
Make a background worker that executes whatever work btnProcess_Click does.
Have it report progress:
worker.WorkerReportsProgress = true;
And now you can have this progress report be triggered by an event you subscribe to.
worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
In doing so, you can create a progress bar than can update itself based on this worker_ProgressChanged event, triggered by your computation.
You can find plenty of implementations of this just by Googling. Good luck, hope this helps.

Backgroundworker ReportProgress event does not fire

I've a backgroundworker which take care of a timer in my application. This is the code:
protected override void OnLoad(EventArgs e)
{
// Inizializzo il backgroundworker
bgwTimer.WorkerReportsProgress = true;
bgwTimer.WorkerSupportsCancellation = true;
bgwTimer.DoWork += (bgwTimer_DoWork);
bgwTimer.RunWorkerCompleted +=(bgwTimer_RunWorkerCompleted);
bgwTimer.ProgressChanged += (bgwTimer_ProgressChanged);
}
void bgwTimer_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
throw new NotImplementedException();
}
void bgwTimer_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
throw new NotImplementedException();
}
Basically the event "ProgressChanged" is never fired and so I cannot update the status of a progressbar.
The event DoWork is linked to this method:
void bgwTimer_DoWork(object sender, DoWorkEventArgs e)
{
int i = 0;
if (bgwTimer.CancellationPending)
{
e.Cancel = true;
}
else
{
while (bgwTimer.IsBusy)
{
Thread.Sleep(1000);
bgwTimer.ReportProgress(i);
refreshTimer();
}
}
}
By my side the code looks good and it runs fine. As you can see the ReportProgress method is called but the event is not fired. Any hints?
UPDATE:
Whops! I found that the event "bgwTimer_ProgressChanged" is fired only if I run the RunWorkerAsync right after the declaration of event. Basically:
bgwTimer.ProgressChanged += (bgwTimer_ProgressChanged);
bgwTimer.RunWorkerAsync(); //this works!
Since I run the worker when the user press a button, the event is not triggered.
Here's the code of click event button:
private void btnNext_Click(object sender, EventArgs e)
{
this.TopMost = true;
btnNext.Enabled = false;
progressBar1.Step = 0;
if (_bgwTimer.IsBusy)
_bgwTimer.CancelAsync();
else
_bgwTimer.RunWorkerAsync();
}
Put a breakpoint, or a Debug.Print or System.Windows.Forms.Messagebox just before bgwTimer.ReportProgress(i), to verify that you're actually entering the while loop.
Note that the BackgroundWorker is not actually a timer; it's a wrapper for a thread that provides a threadsafe invoking layer for your user interface.
Your if (bgwTimer.CancellationPending) { } should be inside the while loop, not outside it. It will only get checked once in your current code.
Note that, if you're inside the DoWork event handler, then by definition you're running an asynchronous process, so IsBusy should always be true (according to the MSDN documentation), and therefore your while is an infinite loop. But check it with your debugger.
It's not raising the event because the value of i is always zero, which is helpfully undocumented but I found out the same thing when building a background worker a while back.
You forgot to start the worker. Add this line to your OnLoad() method:
bgwTimer.RunWorkerAsync();
In the DoWork-Method replace bgwTimer through ((BackgroundWorker)sender). Maybe this is the problem

BackgroundWorker.ProgressChanged and RunWorkerCompletedhandler still want invoke?

I'd like to use a BackgroundWorker for background operations because I thought there is no need to take care of "BeginInvoke" etc. when updating WinForm-Controls. Is that right? As far as I know, you can update WinForms controls directly by using the ProgressChanged and RunWorkerCompleted eventhandlers.
But I can't, I although get the following exception:
Control control name accessed from a thread other than the thread it was created on
Some code:
public partial class ConfigurationForm : Form
{
public ConfigurationForm()
{
InitializeComponent();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
label1.Text = String.Empty;
// [...]
}
private void StartButton_Click(object sender, EventArgs e)
{
if (backgroundWorker1.IsBusy != true)
{
label1.Text = "Converting...";
backgroundWorker1.RunWorkerAsync();
}
}
private void CancelButton_Click(object sender, EventArgs e)
{
if (backgroundWorker1.WorkerSupportsCancellation == true)
{
backgroundWorker1.CancelAsync();
}
progressBar1.Dispose();
this.Close();
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
// EXCEPTION here, why?
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
Converter c = new Converter();
c.Start(worker, e);
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// EXCEPTION in all cases, why?
if (e.Cancelled == true)
{
label1.Text = "Canceled";
}
else if (e.Error != null)
{
label1.Text = "Error: " + e.Error.Message;
}
else
{
label1.Text = "Done!";
}
}
}
I have to say, this is not a WinForms application but an VSTO PowerPoint add-in. The Form above gets created by the add-in like this when the user is clicking an icon in the ribbon bar of PowerPoint:
//Do I need [STAThread] here? but doesn't seem to work anyway
private void button1_Click(object sender, RibbonControlEventArgs e)
{
ConfigurationForm config = new ConfigurationForm();
config.Show();
}
Can you tell me what's the problem here?
I posted the link but I don't actually think it is the best solution. Clearly the failure occurs because you never called Application.Run() or used Form.ShowDialog(). You can assign the context explicitly as shown but you can get some very tricky problems if you don't do it right. Like assigning it more than once.
The better fix is to ask it to automatically install itself. Which then ensures that whatever form you create will then install it only when it wasn't done before. Put this in front of the form creation code:
WindowsFormsSynchronizationContext.AutoInstall = true;
With the big advantage that if the code ever gets repeated, you won't create another instance of it and potentially screw up the thread's ExecutionContext.
Do consider ShowDialog() as another fix. If I'm not mistaken then you now also have a problem with tabbing and shortcut keystrokes.
Your assumption would be correct for Windows Forms. The way it works though is BackgroundWorkers uses the SynchronizationContext of the current thread. In a windows app, that will be a WindowsFormsSynchronizationContext, which does the marshalling for you.
In a VSTO app, it won't be. It will probably just be the default one, which simply executes the methods. The link from Hans Passant has the code you need to get it to work as expected. I.e.:
System.Threading.SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
...create and start your background worker here...

How can I stop my while loop?

I would like to use the following code in C# but I just can't seem to get out of it. I would like to terminate the application if the user presses a key or moves the rodent (aka mouse). Here is my code (no laughing!).
private void frmDots_KeyDown(object sender, KeyEventArgs e)
{
bgNotClicked = false;
Close();
}
private void frmDots_Click(object sender, EventArgs e)
{
bgNotClicked = false;
Close();
}
while (bgNotClicked)
{
// Clear the first element in our XY position. This is the reverse of the way I normally create the dots application
System.Drawing.Rectangle clearDots = new System.Drawing.Rectangle(Dots.PositionX[iCounter], Dots.PositionY[iCounter], 8, 8);
// Create the black color and brush to clear dots
Color clearDotsColor = Color.Black;
SolidBrush clearDotsBrush = new SolidBrush(clearDotsColor);
// Finally clear the dot
e.Graphics.FillEllipse(clearDotsBrush, clearDots);
GetRandomPosition(iCounter);
// Fill the elements to display colors on the displays canvas
System.Drawing.Rectangle colorDots = new System.Drawing.Rectangle(Dots.PositionX[iCounter], Dots.PositionY[iCounter], 8, 8);
// Create the color and brush to show dots
Color colorRandom = GetRandomColor();
SolidBrush colorBrush = new SolidBrush(colorRandom);
// Finally show the dot
e.Graphics.FillEllipse(colorBrush, colorDots);
Thread.Sleep(5);
iCounter++;
if (iCounter == 399)
{
iCounter = 0;
}
}
}
Your "busy waiting" strategy is poor design. Instead, you should use event handlers that are fired:
On keypress.
When the mouse is moved.
In either case, you can respond by terminating the application.
Edit:
After seeing your edit, this is definitely your problem. The issue is that your while loop blocks the main UI thread, so it never handles the Windows Messages which trigger your key press/mouse/etc handlers.
You have a couple of options - you can either move some of this onto a separate thread, do what I suggested below, or add a call to Application.DoEvents in your while loop. This would allow your event handlers to run, which would in turn set bgNotClicked = false;. Right now, that's never occurring because your UI is blocked entirely.
Original Post:
If you're doing this loop in your UI thread, you're going to need to rethink the design a bit.
Setting bgNotClicked = false; somewhere in an event handler will work, but only if your event handler is able to run. If you're doing the above code in the UI thread, it will block your UI thread indefinitely, preventing the event handler from firing.
I would recommend reworking this to be based off a timer (so it runs repeatedly on regular intervals), instead of locked into a while loop. This would allow your other UI events to fire between runs, and instead of setting bgNotClicked = false;, your event handler could just set the timer to be not enabled.
Your bgNotClicked variable needs to be set to false by your event handler for key-press.
If the rodent is moved by your mouse, you would need a similar mouse event handler.
The break keyword will terminate a loop. In this case, when you hit the case where you want to stop the loop, you would just use break;.
If you're looping like that you need to give the application a moment to process the events that you're hoping will cause the interruption. This is the job of the DoEvents method.
private bool WeAreDone = false;
private void DoingIt()
{
while (true)
{
Application.DoEvents();
if (WeAreDone)
{
break;
}
}
}
private void InterruptButton_Click(object sender, EventArgs e)
{
WeAreDone = true;
}
I think using a Timer fits the Windows event-driven model better than the busy wait while loop. You might try something like this:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private int iCounter = 0;
private void Draw()
{
// ....
}
private void timer1_Tick(object sender, EventArgs e)
{
Draw();
iCounter++;
if(iCounter == 399)
{
iCounter = 0;
}
}
private void Form1_Load(object sender, EventArgs e)
{
timer1.Interval = 5;
timer1.Enabled = true;
}
private void Form1_Click(object sender, EventArgs e)
{
timer1.Enabled = false;
Close();
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
timer1.Enabled = false;
Close();
}
}
This does not seems to be the correct way. .Net Framework has provided you with the events to handle the KeyPress and MouseMove/Click actions. Why are you not using them?
Try moving the loop into a BackgroundWorker's DoWork event handler. Then your GUI will still be responsive and instead of that nasty variable, you can just call the CancelAsync method to stop the loop.
You can exit the loop using the break statement.
EDIT: OK, I take back the flag thing!
Use Environment.Exit(2); (or Environment.Exit(1) it really doesn't make a difference) to exit out of the application.
Exit While
...................

Categories

Resources