SIGGRAPH '97
Course 24: OpenGL and Window System Integration
OpenGL and Win32
Processing Messages & Using Menus
Win32 Messages and Menus allow for processing of user input. Methods
for intercepting and responding to messages as well as methods for
using menus is presented below.
Peeking at Messages
While the simple example presented in the last section got us started
with OpenGL, it was very limited in that it didn't provide for any
user input. Messages are the standard method to receive and
process user input in Win32. An easy way to check for messages is
presented below. This approach is very simple and limited. There are
more sophisticated methods for processing messages which will be
covered later in this document.
code defining the main() function in msgproc.c
/* main()
* Entry point
*/
int main(int argc, char** argv)
{
HDC hDC; /* device context */
HGLRC hRC; /* opengl context */
HWND hWnd; /* window */
MSG msg; /* message */
/* create a window */
hWnd = oglCreateWindow("OpenGL", 0, 0, 200, 200);
if (hWnd == NULL)
exit(1);
/* get the device context */
hDC = GetDC(hWnd);
/* set the pixel format */
if (oglSetPixelFormat(hDC, PFD_TYPE_RGBA, 0) == 0)
exit(1);
/* create an OpenGL context */
hRC = wglCreateContext(hDC);
wglMakeCurrent(hDC, hRC);
/* now we can start changing state & rendering */
while (1) {
/* first, check for (and process) messages in the queue */
while(PeekMessage(&msg, hWnd, 0, 0, PM_REMOVE)) {
switch(msg.message) {
case WM_LBUTTONDOWN:
printf("WM_LBUTTONDOWN: %d %d %s %s %s %s %s\n",
LOWORD(msg.lParam), HIWORD(msg.lParam),
msg.wParam & MK_CONTROL ? "MK_CONTROL" : "",
msg.wParam & MK_LBUTTON ? "MK_LBUTTON" : "",
msg.wParam & MK_RBUTTON ? "MK_RBUTTON" : "",
msg.wParam & MK_MBUTTON ? "MK_MBUTTON" : "",
msg.wParam & MK_SHIFT ? "MK_SHIFT" : "");
break;
case WM_MOUSEMOVE:
printf("WM_MOUSEMOVE: %d %d\n",
LOWORD(msg.lParam), HIWORD(msg.lParam));
break;
case WM_KEYDOWN:
printf("WM_KEYDOWN: %c\n", msg.wParam);
if(msg.wParam == 27) /* ESC */
goto quit;
break;
default:
DefWindowProc(hWnd, msg.message, msg.wParam, msg.lParam);
break;
}
}
/* rotate a triangle around */
glClear(GL_COLOR_BUFFER_BIT);
glRotatef(1.0, 0.0, 0.0, 1.0);
glBegin(GL_TRIANGLES);
glColor3f(1.0, 0.0, 0.0);
glVertex2i( 0, 1);
glColor3f(0.0, 1.0, 0.0);
glVertex2i(-1, -1);
glColor3f(0.0, 0.0, 1.0);
glVertex2i( 1, -1);
glEnd();
glFlush();
SwapBuffers(hDC); /* nop if singlebuffered */
}
quit:
/* clean up */
wglMakeCurrent(NULL, NULL); /* make our context 'un-'current */
ReleaseDC(hDC, hWnd); /* release handle to DC */
wglDeleteContext(hRC); /* delete the rendering context */
DestroyWindow(hWnd); /* destroy the window */
return 0;
}
There are many other messages that can be checked for and processed.
See the macros defined in the winuser.h include file for a
full listing, or look at Microsoft Developer Studio InfoViewer topics
beginning with WM_. The method presented above is limited in
that some messages must be "translated" before they can be processed.
The method presented next takes care of these messages as well.
Message Procedure
A much more effective way of processing messages is to use a window
procedure. Every window must have a window procedure associated
with it (actually, the window procedure is associated with the window
class, but since every window has a class, every window also has a
window procedure). The window procedure is called whenever there are
messages for the window in the message queue.
The code for a typical window procedure follows.
code defining the WindowProc() function in msgproc.c
/* WindowProc()
* Minimum Window Procedure
*/
LONG WINAPI WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LONG lRet = 1;
PAINTSTRUCT ps;
switch(uMsg) {
case WM_CREATE:
break;
case WM_DESTROY:
break;
case WM_PAINT:
BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
break;
case WM_LBUTTONDOWN:
printf("WM_LBUTTONDOWN: %d %d %s %s %s %s %s\n",
LOWORD(lParam), HIWORD(lParam),
wParam & MK_CONTROL ? "MK_CONTROL" : "",
wParam & MK_LBUTTON ? "MK_LBUTTON" : "",
wParam & MK_RBUTTON ? "MK_RBUTTON" : "",
wParam & MK_MBUTTON ? "MK_MBUTTON" : "",
wParam & MK_SHIFT ? "MK_SHIFT" : "");
break;
case WM_MOUSEMOVE:
printf("WM_MOUSEMOVE: %d %d\n", LOWORD(lParam), HIWORD(lParam));
break;
case WM_CHAR:
printf("WM_CHAR: %c\n", wParam);
if(wParam == 27) /* ESC */
PostQuitMessage(0);
break;
case WM_SIZE:
printf("WM_SIZE: %d %d\n", LOWORD(lParam), HIWORD(lParam));
glViewport(0, 0, LOWORD(lParam), HIWORD(lParam));
break;
case WM_CLOSE:
printf("WM_CLOSE\n");
PostQuitMessage(0);
break;
default:
lRet = DefWindowProc(hWnd, uMsg, wParam, lParam);
break;
}
return lRet;
}
Each case in the switch statement processes one type
of message. As mentioned above, there are many types of messages.
The ones presented in this code fragment are some of the more common
ones. Notice that the default action is to call a
DefWindowProc() function. This passes on any messages that
the user doesn't intercept to the system message processing function.
The translation and dispatch of messages must be done explicitly. The
following code illustrates a method of doing this.
code defining the main() function in msgproc.c
/* main()
* Entry point
*/
int main(int argc, char** argv)
{
HDC hDC; /* device context */
HGLRC hRC; /* opengl context */
HWND hWnd; /* window */
MSG msg; /* message */
/* create a window */
hWnd = oglCreateWindow("OpenGL", 0, 0, 200, 200);
if (hWnd == NULL)
exit(1);
/* get the device context */
hDC = GetDC(hWnd);
/* set the pixel format */
if (oglSetPixelFormat(hDC, PFD_TYPE_RGBA, 0) == 0)
exit(1);
/* get the device context */
hDC = GetDC(hWnd);
/* create an OpenGL context */
hRC = wglCreateContext(hDC);
wglMakeCurrent(hDC, hRC);
/* now we can start changing state & rendering */
while (1) {
/* first, check for (and process) messages in the queue */
while(PeekMessage(&msg, hWnd, 0, 0, PM_NOREMOVE)) {
if(GetMessage(&msg, hWnd, 0, 0)) {
TranslateMessage(&msg); /* translate virtual-key messages */
DispatchMessage(&msg); /* call the window proc */
} else {
goto quit;
}
}
/* rotate a triangle around */
glClear(GL_COLOR_BUFFER_BIT);
glRotatef(1.0, 0.0, 0.0, 1.0);
glBegin(GL_TRIANGLES);
glColor3f(1.0, 0.0, 0.0);
glVertex2i( 0, 1);
glColor3f(0.0, 1.0, 0.0);
glVertex2i(-1, -1);
glColor3f(0.0, 0.0, 1.0);
glVertex2i( 1, -1);
glEnd();
glFlush();
SwapBuffers(hDC); /* nop if singlebuffered */
}
quit:
/* clean up */
wglMakeCurrent(NULL, NULL); /* make our context 'un-'current */
ReleaseDC(hDC, hWnd); /* release handle to DC */
wglDeleteContext(hRC); /* delete the rendering context */
DestroyWindow(hWnd); /* destroy the window */
return 0;
}
The TranslateMessage() function breaks down virtual-key
messages into character messages. The DispatchMessage()
function dispatches a message to the window procedure, which means it
calls the window procedure with the correct arguments for the given
message.
Menus
Another common method for obtaining user input in Win32 is through
menus. Setting up and managing a menu is very simple.
The following example shows how to create a menu bar.
code defining the menubar() function in menu.c
/* globals */
HMENU hPopup = NULL; /* popup menu */
. . .
/* menubar()
* create a menubar for the window
*/
void menubar(HWND hWnd)
{
HMENU hFileMenu; /* file menu handle */
HMENU hDrawMenu; /* draw menu handle */
HMENU hMenu; /* menu bar handle */
MENUITEMINFO item; /* item info */
/* create the menus */
hMenu = CreateMenu();
hFileMenu = CreateMenu();
hDrawMenu = CreateMenu();
/* fill up the file menu */
item.cbSize = sizeof(MENUITEMINFO);
item.fMask = MIIM_ID | MIIM_TYPE | MIIM_SUBMENU;
item.fType = MFT_STRING;
item.hSubMenu = NULL;
item.wID = 'x';
item.dwTypeData = "E&xit";
item.cch = strlen("E&xit");
InsertMenuItem(hFileMenu, 0, FALSE, &item);
/* now do the draw menu */
item.wID = 'r';
item.dwTypeData = "&Rotate";
item.cch = strlen("&Rotate");
InsertMenuItem(hDrawMenu, 0, FALSE, &item);
item.wID = 's';
item.dwTypeData = "&Don't Rotate";
item.cch = strlen("&Don't Rotate");
InsertMenuItem(hDrawMenu, 1, FALSE, &item);
/* now do the main menu */
item.wID = 0;
item.dwTypeData = "&File";
item.cch = strlen("&File");
item.hSubMenu = hFileMenu;
InsertMenuItem(hMenu, 0, FALSE, &item);
item.wID = 0;
item.dwTypeData = "&Draw";
item.cch = strlen("&Draw");
item.hSubMenu = hDrawMenu;
InsertMenuItem(hMenu, 1, FALSE, &item);
/* attach the menu to the window */
SetMenu(hWnd, hMenu);
/* use the draw menu as a popup menu */
hPopup = hDrawMenu;
}
The above code creates all the menus needed in the program. It also
attaches the menus to the menubar at the top of the window just under
the title (caption) bar. An ampersand in a string used as a
dwTypeData causes an underscore beneath the following letter to be
printed, and uses that letter as the accelerator key.
All menus send a WM_COMMAND message to the window that they
are attached to. The low word of the wParam sent to the message
procedure indicates what item was selected from the menu.
The following code handles the actions attached to each menu. It
should be inserted into the window procedure of an application.
code defining the menubar() function in menu.c
/* globals */
BOOL Rotate = TRUE; /* rotating? */
. . .
/* WindowProc()
* Minimum Window Procedure
*/
LONG WINAPI WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
. . .
case WM_COMMAND:
printf("WM_COMMAND: %c\n", LOWORD(wParam));
switch(LOWORD(wParam)) {
case 's':
Rotate = FALSE;
break;
case 'r':
Rotate = TRUE;
break;
case 'x':
PostQuitMessage(0);
break;
}
break;
. . .
}
A popup menu is one that is attached to a certain mouse button. When
the button is pressed inside the window, the menu should "pop-up"
right below where the mouse was pressed. These type of menus take an
additional step to set up. Since they are triggered when a mouse
button is pressed, the corresponding message must be reacted to.
The following code explains how to react to mouse messages for popup
menus. It should be inserted in the window procedure of the
application.
/* globals */
HMENU hPopup = NULL; /* popup menu */
. . .
/* WindowProc()
* Minimum Window Procedure
*/
LONG WINAPI WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
POINT point;
. . .
case WM_RBUTTONDOWN:
point.x = LOWORD(lParam);
point.y = HIWORD(lParam);
ClientToScreen(hWnd, &point);
TrackPopupMenu(hPopup, TPM_LEFTALIGN, point.x, point.y,
0, hWnd, NULL);
break;
. . .
}
Note that the x and y location of the menu must be in screen
coordinates, not window coordinates. The conversion is facilitated by
the ClientToScreen() function.