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
Using Message Procedures
Using Menus

Example source code:
peek.c
msgproc.c
menu.c



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.