button event queue question and help needed. Topic is solved

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
Dark Alchemist
Super wx Problem Solver
Super wx Problem Solver
Posts: 347
Joined: Wed Nov 02, 2005 10:33 am

button event queue question and help needed.

Post by Dark Alchemist »

I was wanting to dummy proof a button so the end user couldn't just keep clicking the button while the function was running.

Well, I tried connect/disconnect, enable(true/false) and a combination of the two but the queue just keeps filling up as long as I keep hitting the button so when I am done with the routine then (re)Connect-> the button it just loops over and over until the queue is clean.

This sounds odd since I connect and I disconnect the event queue should ditch (ignore) my button mashing.

What is the proper way to dummy proof buttons?

Code: Select all

WxBitmapButton1->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(Mytest::WxBitmapButton1Click), NULL, this);
... //do something here.
WxBitmapButton1->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(Mytest::WxBitmapButton1Click), NULL, this);
Auria
Site Admin
Site Admin
Posts: 6695
Joined: Thu Sep 28, 2006 12:23 am
Contact:

Post by Auria »

Disconnect returns a boolean of whether it was successful; did you check it?

Also, there is a possibility that your OS queues the mouse clicks while your app is unresponsive and "unleashes" them when the app becomes responsive again.

More generally, we'd need to know more about what's going on between the disconnect and the connect (does it block the UI?), what precisely happens when the user clicks multiple times (re-entrency? method called multiple times in a row? etc.)
"Keyboard not detected. Press F1 to continue"
-- Windows
Dark Alchemist
Super wx Problem Solver
Super wx Problem Solver
Posts: 347
Joined: Wed Nov 02, 2005 10:33 am

Post by Dark Alchemist »

OS is Windows.

See the code I posted above? Just make it this

Code: Select all

WxBitmapButton1->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(Mytest::WxBitmapButton1Click), NULL, this);
wxMilliSleep(2000);
WxBitmapButton1->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(Mytest::WxBitmapButton1Click), NULL, this);
Then just press the button as many times as you wish and that is how many times it runs (I had it print to me each time it entered the routine).
JimFairway
wxWorld Domination!
wxWorld Domination!
Posts: 1059
Joined: Sun Dec 30, 2007 6:40 pm
Location: Canada

Post by JimFairway »

Hi,

Another approach is to protect the routine from being called twice in succession.
Check out wxRecursionGuard see: http://docs.wxwidgets.org/stable/wx_wxr ... guard.html

Hope that helps,

Jim
OS: Vista SP1, wxWidgets 2.8.7.
Dark Alchemist
Super wx Problem Solver
Super wx Problem Solver
Posts: 347
Joined: Wed Nov 02, 2005 10:33 am

Post by Dark Alchemist »

Just glancing at the code it seems it is just a mechanism to set a flag and reset a flag so my code does not go in or out of what it does.

The issue is not that they can reenter my routine (because I can set my own flag to stop that) but there appears to be no mechanism, or at least one I could find, to stop the mashing of the button that stores up the mashing button events *even* when I have disconnected the thing, froze it, disabled it, or baked it at 2500 degrees f. :(
User avatar
doublemax
Moderator
Moderator
Posts: 19116
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Post by doublemax »

The behavior is not really surprising, because the events that occur during your longer calculation are only dispatched after you have connected the event handlers already.

So you need a flag to prevent reentrancy and a wxYield() before enabling the button again, so that the pending events are cleared from the queue.

So you get something like this (untested):

Code: Select all

myframe::Onbuttonclick(...)
{
  static bool s_busy=false;
  if(!s_busy) {
     s_busy=true;
     
     button->Disable();
  
     // do something

     wxGetApp().Yield();
     button->Enable();

     s_busy=false;
  }
}
BTW: From a gui point of view i would block the gui like this for at most 1-2 seconds. If the calculation takes any longer, i would display a progress dialog. As that's a modal dialog, it would also prevent multiple button presses, so you wouldn't need this "hack" ;)
Use the source, Luke!
Auria
Site Admin
Site Admin
Posts: 6695
Joined: Thu Sep 28, 2006 12:23 am
Contact:

Post by Auria »

Agreed with doublemax, blocking the GUI is the wrong approach (because the OS may queue event, as I explained earlier); I would instead recommend running the long calculation in a thread. Then clicking on the button disables it, starts the thread; when the thread is done, it sends and event to the main thread and the GUI is updated. The fundamental difference is that the OS then doesn't think your app is being unresponsive and won't queue events; events will be processed as usual but wx will see the button is disabled and nothing will happen.
"Keyboard not detected. Press F1 to continue"
-- Windows
Dark Alchemist
Super wx Problem Solver
Super wx Problem Solver
Posts: 347
Joined: Wed Nov 02, 2005 10:33 am

Post by Dark Alchemist »

doublemax wrote:The behavior is not really surprising, because the events that occur during your longer calculation are only dispatched after you have connected the event handlers already.

So you need a flag to prevent reentrancy and a wxYield() before enabling the button again, so that the pending events are cleared from the queue.

So you get something like this (untested):

Code: Select all

myframe::Onbuttonclick(...)
{
  static bool s_busy=false;
  if(!s_busy) {
     s_busy=true;
     
     button->Disable();
  
     // do something

     wxGetApp().Yield();
     button->Enable();

     s_busy=false;
  }
}
BTW: From a gui point of view i would block the gui like this for at most 1-2 seconds. If the calculation takes any longer, i would display a progress dialog. As that's a modal dialog, it would also prevent multiple button presses, so you wouldn't need this "hack" ;)
You are, of course, correct as it takes about 2.5-3 seconds and the gui goes a gray translucent due to being locked out.

I have my events down but I am getting confused as the best way to handle this.

So, I always write my button click routines that call the real routine (I do this because I reuse the code in other non event driven procedures/routines) and when the button's event fires I wish to show the button as pressed then lock it in the pressed view (easily done) but to disallow any more button presses to be queued until I am ready to react to them again (meaning my real routine is done with whatever it is to do).

So, is there any other way besides a modal of doing this since Yield will (100% sure of this) make the gui go translucent gray which freaks people out (looks like it crashed iow)?
Dark Alchemist
Super wx Problem Solver
Super wx Problem Solver
Posts: 347
Joined: Wed Nov 02, 2005 10:33 am

Post by Dark Alchemist »

Auria wrote:Agreed with doublemax, blocking the GUI is the wrong approach (because the OS may queue event, as I explained earlier); I would instead recommend running the long calculation in a thread. Then clicking on the button disables it, starts the thread; when the thread is done, it sends and event to the main thread and the GUI is updated. The fundamental difference is that the OS then doesn't think your app is being unresponsive and won't queue events; events will be processed as usual but wx will see the button is disabled and nothing will happen.
Just thinking about this and you might be right.

Code: Select all

button routine
{
    if (button_is_pressed == false)
	{
		lock_button_down_visually
		button_is_pressed = true;
		start_thread
	}
}

wait_on_button_thread_event
{
	unlock_button_down_visually;
	button_is_pressed = false;
}
I gotta try this. :)
Dark Alchemist
Super wx Problem Solver
Super wx Problem Solver
Posts: 347
Joined: Wed Nov 02, 2005 10:33 am

Post by Dark Alchemist »

The above that I described worked perfectly.

What I did was set the bitmap label to the same as SetBitmapSelected then set my flag and off I went. Now you may click the button as many times as you wish and it looks like nothing is happening but in reality it is triggering the event just not acting on it. Then when my routine (will become a wxthread) is done I send the event which resets the bitmaplabel and the flag.

Smoke and mirrors but it works.

Thanks everyone.
Post Reply