Adding your own update code (MainLoop, PeekMessage)

If you have a cool piece of software to share, but you are not hosting it officially yet, please dump it in here. If you have code snippets that are useful, please donate!
Post Reply
MrDoom
Knows some wx things
Knows some wx things
Posts: 31
Joined: Sat Aug 16, 2008 9:24 pm
Location: England
Contact:

Adding your own update code (MainLoop, PeekMessage)

Post by MrDoom »

The Problem
You want to process something whenever wxWidgets is not processing any events of it's own, (when using Win32 code, this would be like using PeekMessage), but you're not sure how to change the MainLoop in wxWidgets.

The Solution
After numerous attempts to solve this and an evening of reading, I finally have a solution that works; this solution has been tested using wxWidgets 2.8.8 with Visual Studio 2008 on Windows XP.

The solution works by using wxWidgets' event system to call a function whenever wxWidgets is idle (so no MainLoop changes required). To start with, you will need a class derived from wxApp (chances are you have this already by I will include a cut down version of my header for clarity):

Code: Select all

class CApplication : public wxApp
{
	/* ... */

	public:	
		void DoUpdate(wxIdleEvent &event);

	/* ... */

	DECLARE_EVENT_TABLE()
};
The important parts to note in the above code is the "DoUpdate" function, this will be called whenever wxWidgets is idle; the "DECLARE_EVENT_TABLE" macro simply sets up the class to be able to use the event table (something which you will see more of later).

Now, in the CPP file for the above class, you will need to add some more macros to finish setting up the event table to call your "DoUpdate" function:

Code: Select all

BEGIN_EVENT_TABLE(CApplication, wxApp)
	EVT_IDLE(CApplication::DoUpdate)
END_EVENT_TABLE()
That registers your "DoUpdate" function to be called on an "Idle" event. Now to define the "DoUpdate" function itself:

Code: Select all

void CApplication::DoUpdate(wxIdleEvent &event)
{
	/* ... */

	if(IsMainLoopRunning())
		event.RequestMore();
}
You can put whatever you like in the ellipses comment, but the last part of the function is important. Since by default the "Idle" event is only passed once until something else happens in the application and it becomes idle again; you need to use "RequestMore" to keep adding the event to the event queue until you no longer need it (in the above case, that is the application exiting).

Notes
I hope this helps someone, I have seen many forum posts on this subject either unanswered or totally misinterpreted so I'm pretty sure this will help someone. Finally, I make no assumptions that this is the best solution, I have only been using wxWidgets for under a day myself and was looking for something with the relative simplicity of PeekMessage while avoiding threads.

Good luck. :D
wx_KNOX
Earned a small fee
Earned a small fee
Posts: 12
Joined: Wed Nov 19, 2008 4:19 am

Post by wx_KNOX »

I'm really surprised that there are no comments or replies to this.
I tried head and tail to figure this out.
But it looks like this is the only viable solution to this.

I tried lot's of work around, but none of them work.

Thanks doom for this.

One question though, has this method worked for people consistently across all platforms?
DavidHart
Site Admin
Site Admin
Posts: 4252
Joined: Thu Jan 12, 2006 6:23 pm
Location: IoW, UK

Post by DavidHart »

wx_KNOX wrote:I'm really surprised that there are no comments or replies to this.
Perhaps because it just describes the standard wx solution to the problem. See, for example, http://wiki.wxwidgets.org/WxIdleEvent

Incidentally, it is more usual to put the idle-event handler in a class other than wxApp: e.g. I've connected to idle-events in my frame class, to call code that needs to be run once when the frame is shown and has reached its intended size.
One question though, has this method worked for people consistently across all platforms?
Yes

Regards,

David
wx_KNOX
Earned a small fee
Earned a small fee
Posts: 12
Joined: Wed Nov 19, 2008 4:19 am

Post by wx_KNOX »

If I use an Idle event in my application, and in the event handler, I realize that my app is running way too fast.
Let's assume it's running at 700 fps. :)

Say I want it clamped down to 100 fps.
Is it safe to put a 'Sleep' in the Idle-event-handler?

Does the idle-event-handler work as expected in the Full screen window frame also?
DavidHart
Site Admin
Site Admin
Posts: 4252
Joined: Thu Jan 12, 2006 6:23 pm
Location: IoW, UK

Post by DavidHart »

If I use an Idle event in my application, and in the event handler, I realize that my app is running way too fast.
Is it safe to put a 'Sleep' in the Idle-event-handler?
I've no idea, but if you want controlled timing, use a wxTimer instead.
Does the idle-event-handler work as expected in the Full screen window frame also?
Why not try it and see, but I'd be very surprised if it didn't.
wx_KNOX
Earned a small fee
Earned a small fee
Posts: 12
Joined: Wed Nov 19, 2008 4:19 am

