OpenGL Programming/GLStart/Tut1

From Wikibooks, open books for an open world
< OpenGL Programming
Jump to: navigation, search

Tutorial 1: Creating a Windows program in Dev – C++[edit]

This chapter will cover programming with the Win32 API.

Setting up Dev – C++[edit]

First open up Dev – C++. When it opens up, click on the top menu bar File, New, Project. Make sure the Basic tab is selected and click on Windows Application. Then give it a name and press OK. Then in the next dialog, choose a location on your computer to save and then press Save:

GLstart tut1 1.jpg

After you do that, your project loads up and you get a default source file with windows code already in it. Right – click the tab main.cpp above the source file and click Close. When it asks you to save changes, click on No.

To add a new source file to the project, go to the top menu bar and click on File, New, Source File. It will ask you if you want to add the source file to the project. Click Yes. Now you will have a blank source file to edit. Before we start coding, click on File, Save. It will ask you where you want to save your source file. Make sure you save it in the same place you put your Dev – C++ project. Name it something like ”winmain” and click Save. Now that we got a blank source file ready, we can start coding our window.

The first line of the source file should have the windows header “windows.h” included:

#include <windows.h>

This header includes all the functions and structures we need so far to program in Windows.

Before we go on, there are some things we need to discuss about Window events.

Unlike a standard command – line program, a Windows program has many events happening at once. One example would be the fact that a Window has to constantly repaint the background of itself. This we won’t worry about right now. But there are other events such as a user pressing a button on the window, a user right – clicking with the mouse, etc… The event that we are going to worry about in this lesson is when the user hits the X button on the top – right of the window to exit the program. We have to tell the Operating System (Windows) that when the user does that, the program should quit. The function in Windows that handles these types of events is called a Window Procedure. We are going to define this function next. Also note that Windows doesn’t call these actions “events”, but rather they call them messages. Messages are events. We will be using the term “messages” throughout the lessons. Now let’s start coding the Window Procedure.

The return value of the Window Procedure is the result of the processing of the messages which is returned to the WinMain() function (more on this later):

LRESULT CALLBACK WindowProc(

    HWND hwnd,  // handle of window
    UINT uMsg,  // message identifier
    WPARAM wParam,      // first message parameter
    LPARAM lParam       // second message parameter
   );

I will explain these parameters in a minute. But this is how you declare the Window Procedure. So right underneath the inclusion of the Windows header, put this in:

LRESULT CALLBACK WinProc(HWND hWnd,
                         UINT msg,
                         WPARAM wParam,
                         LPARAM lParam)
{

Notice that you can name the function whatever you want. Here we named it “WinProc”. Easy to remember right? We need this name when we create the actual window. Now let me explain the different parameters there:

HWND hWnd is short for a “handle to a window”. Basically this is the actual window object itself. This handle stores the window in it.

UINT msg is the actual message we will be processing. There are tons and tons of messages we can process, but we will only worry about one in this lesson.

WPARAM wParam and LPARAM lParam are different types of message information. We won’t be using these variables in this lesson. I will explain them some other time.

Now within the Message Procedure we need some way of defining what to do for a certain message. We will use the switch command to sort out these messages:

     switch(msg)
     {

Now we will put in a case statement for the one message we want to handle, which is when the user exits the program. This message has an ID value of WM_DESTROY. When we are in its case statement, we put in a function to quit the program. We will use the PostQuitMessage() function which takes as a single parameter the exit code, which is 0:

          case WM_DESTROY:
               PostQuitMessage(0);
               break;
          
          default: break;
     }

Above we put the quitting message and then ended the switch statement. Now that we took care of that message, we are done with the Window Procedure. One last thing we have to do though is return the results of our message processing to the WinMain function. For this we return the DefWindowProc() function:

LRESULT DefWindowProc(

    HWND hWnd,  // handle to window
    UINT Msg,   // message identifier
    WPARAM wParam,      // first message parameter
    LPARAM lParam       // second message parameter
   );

As you can see this function takes the same parameters as the window procedure itself. So all you have to do is fill it in with the same parameters you did in the Window Procedure declaration:

return DefWindowProc(hWnd,msg,wParam,lParam);
}

Now this finally completes our Window Procedure. Now lets move on to the WinMain() function.


WinMain

The WinMain() function is sort of like the main() function in a regular C or C++ program. It is the entry point for all Windows programs. But it is a bit more complex to set up. Here is its prototype:

int WINAPI WinMain(

    HINSTANCE hInstance,        // handle to current instance
    HINSTANCE hPrevInstance,    // handle to previous instance
    LPSTR lpCmdLine,    // pointer to command line
    int nCmdShow        // show state of window
   );

Let me explain the parameters:

HINSTANCE hInstance keeps track of the window. Let me explain this better. It keeps track of the instance, or the appearance, of the window. This is a very hard concept to explain. We will go over this some more later.

HINSTANCE hPrevInstance is not really used.

LPSTR lpCmdLine specifies command – line arguments for the program. We won’t need to worry about this.

int nCmdShow is the way that the window is to be shown. We won’t mess with this yet.

