Handing double clicks in GTK# - c#

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.

Related

Catching cancel button click between two synchronous tasks

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.

Why does Refresh() not do what DoEvents() does?

I am trying to understand a certain longstanding concept in Windows Forms re: UI programming; following code is from Chris Sells' Windows Forms Programming book (2nd Ed., 2006):
void ShowProgress(string pi, int totalDigits, int digitsSoFar) {
// Display progress in UI
this.resultsTextBox.Text = pi;
this.calcToolStripProgressBar.Maximum = totalDigits;
this.calcToolStripProgressBar.Value = digitsSoFar;
if( digitsSoFar == totalDigits ) {
// Reset UI
this.calcToolStripStatusLabel.Text = "Ready";
this.calcToolStripProgressBar.Visible = false;
}
// Force UI update to reflect calculation progress
this.Refresh();
}
This method is part of small sample application that has another long-running method which calculates Pi. Each time a cluster of digits are calculated, ShowProgress() is called to update the UI. As explained in the book, this code is the "wrong" way of doing things, and causes the UI to freeze when the application is minimized and then brought into the foreground again, causing the system to ask the application to repaint itself.
What I don't understand: Since this.Refresh() is being called repeatedly, why doesn't it process any system repaint event that is waiting for attention?
And a follow-up question: When I add Application.DoEvents() immediately following this.Refresh(), the freeze-up problem disappears. This is without having to resort to Invoke/BeginInvoke, etc. Any comments?
Basically, the reason for this is the way Windows handles messages - it does this in a synchronous way in an internal message loop.
The point is that there was a message that triggered your code. For example a button click. Your application is in the middle of handling the message. From within this handler, you force the refresh which puts another WM_PAINT in the message queue. When your handler finishes, the message loop will surely pick it up and dispatch, thus repainting the control. But your code is not finished, in fact it loops calling your ShowProgress, causing WM_PAINT being queued forever.
On the other hand, the DoEvents() causes an independent instance of the message loop to fire. It's fired from within your code which means that the call stack looks like this:
outer message loop -> your code -> inner message loop.
The inner message loop processes all pending messages, including the WM_PAINT (thus the control is redrawn) but it is dangerous - as it will dispatch all other pending messages, including button clicks, menu clicks or event closing your application with the X at the top-right corner. Unfortunately, there's no easy way to make the loop to process the WM_PAINT only which means that calling DoEvents() exposes your application to subtle potential problems involving unexpected user activity during the execution of your code which triggers the DoEvents.

messagebox without wait or suspend

I need to show a message box to user, with yes no options, but not to suspend the process of my function, it is a lengthy function that takes a minute or so to complete.
the task of messagebox is to asks the user to continue or not. if the user clicks yes, nothing special happens, the work continues to complete, if user don't clicks any buttons, the process must do the task, and when it finishes, the messagebox must disappears, but if the user clicks no, the function must exit.(like the calculator when calculating for example 10000000!).
Sounds like a good time to use a Background Worker. Keeps the UI responsive, while the background task does the long computation. It supports cancellation.
For your problem you can write your own form and handle events Also as Mark suggested you can use the benefit of background. In fact message box doesn't have public constructor to create it and assign some events, So you should write it yourself with your own form.

C# How To: Trying to call button twice in same class?

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.

How to determine whether a C# control is being unloaded?

In my C# Windows Forms application, I have a user control that contains other controls and does its own logic. One of them is a delayed call (Timer-invoked) that does certain things after the user has finished a keyboard input (live filter text). It accesses the other controls for this, one of them is that text input control. This method is invoked 500 ms after the last input event.
Now I have a problem when the delayed call is running while the application is terminating. When I enter some text, then wait about 500 ms (it seems to work every time) and then press Alt+F4 to close the window, the application throws a NullReferenceException while trying to access the text input control. This doesn't happen when I close the window immediately after the last input or a second or more after.
It seems that the control is being disposed or something and its methods cannot access the child controls anymore. So, when the control is being put in that state (by whomever and whatever that state exaclty is), those timer need to be stopped first so that the controls can be safely disposed.
I have already tried to stop the timer in the OnHandleDestroyed method (overridden) and at the beginning of the Designer-generated Dispose method. Nothing helped.
This procedure works fine in regular Forms when stopping the timers in the overridden OnFormClosed method, before calling base.OnFormClosed(). I just cannot find a suitable event in a user control.
Try this in your UserControl:
bool isDisposed;
protected override void Dispose(bool disposeManaged)
{
if(!isDisposed)
{
if(disposeManaged)
{
//Dispose your timer here
}
isDisposed = true;
}
}
Another possibility is that one of your UI classes doesn't do its cleanup. Eg. it registers itself for an event but doesn't deregister when it's manually disposed. It is never collected by the GC and when the event is fired for the next time, it tries to access some members that were set to null during the Dispose(...) call before.
Another possibility is that you have a more complex race condition within your code but it's hard to say from here.

Categories

Resources