Post by wx_KNOX »

The earlier post I was actually referring to a real-time application like games.
Is 'handling the idle' event the right way then?
Is there any other way of doing it for real-time applications?
MrDoom
Knows some wx things
Knows some wx things
Posts: 31
Joined: Sat Aug 16, 2008 9:24 pm
Location: England
Contact:

Post by MrDoom »

Very late response from me, I haven't checked this forum in ages. Exactly one year it would seem.
DavidHart wrote:Incidentally, it is more usual to put the idle-event handler in a class other than wxApp: e.g. I've connected to idle-events in my frame class, to call code that needs to be run once when the frame is shown and has reached its intended size.
Yes, that would make more sense, and that is what I would do nowadays. As I said, I had only been using wxWidgets for a day when I posted this.

wx_KNOX wrote:The earlier post I was actually referring to a real-time application like games.
Is 'handling the idle' event the right way then?
Is there any other way of doing it for real-time applications?
I don't see anything wrong with that approach, I liken it to using PeekMessage which is what I do when using a Win32 window as a game shell. Really your render and update rate should be independent, I use an accumulator timer to have a fixed update step regardless of how fast the game is rendering.
DEmberton
In need of some credit
In need of some credit
Posts: 9
Joined: Thu Oct 29, 2009 11:09 am

Post by DEmberton »

wx_KNOX wrote:If I use an Idle event in my application, and in the event handler, I realize that my app is running way too fast.
Let's assume it's running at 700 fps. :)

Say I want it clamped down to 100 fps.
Is it safe to put a 'Sleep' in the Idle-event-handler?
The problem with Sleep is that it blocks everything and stops messages getting through. The better way of doing this sort of thing is to override the whole main loop. You can use MsgWaitForMultipleObjects (a Windows function, so I've no idea what the equivalent is for other platforms) to block for a certain time or until a message arrives.

I've attached an example. Feel free to do what you like with it. It has a 100ms delay, but you can easily do something more sophisticated to try to acheive a certain update rate.
Attachments
MessageLoop.cpp
(3.25 KiB) Downloaded 608 times
VS2005. Windows 7 Pro. 2.9.0.
User avatar
cutecode
Super wx Problem Solver
Super wx Problem Solver
Posts: 425
Joined: Fri Dec 09, 2016 7:28 am
Contact:

Re:

Post by cutecode »

I've attached an example.

Code: Select all

   while(!message && wxTheApp && wxTheApp->ProcessIdle());
   {
	message = Pending();
   }
Why did you insert ';' at the end of the while loop?
Was it intentionally?
wx 3.1.6 win/mac/linux

regards,
Alexander Saprykin
https://v2.dental-soft.ru
thatszechuan
In need of some credit
In need of some credit
Posts: 2
Joined: Fri Aug 23, 2019 5:28 pm

Re: Adding your own update code (MainLoop, PeekMessage)

Post by thatszechuan »

MrDoom wrote: Sat Aug 16, 2008 9:53 pm

Code: Select all

void CApplication::DoUpdate(wxIdleEvent &event)
{
	/* ... */

	if(IsMainLoopRunning())
		event.RequestMore();
}
I have been trying to get some sort of auto-refresh working in a test program and also had issues finding a good example. A lot of half-answers and low effort C+P but no discussion of the implementation like you included in your post!

It's a very simple program updating a total value based on the sum of some text boxes - I want the total to change as the values are changed.

Even with event.RequestMore() the total value is updating only the first time the program is run, using the initial variables. If I change the initial values it will update the total when run, but it is not responding to real time changes in the values. The output box is flickering like it is being updated but the variables are not changing. If I'm missing something obvious I'd really like to know!

Code: Select all

void MyTestApp::OnIdle(wxIdleEvent& event)
{
//Grabbing a float
    float subtotal = (std::strtof (TextCtrl4->GetLabel(),NULL) + std::strtof (TextCtrl5->GetLabel(),NULL))*std::strtof(TextCtrl7->GetLabel(),NULL); 

//Setting the rounded float
    TextCtrl14->SetLabel(std::to_string(floor(subtotal*10)/10)); 

//Keep updating?
    event.RequestMore(true);
}
thatszechuan
In need of some credit
In need of some credit
Posts: 2
Joined: Fri Aug 23, 2019 5:28 pm

Re: Adding your own update code (MainLoop, PeekMessage)

Post by thatszechuan »

Solution found for my question: I am a huge idiot and was using Get/Set Label instead of Get/Set Value. :(
Post Reply