After the message procedure type in the initialization for the WinMain() function:

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nShowCmd)
{

Now within the WinMain() function we have to declare three very important variables. Put this in right after the WinMain() initialization:

     HWND hWnd;
     WNDCLASSEX wcex;
     MSG msg;

Let me explain these variables:

HWND hWnd we already explained in the message procedure. It is the handle that stores the window itself.

WNDCLASSEX wcex is the actual window structure. This structure, which you define, sets certain properties of the window. We will be filling in this structure in a minute.

MSG msg is the message structure like the one in the message procedure. This particular structure contains fields such as the actual message itself. We will be using this later in the WinMain() function.

Now that we got those variables, lets define that WNDCLASSEX structure. Here is the prototype of the WNDCLASSEX structure:

typedef struct _WNDCLASSEX {    // wc  
    UINT    cbSize; 
    UINT    style; 
    WNDPROC lpfnWndProc; 
    int     cbClsExtra; 
    int     cbWndExtra; 
    HANDLE  hInstance; 
    HICON   hIcon; 
    HCURSOR hCursor; 
    HBRUSH  hbrBackground; 
    LPCTSTR lpszMenuName; 
    LPCTSTR lpszClassName; 
    HICON   hIconSm; 
} WNDCLASSEX;

Let me show you what to fill in these properties with:

- UINT cbSize is the size of the structure itself. - UINT style is the window styles. There are quite a few options for this. We will use the CS_HREDRAW and CS_VREDRAW to allow the window to be resized horizontally and vertically. - WNDPROC lpfnWndProc is the name of the message procedure you want to link to. We already created this in the beginning so put the name of it (WinProc) in here. - int cbClsExtra and int cbWndExtra will not be used so it will be set to 0 for both of them. - HANDLE hInstance is the instance handle we created in the declaration of WinMain. Just put in the name we gave it (hInstance) in this property. - HICON hIcon is the big icon we want to use for the program. For this we will load a standard icon using the LoadIcon() function which takes as parameters: the HINSTANCE of the icon (we set this to NULL) and the Icon name (set to IDI_APPLICATION for a standard windows icon) - HCURSOR hCursor is the mouse cursor we want to use. For this we use the LoadCursor() function which takes as parameters: HINSTANCE of Cursor (set to NULL) and the name of the Cursor ( IDC_ARROW for a standard Windows arrow) - HBRUSH hbrBackground is the color we want for the background. We get this color by using the GetStockObject() function which takes as parameter the name of the brush (we set this to GRAY_BRUSH) and type cast it to HBRUSH type. - LPCTSTR lpszMenuName identifies the menu we want to use in our program. Since we don’t have one, we set this to NULL. - LPCTSTR lpszClassName is a string for the name of the Windows class that identifies the window. We will need this name later when we create the window. Set it to “WinClass”. - HICON hIconSm is the small icon to use for the program. Set this to NULL for a default Icon.

So here is the whole structure defined and ready to be typed:

     wcex.cbSize = sizeof(WNDCLASSEX);
     wcex.style = CS_HREDRAW | CS_VREDRAW;
     wcex.lpfnWndProc = WinProc;
     wcex.cbClsExtra = 0;
     wcex.cbWndExtra = 0;
     wcex.hInstance = hInstance;
     wcex.hIcon = LoadIcon(NULL,IDI_APPLICATION);
     wcex.hCursor = LoadCursor(NULL,IDC_ARROW);
     wcex.hbrBackground = (HBRUSH) GetStockObject(GRAY_BRUSH);
     wcex.lpszMenuName = NULL;
     wcex.lpszClassName = "WinClass";
     wcex.hIconSm = NULL;

After the defining of the WNDCLASSEX structure, we have to register it with the RegisterClassEx() function, which takes as a single parameter a pointer to the WNDCLASSEX structure we want to register:

     RegisterClassEx(&wcex);

Now we go on to actually create the window. Remember that HWND (handle to window) that we created at the beginning of the WinMain() function? Well, we are going to set that equal to the window we are about to create using the CreateWindow() function:

HWND CreateWindow(

    LPCTSTR lpClassName,        // pointer to registered class name
    LPCTSTR lpWindowName,       // pointer to window name
    DWORD dwStyle,      // window style
    int x,      // horizontal position of window
    int y,      // vertical position of window
    int nWidth, // window width
    int nHeight,        // window height
    HWND hWndParent,    // handle to parent or owner window
    HMENU hMenu,        // handle to menu or child-window identifier
    HANDLE hInstance,   // handle to application instance
    LPVOID lpParam      // pointer to window-creation data
   );

Let me explain these parameters:

- LPCTSTR lpClassName is the name of the window class we defined in the definition of the WNDCLASSEX structure (“WinClass”) - LPCTSTR lpWindowName is the string that appears on the title bar of the window. For now, set it to “Window” - DWORD dwStyle is specifies certain window styles. There are a bunch of these, but for now we will set it to WS_OVERLAPPEDWINDOW for the window to appear with a border and a minimize,maximize, and close button just like a standard windows application. - int x and int y are the X and Y locations starting from the top – left corner of the screen of the window. For now we set it to 0 and 0. - int nWidth and int nHeight are the height and width in pixels of the window. For now set it to 400 and 400. - HWND hWndParent is a window handle to a parent window. Since we are only creating a single window, set it to NULL to indicate there is no parent window. - HMENU hMenu identifies a menu to use. Since we have none, set it to NULL. - HANDLE hInstance is the instance handle that we set to the WNDCLASSEX structure (hInstance). - LPVOID lpParam is not needed, so set it to NULL.

Here is the whole function with the right parameters passed:

     hWnd = CreateWindow("WinClass","My Window",
            WS_OVERLAPPEDWINDOW,0,0,400,400,NULL,NULL,
            hInstance,NULL);

Just to be on the safe side, lets make sure that the window was created successfully. If the CreateWindow() function was successful, the window handle (hWnd) would have a window attached to it. If CreateWindow() wasn’t successful, then the window handle would have a value of NULL in it. So first, check for a value of NULL in the window handle with an IF statement:

     if(hWnd == NULL)
     {

Now if the window handle was NULL, we need to tell the user about the error and close the program down. First to inform the user of the problem, we can use a popup message box. For that we use the MessageBox() function:

int MessageBox(

    HWND hWnd,  // handle of owner window
    LPCTSTR lpText,     // address of text in message box
    LPCTSTR lpCaption,  // address of title of message box  
    UINT uType  // style of message box
   );

The first parameter is the window handle of the window the message box is coming from. Since if the window handle is NULL, which we are checking for, put in NULL for this value. The second parameter, lpText, is the main text in the message box dialog. For this put in “Error: Couldn’t create window”. The third parameter, lpCaption, is the title caption on the top of the message box. Set this to “ERROR”. The final parameter, uType, is the message box style. Here you can identify the types of buttons for the user to press on the message box and an icon to appear on the message box. For now, set this to MB_OK for just an OK button on there. After we create the message box, we have to quit the program. Just like a standard C++ main() function, when an error occurs we put ”return -1” instead of “return 0” to tell the Operating System we are quitting the program with an error. So after the message box put “return 0”:

             MessageBox(NULL,"Error: Unable to create Window","ERROR",MB_OK);
             return -1;
     }

Now, assuming the window was created successfully, we need to actually show it. We use the ShowWindow() function for that:

BOOL ShowWindow(

    HWND hWnd,  // handle of window
    int nCmdShow        // show state of window
   );

The first parameter is the handle to the window we want to show (hWnd). The second parameter is the variable we created at the initialization of the WinMain() function (nShowCmd):

     ShowWindow(hWnd,nShowCmd);

Right after this, we should handle any updates to the window by using the UpdateWindow() function which takes as a single parameter the handle of the window to update(hWnd):

     UpdateWindow(hWnd);

Now that our window is shown, we need to prevent the program from exiting so our window stays on the screen. To do that we create a main loop. The way I am going to do the loop is create a while loop with a condition of 1 so that the loop is infinite:

     while(1)
     {

Now while the loop is going, we need some way to check for if a user quit the program. To do that we have to check the messages being sent to see if one was a quit message. For that we use the PeekMessage() function:

BOOL PeekMessage(

    LPMSG lpMsg,        // pointer to structure for message
    HWND hWnd,  // handle to window
    UINT wMsgFilterMin, // first message
    UINT wMsgFilterMax, // last message
    UINT wRemoveMsg     // removal flags
   );

- LPMSG lpMsg is a pointer to the message structure that we created at the beginning of the WinMain() function (msg) - HWND hWnd is the window that we want the messages from processed. You can put NULL in here to select the default window - UINT wMsgFilterMin and UINT wMsgFilterMax specify the minimum and maximum ranges for the messages to be processed. Since we want to process all messages, put 0 in for both of these parameters - UINT wRemoveMsg determines how messages are handled after they are processed. We will use the value PM_REMOVE telling it to remove messages after they are processed.

Since the PeekMessage() function returns a Boolean value determining if it was successful or not, lets put it in an IF Statement to assure that it ran successfully:

             if(PeekMessage(&msg,NULL,0,0,PM_REMOVE))
             {

If a message was processed, we need to only check to see if that message was a Quit message. We can do that by checking the message property of the MSG structure we created earlier (msg). The message we are checking for is the WM_QUIT message. If it is, then we have to break out of the loop that the program is currently in:

                 if(msg.message == WM_QUIT) break;

If a quit message was not processed, we still have to have the other messages processed. For that we use the TranslateMessage() function to interpret the message. Then after that we use the DispatchMessage() function to execute the message. Both functions take as a single parameter a pointer to the MSG structure:

                 TranslateMessage(&msg);
                 DispatchMessage(&msg);
             }
     }

Those two curly brackets end our main loop. The last thing we do before we finish up the WinMain() function is return a value to the Operating System. We return 0 as the value since by this time the program has run successfully:

     return 0;

}

Now compile and run the program, and you should get this nice result:

GLstart tut1 2.jpg