I have a list of tasks running and would like to show the progress in a (WinForms) form with a Cancel button.
I am aware, that there are several async options, but I have two restraints: The tasks must not run on a separate thread and the solution must be compatible with .NET 3.5 (it is an AddIn for a program, I have no access to).
It is fine, if one task finishes, before the cancellation comes into force. So I wonder, if there is some chance to check in synchronous code, if a mouse click on a button happened while having performed some task?
edit: This is the intended code:
foreach (IStep step in Steps)
{
if (Cancelled)
return;
step.Run();
ReportProgress(100.0 * completedWeight / totalWeight, step.Description);
completedWeight += step.Weight;
}
ReportProgress(100, "Completed");
So IStep contains a Run() method, and I am perfectly fine with completing a step before cancelling. I do not know how to catch mouse click on the Cancel button while executing some step to set Cancelled to true.
Obviously there is no "standard" solution here, so we have to think outside the box...
Say you have your application (AddIn or whatever, doesn't matter) and you can't control the loop from a button.
You read/write to the database.
On top of your loop, where it says:
if (Cancelled)
return;
We have to replace with:
If(CheckIsCancelled())
You have to find a way to make a button that can be clicked, either another form near the current one, but it must be able to run independently from the current form that is blocked by your loop.
Create a database parameter in some sort of Config/Util table.
E.g. CancelMyLoop - Bit
On that button click - set the parameter value to true.
And back to the method: CheckIsCancelled()
it will go in the db and read that value every time.
Downside is performance, but you want the impossible so you have to settle with a workaround like this...
You can create your own implementation, just giving you an idea.
Related
At the moment I'm trying to debug some code in which I'm cheking for the visibility of items (with the .IsVisible() method for example). The problem is, when I'm jumping from one breakpoint to the next or jumping between lines, the data obviously changes, but the UI of the program doesn't seem to change at all. That makes it a bit difficult for me to tell if things are visible and I have to trust Visual Studio.
Is there a way I can make the UI update while debugging, so I can see the changes over there as well?
You have to force a synchronous re-render of the UI. You could define this extension method somewhere:
public static void SynchronouslyRedraw(this UIElement uiElement) {
uiElement.InvalidateVisual();
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() => { })).Wait();
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() => { })).Wait();
}
and call it on your Window after each breakpoint (using the immediate window, a breakpoint action, an additional line of code etc). It should synchronously re-render the Window in question:
this.SynchronouslyRedraw(); // assuming your breakpoint is in your `Window` class for example.
Note that this method works on any UIElement that is in the visual tree of a Window object.
The UI only updates when you give it a chance - that means not occupying the UI thread with other work (such as your event handler).
There isn't a simple way to do this in the debugger in WPF. In Windows Forms, you can just use Application.DoEvents();, but WPF requires you to await Dispatcher.Yield();, so you can't just execute it whenever you want. Both solve the same problem in much the same way - they give the message loop an opportunity to handle all the pending messages, and then go back to where you left off. Both also have the same weakness - they introduce an opportunity for reentrancy, so be careful.
I'm converting a WinForms app over to GTK# and I want to simulate the OnMouseDoubleClick method on a GTK# DrawingArea widget.
In WinForms, you have two methods you can override, one for a single click and one for a double click. If WinForms only detects a single click, it only calls the OnMouseSingleClick method, and if it detects a double click, it only calls the OnMouseDoubleClick method.
Now in GTK#, you only have a single method you can override for clicking, OnButtonPressEvent. If you single click, OnButtonPressEvent only gets invoked once, but if you double click, it gets invoked three times! This is because GTK sends a signal for each click, PLUS one for the double click.
There are similar questions on this site but none of the answers have been about the widget I'm using, or the answers weren't satisfactory. So my question again is, how do I simulate WinForms OnMouseDoubleClick method with GTK#?
GTK doesn't offer the behavior implemented by WinForms, likely because it entails a delay before a genuine single click takes effect. (Without that delay WinForms would emit at least one spurious OnMouseSingleClick before OnMouseDoubleClick. This delay is very visible when you use single-click to rename a file in a file manager that also uses double-click to open the file.)
To implement it yourself, you need to do the following, in pseudo-code:
Add a boolean wait and an unsigned handlerId member to your class. Set wait to false.
When OnButtonPress receives a single-click and wait is false: Set wait to true and use GLib.Timeout.Add to schedule an OnWaitForDoubleClickTimeout method to run after the delay equal to the GTK double-click interval. You can obtain the double-click interval as the gtk-double-click-time property from Gtk.Settings. Store the return value of GLib.Timeout.Add in handlerId.
When OnButtonPress receives a single-click and wait is true: Cancel the timeout by calling GLib.Source.Remove(handlerId), reset wait to false, and otherwise ignore the event.
When OnButtonPress receives a double-click: Invoke OnMouseDoubleClick(). At this point, due to #3, wait should be false.
In OnWaitForDoubleClickTimeout, reset wait to false and invoke OnMouseSingleClick. Finally, return false so the same timeout doesn't fire again.
I'm working on a simulation code where I have a "Project" which can hold numerous simulations. You can choose to run them one at a time, or you can run them all in sequence. In this specific case, I have 18 simulations which run one by one, and overall the process takes about 20 sec.
In short, a button is pressed on a form, and the following actions occur:
1) Create simulation object
2) Perform simulation start command
3) Write simulation data to file
4) Dispose simulation object
5) Update DataGridView which holds the simulation list (rewrites "Processing" to "Complete")
6) Update progress bar value in user control.
7) Refresh user control.
Rough source code is as follows:
for (int i = 0; i < dataSet.Count; i++)
{
using (Processor p = new Processor())
{
bool didTestPass = p.RunTest(dataSet[i]);
if (didTestPass)
dataGridViewProcessList.Rows[i].Cells[5].Value = "Run complete.";
else
dataGridViewProcessList.Rows[i].Cells[5].Value = "Run completed with errors.";
}
progressBarRuntime.Value = ((i+1) / dataSet.Count) * 100;
this.Refresh();
this.OnUpdateMainForm(this, null);
}
What I've found is, if you remain within the application's focus, all 18 simulations run fine. However, if you drop focus (say, switch to another program), it consistently behaves erratically at the 8th simulation. I say erratically because it acts differently:
When debugging through Visual Studio, the form freezes briefly, then suddenly all remaining simulations are processed and the progress bar snaps to full.
When running as a standalone program, it crashes straight to desktop. No warning, no exception throw, nothing.
I've also found that if I stay focused and let it reach, say, simulation 14, then drop focus from the program, it will immediately exhibit the above behavior.
I'm not particularly familiar with the concept of performing large calculation efforts under the hood while a Windows Form is active. At first I felt like maybe the Form needed to be refreshed (since this is all happening on a UserControl) but I saw no difference when I put in an event to force the Form to Refresh().
I ended up discovering that the source of my problem was that I was performing work on the interface's main thread, and as a result it was causing my program to hang and crash to desktop.
I created a BackgroundWorker and placed my work code into the DoWork event, then moved the ProgressBar indicator update into the ProgressChanged event.
More details regarding BackgroundWorker and its implementation at MSDN.
In short, the lesson learned for me is that if an activity in a Form is going to take longer than a few seconds, it should be done using a BackgroundWorker to prevent hanging up the interface.
Ok, I'm looking for something pretty simple: creating a MessageBox that doesn't stop my code.
I'm guessing I'll have to create a different thread or something? Please advise on the best way to accomplish this.
Thanks!
You could spin up another message pump by calling it on separate thread. MessageBox.Show pumps message so it is safe to do without a call to Application.Run.
public void ShowMessageBox()
{
var thread = new Thread(
() =>
{
MessageBox.Show(...);
});
thread.Start();
}
Edit:
I probably should mention that I do not recommend doing this. It can cause other problems. For example, if you have two threads pumping messages then it is possible for the message box to get stuck behind another form with no way to make it go away if the form is waiting for someone to close the message box. You really should try to figure out another way around the problem.
No, You're going to have to make your own message box form. the MessageBox class only supports behavior similar to .ShowDialog() which is a modal operation.
Just create a new form that takes parameters and use those to build up a styled message box to your liking.
Update 2014-07-31
In the spirit of maintaining clarity for anyone else who finds this through google I'd like to take a second to explain this a bit more:
Under the hood MessageBox is a fancy C# Wrapper around the Windows SDK user32.dll MessageBox Function and thus behaves exactly the same way (after converting .NET Enums into the integers that represent the same thing in the system call.
What this means is that when you call MessageBox.Show() the call is marshaled out to the OS and will block the current thread until an option is selected or the window is killed. To prevent your code from being halted you need to launch the message box on a seperate thread, but this will mean that any result that comes back from the message box (Yes / No / Ok / Cancel / Etc...) will be returned to the separate thread that was tasked to call the message box.
If you act on the result of this message box launched this way you'll have to Dispatch the result back to the UI Thread for Thread Saftey.
Alternatively you can create your own message box form in WinForms / WPF and call it with the .Show() method. Any click events on the buttons will execute on the UI Thread and you will not have to dispatch the calls back to the UI Thread to manipulate things in the UI.
Did some searches here & on the 'net and haven't found a good answer yet. What I'm trying to do is call a button twice within the same class in C#.
Here's my scenario -
I have a form with a button that says "Go". When I click it the 1st time, it runs through some 'for' loops (non-stop) to display a color range. At the same time I set the button1.Text properties to "Stop". I would like to be able to click the button a 2nd time and when that happens I would like the program to stop. Basically a stop-and-go button. I know how to do it with 2 button events, but would like to utilize 1 button.
Right now the only way to end the program is the X button on the form.
I've tried different things and haven't had much luck so far so wanted to ask the gurus here how to do it.
BTW, this is a modification of a Head First Labs C# book exercise.
Thanks!
~Allen
You would need to use Multithreading (launch the process intensive code asynchronously in a separate thread), for instance, using the BackgroundWorker object in .NET 2+. This would be necessary because your UI will not respond to the user's click until the loop running in the Start method is completed. It is quite irrelevant if you use the same button or another one to toggle the process, because the processor is busy processing the loop.
The BackgroundWorker has a property called WorkerSupportsCancellation which needs to be true in this scenario. When the user clicks Stop you would invoke the CancelAsync method of the BackgroundWorker.
See MSDN for a good example. Also DreamInCode has a good tutorial which seems quite similar to your requirement.
Why not create two buttons, hide one when the other is visible? That should be a lot of easier to handle.
Or you can add a bool field to indicate which operation branch to execute.
One simple solution would be to add a boolean member to your form that is, e.g., true when the button says "Go" and false when the button says "Stop".
Then, in your button's event handler, check that boolean value. If the value is true, then start your operation and set the value to false when you change the button's text to say "stop". Vice-versa for the other case. :)
There are other techniques that I might prefer if this were production code, perhaps including considering the design of the form more carefully, but as this is clearly a learning exercise I believe that a simple boolean flag indicating the current state of the form is just what you're looking for.
Note that I would strongly discourage you from checking the value of the button text to determine what state the object is in. Whenever possible, as a general rule of good design, you want your visual state to be "decoupled" from your underlying object's state. That is to say, your visual widgets can depend on your underlying objects, but your underlying objects should not depend on your visual widgets. If you tested the text of the button, your underlying logic would depend on your visual state and that would violate this general rule.
If your problem is related to the fact that you can't cancel the operation while it's being performed, you'll want to look into using a BackgroundWorker to perform your long-running activity.
Another option would be to check the current text on your button to determine what to do:
void btnStartStop_Click(Object sender, EventArgs e)
{
if (btnStartStop.Text == "Go")
{
btnStartStop.Text = "Stop";
// Go code here
}
else
{
btnStartStop.Text = "Go";
// Stop code here
}
}
Are you getting your second button click event? Put a breakpoint in your click handler and run your code. When you click the second time, do you ever hit your breakpoint?
If your loop is running continuously, and it is in your button click handler, then your loop is running in the UI thread. You probably don't get to "see" the second button click until after the loop is completed. In addition to the branch code that you see above, try either inserting a DoEvents in your loop processing (this is a place where your loop will temporarly give up control so that messages can be processed). Or, (better) have a look at the backgroundworker class -- do most of your processing in a different thread, so that you UI can remain responsive to button clicks.
Cerebrus is right about using the Background Worker thread. However if you are doing a WPF app then it won't be able to update the UI directly. To get around this you can call Dispatcher.BeginInvoke on the main control/window.
Given code like:
Private Delegate Sub UpdateUIDelegate(<arguments>)
Private Sub CallUpdateUI(<arguments>)
control.Dispatcher.BeginInvoke(Windows.Threading.DispatcherPriority.Background, New UpdateUIDelegate(AddressOf UpdateUI), <arguments>)
End Sub
Private Sub UpdateUI(<arguments>)
'update the UI
End Sub
You can call CallUpdateUI from the Background Worker thread and it will get the main thread to perform UpdateUI.
You could set the Tag property on the button to a boolean indicating whether the next action should be "Stop" or "Go", and reset it each time you click the button. It's an Object property, though, so you'll have to cast it to bool when you read it.