Click here to Skip to main content
Click here to Skip to main content

GLUT Window Template

, 31 Jan 2008 CPOL
Rate this:
Please Sign up or sign in to vote.
This article describes in detail how to set up your first OpenGL GLUT window and provides you with a template for your OpenGL applications
Screenshot - GLUT_Window_Template.jpg

Note: This article has been reposted at the author's request after the original was deleted at the author's request.

Contents

Introduction

This program sets up a platform-independent OpenGL window using the GLUT library. It can be used in the following ways:

  • Learn GLUT and OpenGL through:
    • Interactive Program
    • Documentation
    • Source Code
  • Start your first OpenGL program
  • Use the code as a template for your OpenGL applications

Make sure you read the “Running the Program” section of the documentation before you download the program and run it.

What is OpenGL?

OpenGL is the most widely used application programming interface (API) for developing portable 2D and 3D graphics applications. It was originally developed in 1992 by Silicon Graphics and is currently controlled and maintained by the OpenGL ARB (Architecture Review Board), which has become a part of the Khronos Group in 2006. ARB representatives include SGI, Microsoft, Apple, nVidia, ATI, Intel, id Software (famous for creating DOOM and QUAKE), and 3D Labs.

OpenGL has become an industry standard and a very popular API for its ease of use, portability, stability and rich documentation. According to OpenGL.org, OpenGL is the only truly open, vendor-neutral, multiplatform graphics standard. OpenGL 2.1 is the latest version of OpenGL and was released on August 2, 2006. OpenGL 3.0 is currently under development and will run specifically on hardware born after November 8th, 2006.

Why use GLUT?

OpenGL is a window system independent graphics library. This means that it doesn't handle window system operations that are specific to the Operating System. The reason for this is to make OpenGL portable on even new platforms. Creating a rendering window and handling events is left to the native window system to define. The OpenGL Utility Toolkit (GLUT) is intended to fill this gap, and thus provides developers with a window system independent API for OpenGL programs. GLUT supports the following functionality:

  • Window management
  • Event handling
  • Right click menu
  • Rendering fonts
  • Rendering various solid and wireframe objects
  • Reading from sophisticated input devices

It is important to note that GLUT is not a fully-featured windowing toolkit. It is mostly used for learning OpenGL and developing simple OpenGL programs. GLUT is simple, easy and small. The GLUT library has C, C++, FORTRAN and Ada programming bindings. It is portable to nearly all OpenGL implementations for the X Window System and Windows. The latest stable GLUT version is 3.6; the 3.7 version is currently in Beta phase.

Why Have a GLUT Window Template?

Knowing that OpenGL is an API intended for graphical applications, it is very clear that a window is always required to render the graphical objects. To avoid having to write the same code every time you want to create a graphical application using OpenGL, this program code can be used as a template to get you directly started with what matters to you in the program. The OpenGL GLUT window template has the following properties:

  • Title: "GLUT Window Template"
  • Background Color: black (R = 0, G = 0, B = 0)
  • Dimensions: width = 240, height = 240
  • Position: x = (Screen Width - Window Width) / 2, y = (Screen Height - Window Height) / 2. This would mean that the window is centered on the screen
  • Handling of keyboard, mouse and display events
  • Showing when events occur, including their meaning, through the command prompt

Usage

Running the Program

In order to run the program, three dynamic link libraries (DLLs) are required: opengl32.dll, glu32.dll and glut32.dll. The opengl32.dll and glu32.dll files already come with your Windows OS and are found in the system folder. To run the executable, you have to download the glut32.dll and copy it into your system folder. You can find all of the DLL files in the attached ZIP file, GLUT_Window_Template_dll.zip under Visual C++\dll.

Compiling the Code with Microsoft Visual C++

In order to write a C OpenGL application with GLUT using Microsoft Visual Studio on a Windows platform, you need the following files:

  • C header files: GL.h, GLU.h and GLUT.h
  • C LIB files: glui32.lib, opengl32.lib and glut32.lib

You can find all of the header and LIB files in the attached ZIP file GLUT_Window_Template_dll.zip under Visual C++\include\GL and Visual C++\lib, respectively.

Microsoft Visual C++ 6

