I had a problem a while back. I was writing a level editor using MFC and OpenGL, and couldn't seem to get the camera to move smoothly with the messages MFC was sending my app. It was also inconvenient to call an update function every time I did something like move an object. What I needed was the ability to continuously update my OpenGL views. But how? Well, to cut a long story short, I found out after quite a bit of digging around in MFC and the web that it's actually quite simple.
Basically, all you need is a way of replicating the familiar Win32 message loop inside MFC. The question is: how? At first glance, an MFC application doesn't appear to have a message loop. It's event-driven. However, take a look in thrdcore.cpp, in your MFC source folder, and you'll find the message loop, neatly tucked away in a member function of CWinThread called Run. Follow the trail back a little further to the function that calls CWinThread::Run and you come to CWinApp::Run, an overridable virtual function of the class which every MFC app has, yours and mine included. The trick to continuous updating in an MFC application is to override CWinApp::Run, and it's partner in crime CWinApp::OnIdle, to create your own message loop. Here's how:
- Use ClassWizard (in Visual C++ 6) or the Properties window (in Visual C++.NET) to add overrides for the virtual functions Run and OnIdle to your program's CWinApp-derived class. Both will insert
the following code:
BOOL CSampleApp::OnIdle(LONG lCount) { // TODO: Add your specialized code here and/or call the base class return CWinApp::OnIdle(lCount); } int CSampleApp::Run() { // TODO: Add your specialized code here and/or call the base class return CWinApp::Run(); }
- Delete the contents of the new CSampleApp::Run function, and insert the following code, lifted straight from CWinThread::Run:
ASSERT_VALID(this); // for tracking the idle time state BOOL bIdle = TRUE; LONG lIdleCount = 0; // acquire and dispatch messages until a WM_QUIT message is received. for (;;) { // phase1: check to see if we can do idle work while (bIdle && !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)) { // call OnIdle while in bIdle state if (!OnIdle(lIdleCount++)) bIdle = FALSE; // assume "no idle" state } // phase2: pump messages while available do { // pump message, but quit on WM_QUIT if (!PumpMessage()) return ExitInstance(); // reset "no idle" state after pumping "normal" message if (IsIdleMessage(&m_msgCur)) { bIdle = TRUE; lIdleCount = 0; } } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)); } ASSERT(FALSE); // not reachable
- Finally, add another function to your CWinApp-derived class, named Update or something similar. Place a call to this function in CWinApp::OnIdle, and another after the "phase2" do...while loop in CWinApp::Run. Any code that needs to run continuously should be put in or called from Update. It's now trivial to create a game loop, or run any other continuously updating routine, for repainting views.
There are a couple of drawbacks to this technique. The first is that it's intrusive to MFC, but because the overridden CWinApp::Run function uses basically the same code with a few additions, the impact is minimal.
The second is that without modifications, any application that uses this technique will perform full processing all of the time (just as a normal message loop does). While this is probably what you're aiming to achieve if you're creating your own message loop, it might be undesirable when the application does not have focus. The most obvious way to remedy this is to call GetForegroundWindow through your main window, and check if the CWnd pointer it returns matches that of the main window. If it doesn't, no updating needs to occur. However, a better way is to set up your main window to receive OnActivate messages, and add a Boolean variable to your main window class. When the OnActivate function is called by MFC, set the Boolean variable to true or false, depending on whether the window has been activated or deactivated. That variable can then be used in CWinApp::Run to determine if update code should be run.
An alternative to this technique, which may be a better long-term solution for a program, is to create a separate thread at the start of your application, and use that to run any continuously updating code.
References:
- Microsoft Foundation Classes source, Microsoft Corp.
http://msdn.microsoft.com/visualc - Bill Wagner, "3D Shape Creation, Part 2"
http://fawcette.com/archives/listissue.asp?pubID=3&MagIssueId=248# - Joe Houston, "Creating 3D Tools With MFC"
http://www.gamedev.net/reference/programming/features/3dmfc/