To use the code under a Visual C++ 6.0 environment, perform the following steps:

  • Open Microsoft Visual C++ 6.0
  • Create a new Win32 Console Application Project

    Screenshot - GLUT_Window_Template_1.jpg
  • Copy the source file GLUT_Window_Template.c to your project folder. This is GLUT_Window_Template_src in our case. You may want to rename the file to fit your program's needs.
  • Add the source file to the project using the menu option Project\Add To Project\Files:

    Screenshot - GLUT_Window_Template_2.jpg
  • Make sure that the header files are in the include folder, LIB files are in the lib folder and DLLs are in the system folder:

    Files Description Source folder (attached) Target folder
    GL.H, GLU.H, GLUT.H Header Files Visual C++/include/GL C:\Program Files\Microsoft Visual Studio\VC98\Include\GL
    OPENGL32.LIB, GLU32.LIB, GLUT32.LIB Lib Files Visual C++/lib C:\Program Files\Microsoft Visual Studio\VC98\Lib
    OPENGL32.DLL, GLU32.DLL, GLUT32.DLL DLL Files Visual C++/dll C:\WINDOWS\system32
  • Link the code to the libraries:

    Screenshot - GLUT_Window_Template_3.jpg

    To avoid having to set the link settings in every Visual C++ 6.0 project you create, you may want to include the following code segment in your code, which would basically do the same thing as above.

    // Link the lib files to the program. This is not necessary
    // if you have added the lib names using Project/Settings/Link
    #pragma comment (lib, "opengl32.lib")
    #pragma comment (lib, "glut32.lib")
    #pragma comment (lib, "glu32.lib")
  • To avoid getting the console window whenever you want to run your OpenGL window, you may want to include this directive in your code:

    #pragma comment(
        linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")

Using the Code

The source code is intended to be used as a template for your OpenGL applications. To use it in your new application, you can simply rename the C file and add it to your Visual Studio project.

Explaining the Code

The header files for GLUT should be included in GLUT programs with the following include directive:

  #include <span class="code-keyword"><GL/glut.h>  </span>

There is no need to include <GL/gl.h> or <GL/glu.h> since <GL/glut.h> already includes them.

Initialization

Functions starting with glutInit are used to initialize the GLUT state. The glutInit function is the primary initializing routine and must only be called once in a GLUT program. OpenGL or GLUT functions that are not starting with glutInit are not allowed to be called before the primary glutInit function. However, other functions starting with glutInit can be called before the primary glutInit function. The reason for this is to allow for specifying the window initialization state in command-line arguments and before the session is negotiated with the window system.

Below I show each initialization function prototype, its description and how I used it in my program.

Connecting to the Window System

void glutInit(int *argcp, char **argv);

glutInit initializes the GLUT library and negotiates a session with the window system.

glutInit(&argc, argv);

Setting Window Size

void glutInitWindowSize(int width, int height);  

Use glutInitWindowSize to set the initial size of the window that will be created. The value for the width and height parameters must be greater than zero. In case no value is specified, i.e. function not called, the default window size is 300 by 300. Note that the window system is not obligated with this information. Thus, GLUT programs must depend on the glutReshapeFunc to determine the true size of the window.

//  variables representing the window size
int window_width = 240;
int window_height = 240;

//  Set the window size
glutInitWindowSize (window_width, window_height); 

Setting Window Position

void glutInitWindowPosition(int x, int y);  

Use the glutInitWindowPosition to set the initial position of a window. The default value is -1 by -1, which tells the window system to determine the appropriate position.

//  define the window position on screen
int window_x;
int window_y;

//-------------------------------------------------------------------------
//  This function sets the window x and y coordinates
//  such that the window becomes centered
//-------------------------------------------------------------------------
void centerOnScreen (void)
{
    window_x = (glutGet (GLUT_SCREEN_WIDTH) - window_width)/2;
    window_y = (glutGet (GLUT_SCREEN_HEIGHT) - window_height)/2;
}

//  Set the window position
glutInitWindowPosition (window_x, window_y);

In the code above, I am simply making the window centered on the screen. glutGet (GLUT_SCREEN_WIDTH) gets the width of the screen in pixels and glutGet (GLUT_SCREEN_HEIGHT) gets its height.

Setting Display Mode

void glutInitDisplayMode(unsigned int mode);  

The display mode specifies what buffers are to be used in order to render the OpenGL graphics. One may choose more than one mode by using the logical OR operator. The following are the possible modes that can be selected; they have been copied from the GLUT API Version 3 Reference Manual:

Mode Description
GLUT_INDEX Bit mask to select a color index mode window. This overrides GLUT_RGBA if it is also specified.
GLUT_SINGLE Bit mask to select a single buffered window. This is the default if neither GLUT_DOUBLE nor GLUT_SINGLE are specified.
GLUT_DOUBLE Bit mask to select a double buffered window. This overrides GLUT_SINGLE if it is also specified.
GLUT_ACCUM Bit mask to select a window with an accumulation buffer.
GLUT_ALPHA Bit mask to select a window with an alpha component to the color buffer(s).
GLUT_DEPTH Bit mask to select a window with a depth buffer.
GLUT_STENCIL Bit mask to select a window with a stencil buffer.
GLUT_MULTISAMPLE Bit mask to select a window with multisampling support. If multisampling is not available, a non-multisampling window will automatically be chosen. Note: both the OpenGL client-side and server-side implementations must support the GLX_SAMPLE_SGIS extension for multisampling to be available.
GLUT_STEREO Bit mask to select a stereo window.
GLUT_LUMINANCE Bit mask to select a window with a "luminance'' color model. This model provides the functionality of OpenGL's RGBA color model, but the green and blue components are not maintained in the frame buffer. Instead, each pixel's red component is converted to an index between zero and glutGet(GLUT_WINDOW_COLORMAP_SIZE)-1 and looked up in a per-window color map to determine the color of pixels within the window. The initial colormap of GLUT_LUMINANCE windows is initialized to be a linear gray ramp, but can be modified with GLUT's colormap routines.


glutInitDisplayMode (GLUT_RGBA | GLUT_DOUBLE);

To display graphical content on a window, we need to tell GLUT whether to use single buffering or double buffering. In case single buffering is to be used, we need to set the mode to GLUT_SINGLE (by default). To display OpenGL contents on the window, we must call the glFlush OpenGL function which copies the contents of the frame buffer to the window.

However, GLUT_DOUBLE is usually the better option, as it allows for faster graphics rendering without flickering due to the use of two frame buffers that are continuously swapped. To swap those buffers, we call the GLUT function glutSwapBuffers. Note that GLUT_DOUBLE uses more memory than GLUT_SINGLE due to the use of 2 frame buffers rather than 1.

Create Window

int glutCreateWindow(char *name);  

Creates the GLUT window with name as its title. The value returned is a unique small integer identifier for the window. Rendering to a created window is ineffective before the glutMainLoop function is called because the window cannot yet be displayed.

//  variable representing the window title
char *window_title = "GLUT Window Template";

//  Create Window
glutCreateWindow (window_title);

After creating the window, you may want to display it in full screen.

//  Tells whether to display the window full screen or not
//  Press Alt + Esc to exit a full screen.
int full_screen = 0;

//  View in full screen if the fullscreen flag is on
if (full_screen)
    glutFullScreen ();

Set Program Initial State

The purpose here is to set some OpenGL properties and initialize any data structures you have in your program. In our case, we need to set the frame buffer clear color to black. This would mean that whenever we want to refresh our graphics, we need to first clear the old drawing with a black canvas and then redraw our new contents. Otherwise, our graphics won't display in the correct manner.

//-------------------------------------------------------------------------
//  Set OpenGL program initial state.
//-------------------------------------------------------------------------
void init ()
{
    //  Set the frame buffer clear color to be black.
    glClearColor (0.0, 0.0, 0.0, 0.0);
} 

The arguments to glClearColor are R (red), G (green), B (blue) and A (alpha). Alpha is used for transparency. The values range between 0.0 and 1.0. For example, in case you want to set the clear color to gray (128, 128, 128), you need to divide each component by 255 in order to get the right parameters for glClearColor.

Set Callback Functions

// Set the callback functions
glutDisplayFunc (display);
glutReshapeFunc  (reshape);
glutMouseFunc (mouse);
glutMotionFunc (motion);
glutPassiveMotionFunc (pmotion);
glutKeyboardFunc (keyboard);
glutSpecialFunc (special);

These are the callback functions that are mostly used. To see the remaining callback functions that GLUT supports, please refer to the GLUT API Version 3 Reference Manual.

glutDisplayFunc

//-------------------------------------------------------------------------
//  This function is passed to glutDisplayFunc in order to display
//    OpenGL contents on the window.
//-------------------------------------------------------------------------
void display (void)
{
    //  Clear the window or more specifically the frame buffer...
    //  This happens by replacing all the contents of the frame
    //  buffer by the clear color (black in our case)
    glClear (GL_COLOR_BUFFER_BIT);

    //  Draw scene
    drawObject ();

    //  Swap contents of backward and forward frame buffers
    glutSwapBuffers ();
}

glClear (GL_COLOR_BUFFER_BIT) will clear the frame buffer so that things can be re-drawn clearly. glutSwapBuffer places the contents of the back buffer into the from buffer visible on the window.

The drawObject function simply displays an icosahedron and tells us that the display function is currently being called. When GLUT determines that the window needs to be redisplayed, the display callback for the window is called. The display callback function in GLUT can be either set explicitly by calling glutPostRedisplay or implicitly as the result of window damage reported by the window system. Multiple posted redisplays for a window are coalesced by GLUT to minimize the number of display callbacks called.

//-------------------------------------------------------------------------
//  Draws our object.
//-------------------------------------------------------------------------
void drawObject ()
{
    //  Show when are displaying an object
    printf ("Displaying object...\n");

    //  Draw Icosahedron
    glutWireIcosahedron ();
}

You will notice the "Displaying object..." message on the command prompt in the following cases:

  • glutPostRedisplay is explicitly called in the code
  • After the window is resized
  • When the window loses focus and then gets it back

If you notice this happening in other cases, please let me know.

glutReshapeFunc

//-------------------------------------------------------------------------
//  This function is passed to the glutReshapeFunc and is called
//  whenever the window is resized.
//-------------------------------------------------------------------------
void reshape (int w, int h)
{
    //  Stay updated with the window width and height
    window_width = w;
    window_height = h;

    //  Reset viewport
    glViewport(0, 0, window_width, window_height);

    //  Print current width and height on the screen
    printf ("Window Width: %d, Window Height: %d.\n",
        window_width, window_height);
}

The reshape function will be called every time a window is resized and when the window is first displayed on the screen. The w and h parameters of the callback specify the new window size in pixels.

I am using the glViewport function here to reset the drawing area to be equal to the window size whenever the window is resized. If you try commenting out the glViewport function, you will notice that the drawing remains the same no matter how you resize the window. However, calling the glViewport function will enlarge and shrink the drawing area based on the changes in the window size.

glutMouseFunc

When a user presses and releases a mouse button, two events are generated: one for the press and the other for the release. The state parameter is either GLUT_UP or GLUT_DOWN, indicating whether the callback was due to a release or press, respectively. The button parameter is one of GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON or GLUT_RIGHT_BUTTON and indicates which button was clicked. The x and y parameters indicate the mouse coordinates relative to the GLUT window when the mouse button's state changed. If a menu is attached to a button for a window, mouse callbacks will not be generated for that button. Passing NULL to glutMouseFunc disables the generation of mouse callbacks.

//-------------------------------------------------------------------------
//  This function is passed to the glutMouseFunc and is called
//  whenever the mouse is clicked.
//-------------------------------------------------------------------------
void mouse (int button, int state, int x, int y)
{
    switch (button)
    {
        //  Left Button Clicked
        case GLUT_LEFT_BUTTON:

            switch (state)
            {
                //  Pressed
                case GLUT_DOWN:
                    printf ("Mouse Left Button Pressed (Down)...\n");
                    break;
                //  Released
                case GLUT_UP:
                    printf ("Mouse Left Button Released (Up)...\n");
                    break;
            }

            break;

        //  Middle Button clicked
        case GLUT_MIDDLE_BUTTON:

            switch (state)
            {
                //  Pressed
                case GLUT_DOWN:
                    printf ("Mouse Middle Button Pressed (Down)...\n");
                    break;
                //  Released
                case GLUT_UP:
                    printf ("Mouse Middle Button Released (Up)...\n");
                    break;
            }

            break;

        //  Right Button Clicked
        case GLUT_RIGHT_BUTTON:

            switch (state)
            {
                //  Pressed
                case GLUT_DOWN:
                    printf ("Mouse Right Button Pressed (Down)...\n");
                    break;
                //  Released
                case GLUT_UP:
                    printf ("Mouse Right Button Released (Up)...\n");
                    break;
            }

            break;
    }
}

In order to check if the Shift, Ctrl or Alt key was pressed while the mouse event occurred, we can use the glutGetModifiers function. This returns the modifier key state at the time the input event for a keyboard, special or mouse callback is generated. The modifiers are GLUT_ACTIVE_SHIFT (Shift or Caps Lock), GLUT_ACTIVE_CTRL (Ctrl) and GLUT_ACTIVE_ALT (Alt).

glutMotionFunc

As long as the user is moving the mouse while a button is pressed (i.e. dragging), this event will be called continuously.

//-------------------------------------------------------------------------
//  This function is passed to the glutMotionFunc and is called
//  whenever the mouse is dragged.
//-------------------------------------------------------------------------
void motion (int x, int y)
{
    //  Print the mouse drag position
    printf ("Mouse Drag Position: %d, %d.\n", x, y);
}

glutPassiveMotionFunc

As long as the user is moving the mouse while no button is pressed, this event will be called continuously.

//-------------------------------------------------------------------------
//  This function is passed to the glutPassiveMotionFunc and is called
//  whenever the mouse is moved.
//-------------------------------------------------------------------------
void pmotion (int x, int y)
{
    //  Print mouse move position
    printf ("Mouse Move Position: %d, %d.\n", x, y);
}

glutKeyboardFunc

Each key press generating an ASCII character will generate a keyboard callback. The key callback parameter is the generated ASCII character. The x and y callback parameters indicate the mouse location in window-relative coordinates when the key was pressed. Passing NULL to glutKeyboardFunc disables the generation of keyboard callbacks.

//-------------------------------------------------------------------------
//  This function is passed to the glutKeyboardFunc and is called
//  whenever the user hits a key.
//-------------------------------------------------------------------------
void keyboard (unsigned char key, int x, int y)
{
    //  Print what key the user is hitting
    printf ("User is hitting the '%c' key.\n", key);
    printf ("ASCII code is %d.\n", key);

    switch (key)
    {
        //  User hits A key
        case 'a':

            break;

        //  User hits Shift + A key
        case 'A':

            break;

        //  User hits Enter
        case 13:
            printf ("User is hitting the Return key.\n");
            break;

        //  User hits Space
        case 32:
            printf ("User is hitting the Space key.\n");
            break;

        //  User hits back space
        case 8:
            printf ("User is hitting the Back Space key.\n");
            break;

        //  User hits ESC key
        case 27:
            exit (1);
            break;
    }

    glutPostRedisplay ();
}

In order to check if the Shift, Ctrl or Alt key was pressed while the ASCII key was pressed, we can use the glutGetModifiers function. Also, modifiers might have an effect on the ASCII character itself. For example, if Caps Lock is off and we press Shift + A Key, the value of the key parameter will be set to A. If only the A key is pressed, then the key parameter is set to a.

glutSpecialFunc

//-------------------------------------------------------------------------
//  This function is passed to the glutSpecialFunc and is called
//  whenever the user hits a special key.
//-------------------------------------------------------------------------
void special (int key, int x, int y)
{
    switch (key)
    {
        case GLUT_KEY_F1 :
            printf ("F1 function key.\n");
            break;
        case GLUT_KEY_F2 :
            printf ("F2 function key. \n");
            break;
        case GLUT_KEY_F3 :
            printf ("F3 function key. \n");
            break;
        case GLUT_KEY_F4 :
            printf ("F4 function key. \n");
            break;
        case GLUT_KEY_F5 :
            printf ("F5 function key. \n");
            break;
        case GLUT_KEY_F6 :
            printf ("F6 function key. \n");
            break;
        case GLUT_KEY_F7 :
            printf ("F7 function key. \n");
            break;
        case GLUT_KEY_F8 :
            printf ("F8 function key. \n");
            break;
        case GLUT_KEY_F9 :
            printf ("F9 function key. \n");
            break;
        case GLUT_KEY_F10 :
            printf ("F10 function key. \n");
            break;
        case GLUT_KEY_F11 :
            printf ("F11 function key. \n");
            break;
        case GLUT_KEY_F12 :
            printf ("F12 function key. \n");
            break;
        case GLUT_KEY_LEFT :
            printf ("Left directional key. \n");
            break;
        case GLUT_KEY_UP :
            printf ("Up directional key. \n");
            break;
        case GLUT_KEY_RIGHT :
            printf ("Right directional key. \n");
            break;
        case GLUT_KEY_DOWN :
            printf ("Down directional key. \n");
            break;
        case GLUT_KEY_PAGE_UP :
            printf ("Page up directional key. \n");
            break;
        case GLUT_KEY_PAGE_DOWN :
            printf ("Page down directional key. \n");
            break;
        case GLUT_KEY_HOME :
            printf ("Home directional key. \n");
            break;
        case GLUT_KEY_END :
            printf ("End directional key. \n");
            break;
        case GLUT_KEY_INSERT :
            printf ("Inset directional key. \n");
            break;
    }

    glutPostRedisplay ();
}

Each key press generating a non-ASCII character will generate a special callback. The x and y callback parameters indicate the mouse location in window relative coordinates when the key was pressed. Passing NULL to glutSpecialFunc disables the generation of special callbacks. Here is the list of special keys as specified in the GLUT API Version 3 Reference Manual:

Key Description
GLUT_KEY_F1 F1 function key.
GLUT_KEY_F2 F2 function key.
GLUT_KEY_F3 F3 function key.
GLUT_KEY_F4 F4 function key.
GLUT_KEY_F5 F5 function key.
GLUT_KEY_F6 F6 function key.
GLUT_KEY_F7 F7 function key.
GLUT_KEY_F8 F8 function key.
GLUT_KEY_F9 F9 function key.
GLUT_KEY_F10 F10 function key.
GLUT_KEY_F11 F11 function key.
GLUT_KEY_F12 F12 function key.
GLUT_KEY_LEFT Left directional key.
GLUT_KEY_UP Up directional key.
GLUT_KEY_RIGHT Right directional key.
GLUT_KEY_DOWN Down directional key.
GLUT_KEY_PAGE_UP Page up directional key.
GLUT_KEY_PAGE_DOWN Page down directional key.
GLUT_KEY_HOME Home directional key.
GLUT_KEY_END End directional key.
GLUT_KEY_INSERT Insert directional key.

Begin Event Processing

void glutMainLoop(void);

After a GLUT program has completed initial setup, such as creating windows and menus, GLUT programs enter the GLUT event processing loop by calling glutMainLoop. This function should be called at most once in an OpenGL program.

Conclusion

I think this article can help significantly in getting you started with OpenGL. At the same time, the template can be used to save lots of copy and paste from old projects or the Internet. In case you find this template useful or have suggestions, please let me know.

References

Revision History

26/07/2007

  • Article reposted at author's request

mid-July, 2007

  • Article deleted at author's request

02/07/2007

  • Added References section

06/06/2007

21/05/2005:

  • Original article posted

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Ali BaderEddin
Software Developer Microsoft
United States United States
http://mycodelog.com/about/

Comments and Discussions

 
QuestionImage Pinmemberanubis1120-Sep-13 15:11 
GeneralMaximized window PinmemberM.Siyamalan25-Mar-10 19:05 
GeneralRe: Maximized window PinmemberAli BaderEddin26-Mar-10 8:58 
GeneralRe: Maximized window PinmemberM.Siyamalan26-Mar-10 9:05 
GeneralA small bug in the template.. PinmemberPeter Gregson8-Nov-09 2:25 
GeneralRe: A small bug in the template.. PinmemberAli BaderEddin9-Nov-09 15:06 
Generalset callback functions Pinmemberibneuron16-Aug-09 21:42 
Generalclear the image and exit Pinmembershaniaamir12-Apr-09 1:04 
GeneralDraw Image Pinmemberdanielku1522-Mar-09 1:30 
GeneralRe: Draw Image PinmemberAli BaderEddin22-Mar-09 14:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.141015.1 | Last Updated 31 Jan 2008
Article Copyright 2007 by Ali BaderEddin
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid