Lesson 4 Startup Options

Up until now we have been using the default startup options for glut. In this lesson we will learn how to use our MFC dialog to set initial glut options.

You can continue with the lesson 1 project you've already created and modified in the subsequent lessons. For continuity and to allow you to download the lessons as separate projects I will be showing the new code in a new project called Lesson4. 

If you didn't do lesson 3 you can get it here.

Click on the resources tab and expand the project tree, and then expand the Dialog folder. double click on "IDD_LESSON4_DIALOG". You should now see this:

From your toolbox select Group Box.

Place a group box control on your form and change its caption to "Resolution"

Place four radio button controls on your form. Right click on the first radio button control and choose properties, if the properties sheet is not already open. Set the first radio button caption to "640 x 480" and set the id to "IDC_RADIO_640x480". 
Set the second one to "800 x 600" with the id "IDC_RADIO_800x600". 
Set the third to "1024 x 768" with id "IDC_RADIO_1024x768" 
For the last radio button set it to "Full Screen" with id "IDC_RADIO_FULLSCREEN"

When you double click on a control in the resource editor the IDE will automatically create a mouse click event listener and create a function for you to put code into. Double click on each of the controls to create a function for each. Here's what you should get:

The event message map.

The the event listener functions.

Next we need to add a function to MFCopenGL to set the screen resolution. Click on the class tab and expand the project. Right click on MFCopenGL, choose add and add function. Create a public member function named "setRes" with a void return and one parameter named "resType" of type int. We will need someplace to save the resolution settings so add two private member variables to the MFCopenGL class of type int called "sizeX" and "sizeY". Add a private member variable of type bool named "fullscreen".

Open the MFCopenGL default constructor and change the default values for sizeX and sizeY to 640 and 480 respectively. This will give our glut window a default size if we choose to not pick a radio button. The default constructor should look like this:

We need to move our glut window initialization code into the MFCopenGL class. Click on the class tab and expand the project, right click on MFCopenGL  and choose add add add function. Add a function named glutInit that takes no parameters and is a void return. expand the MFCopenGL  class and double click on glutInit. Add the following code:

//set out options to RGB, double buffered and depth
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);/
glutInitWindowSize (sizeX, sizeY);// set the initial glut window size
glutInitWindowPosition (100, 100);// Set the initial glut window position
glutCreateWindow("MFC Glut Lesson4");//give the glut window a title
if(fullscreen)//if true place glut in full screen mode
{
glutFullScreen();
}

Your glutInit function should look like this:

Remove the old glut window initialization code from CLesson4Dlg::OnBnClickedOk() and add gl.glutInit(); in its place. Your should have this now:

To make it easier to pass the correct parameters into the resType function we will create a enum with some constant names that are easy to recognize. In order to have these constants available to all our classes we will place them in the "stdafx.h" file. Click on the solutions tab, expand the project, expand the "header files" folder and double click on "stdafx.h". At the end of the file place:

#ifndef res
typedef
enum {RESOLUTION_640X480 = 1, RESOLUTION_800X600, RESOLUTION_1024X768, RESOLUTION_FULLSCREEN} res;
#endif

We use the "#ifndef" block to prevent multiple declarations of the enum "res". Your "stdafx.h" should look like this:

Lets add our resolution code to MFCopenGL. Click on the class tab, expand the project and expand the MFCopenGL class. Double click on setRes(int resType). Add the following code to the setRes function:

switch(resType) {
case RESOLUTION_640X480 :
sizeX = 640;
sizeY = 480;
fullscreen = false;
break;
case RESOLUTION_800X600 :
sizeX = 800;
sizeY = 600;
fullscreen = false;
break;
case RESOLUTION_1024X768 :
sizeX = 1024;
sizeY = 768;
fullscreen = false;
break;
case RESOLUTION_FULLSCREEN :
fullscreen = true;
break;
default :
MessageBox(NULL, "Invalid setRes value, use int 1-4", "Warning", MB_OK);
break;
}

What we are doing here is receiving an int and checking it against the enum constant list and looking for a match. When we find an int match we then set the sizeX and the sizeY. We will use these size variables to set the glut window size when it is launched. Your resType function should look like this:

We will now add the code which calls this function. Expand the CLesson4Dlg class and double click on the OnBnClickedRadio640x480() function. You will see the radio button event handling functions listed there. 

Add:
"gl.setRes(RESOLUTION_640X480); " to OnBnClickedRadio640x480()
"gl.setRes(RESOLUTION_800X600);" to OnBnClickedRadio800x600()
"gl.setRes(RESOLUTION_1024X768);" to OnBnClickedRadio1024x768()
"gl.setRes(RESOLUTION_FULLSCREEN);" to OnBnClickedRadioFullscreen()

Your radio button functions should look like this:

Now you can see why we used the enum. It makes the code more readable and there is no question about what you are asking the function to do.

Compile and run your program and you should see:

Note: Don't test the full screen button yet; it will work but you wont have any way to get back out of full screen mode and consequently no way to shut down the program except [ctrl] [alt] [del]. We will add code later to deal with switching out of full screen mode.

Select  the 800 x 600 radio button and hit play.

Now test 640 x 480:

Test 1024 x 768:

In order to allow us to exit from full screen mode we will need to add keyboard support to our program. Glut has two keyboard callbacks, glutKeyboardFunc and glutSpecialFunc  The glutKeyboardFunc function is for receiving regular alpha numeric keystrokes, and the glutSpecialFunc function is for capturing keystrokes from the special keys like the function keys, insert and end. We will use the escape key to tell the program to shut down. For that we will need glutKeyboardFunc. I know it seems counterintuitive to have the escape key lumped into the alphanumeric keys but that's the way the specifications for glut were written. The glutKeyboardFunc needs a function to hook to so lets add one to our MFCopenGL. Add void keyboard(unsigned char key, int x, int y) to MFCopenGL. The first parameter is the ANSI code for the key presses. The next two parameters correspond to the (X, Y) coordinate position of the mouse at the time when the key was pressed. We will not be using the X, Y information but the parameters still need to be there in order to match the requirements of the callback. Open up the keyboard function and add this code:

//hit escape to close program

if (key == 27)
{
exit(0); // escape key is ascii 27
}

Your keyboard function should look like this:

Now lets define the callback for glut. Add the following code to the top of the Lesson4Dlg.cpp after the resize callback definition:

void keyboard(unsigned char key, int x, int y)
{
gl.keyboard(key, x, y);
}

Your callback definition area should look like this now:

We still have to tell glut about the keyboard callback so in the OnBnClickedOk() function add "glutKeyboardFunc (keyboard);" after the resize callback settting. Your OnBnClickedOk()  should look like this now:

Compile and run your program. Choose the "Full Screen" button and hit play. Now hit the escape key and the program should shut down. In fact hitting the escape key in any of the resolutions will exit the program now. 

The next step is to show the correct default radio button pre-selected when the program launches.

Click on the class tab, expand the project and double click on the CLesson4Dlg class. After  public and just prior to afx_msg void OnBnClickedOk(); add:

CButton radio640x480;
CButton radio800x600;
CButton radio1024x768;
CButton radioFullScreen;

Your CLesson4Dlg class header should look like this:

In the class tab expand the CLesson4Dlg class and double click on DoDataExchange and add:

DDX_Control(pDX, IDC_RADIO_640x480, radio640x480);
DDX_Control(pDX, IDC_RADIO_800x600, radio800x600);
DDX_Control(pDX, IDC_RADIO_1024x768, radio1024x768);
DDX_Control(pDX, IDC_RADIO_FULLSCREEN, radioFullScreen);

At the end of the function. Your DoDataExchange should look like this:

This will add a member variable for each radio button and map it to the correct resource. We can now control the settings of the radio buttons via code.

Double click on OnInitDialog in the class tab. At the end of the function just prior to the return add:

radio640x480.SetCheck(true);
OnBnClickedRadio640x480();

This will set the radio button to show the black dot and then run the function associated with that radio button. Your OnInitDialog  should look like this:

Compile your program and you should see this:

Press play and you will see that the glut window starts in the pre-selected resolution. On your own try setting the pre-select to each of the other radio buttons in turn to check that you have the variable mapping done correctly.

Next lets add a group box to control the start up glut window position. From the toolbar select group box and then click on the dialog under the resolution group box. On the property sheet for the new group box change the caption to "Position". 

Add two radio buttons to the position group box and change their captions to "Upper Left" and "Center". Set the resource id of Upper Left to "IDC_RADIO_UPPER_LEFT" and the resource id of center to "IDC_RADIO_CENTER". 

Double click on each of these new controls in turn to order to map a function to the click event of these two new controls.

We need to add variables to our MFCopenGL class to hold the start up window position and create a function to set them. Click on the class tab and expand the project. Right click on the MFCopenGL class and choose add variable. Add three private member variables of type int named posX, posY. and posType. Right click on MFCopenGL class and choose add and add function. Add a public member function that returns void named setPos that takes one int parameter named type. In order to do calculations to find the center of the screen we will need to find and store the desktop dimensions. Right click on MFCopenGL and choose add and add variable. Create a private member variable of type RECT named desktopRec. In order to set this variable when we initialize glut we need to modify our glutInit member function. Expand the MFCopenGL class and double click one the glutInit(void) change the function to void MFCopenGL::glutInit(RECT aRec). Double click on the class name MFCopenGL to open the header file. Scroll down and change the glutInit function declaration to "void glutInit(RECT aRec);". You class header should look like this:

In order to make the code more readable we will create another enum. Click on the solution tab and expand the project. Double click on stdafx.h and add the following code to the end of the file.

#ifndef pos
typedef
enum {POSITION_UPPER_LEFT = 0, POSITION_CENTER} pos;
#endif

Your stdafx.h should look like this now:

We need to set the default construction now. Click on the class tab expand the project and expand MFCopenGL. Double click on the default constructor. Add the following code to the top of the contructor:

//initialized desktopRec to the minimum resolution size possible
desktopRec.left = 0;
desktopRec.top = 0;
desktopRec.right = 640;
desktopRec.bottom = 480;

Your constructor should look like this:

Next we modify the glutInit function to set the initial window position. Click on the class tab, expand the project and expand the MFCopenGL class. Double click on the glutInit member function. add the following code to the top of the glutInit function:

desktopRec = aRec;
switch
(posType) {
case
POSITION_UPPER_LEFT :
posX = 0;
posY = 0;
break
;
case
POSITION_CENTER :
posX = desktopRec.right/2 - sizeX/2;
posY = desktopRec.bottom/2 - sizeY/2;
break
;
default
:
MessageBox(NULL, "Invalid posType value, use int 0-1", "Warning", MB_OK);
break
;
}

Modify the glutInitWindowPosition to read "glutInitWindowPosition (posX, posY);".

Your glutInit function should look like this:

Now we need to call our new setPos function from our MFC dialog. Click on the resource tab, expand the project, and expand the dialogs folder. Double click on IDD_LESSON4_DIALOG. Double click on the Upper Left radio button in the resource editor. Add "gl.setPos(POSITION_UPPER_LEFT);" to OnBnClickedRadioUpperLeft(). Now add gl.setPos(POSITION_CENTER); to OnBnClickedRadioCenter().

Compile and run and you should see some odd behavior. When we use a group box all the radio button controls inside will toggle automatically when we click on one, but it also unchecks the other buttons in the other group box. When we have more than one group box on a dialog the dialog resource control can have ambiguous behavior in respect to this toggling.  In order to get consistent behavior we will change our radio buttons so that we toggle them manually from inside the code. First lets add some variables for our two new radio buttons and map resources to them. Click on the class tab, expand the project and double click on the CLesson4Dlg class. In the CLesson4Dlg header add 

CButton radioUpperLeft;
CButton radioCenter;

below our other CButton instances. 

On the class tab expand the CLesson4Dlg class and double click on DoDataExchange and add 

DDX_Control(pDX, IDC_RADIO_UPPER_LEFT, radioUpperLeft);
DDX_Control(pDX, IDC_RADIO_CENTER, radioCenter);

to the end of the function. Your DoDataExchange  should look like this:

Double click on your OnInitDialog and add 

radioUpperLeft.SetCheck(true);
OnBnClickedRadioUpperLeft();

just prior to the return. Your OnInitDialog should look like this:

Click on the resources tab, expand the project and expand the dialogs folder. Double click on IDD_LESSON4_DIALOG to open the resource editor. Highlight each of the radio buttons in turn and change their auto properties to false. This will turn off the auto toggle. While your in the properties sheets you can also set each of the radio buttons tab stop properties to true. Set the 640 x 480 radio button  and the Upper Left radio button group property to true. This will allow navigation through the controls via the tab key and arrow keys once in a group. For example:

We now need to put the toggle code in our radio button mapped functions. Click on the class tab expand the project expand the CLesson4Dlg class and double click on OnBnClickedRadio640x480. Modify you mapped functions to match the following code:


void CLesson4Dlg::OnBnClickedRadio640x480()
{
// TODO: Add your control notification handler code here
radio640x480.SetCheck(true);
radio800x600.SetCheck(false);
radio1024x768.SetCheck(false);
radioFullScreen.SetCheck(false);
gl.setRes(RESOLUTION_640X480);
}
void CLesson4Dlg::OnBnClickedRadio800x600()
{
// TODO: Add your control notification handler code here
radio640x480.SetCheck(false);
radio800x600.SetCheck(true);
radio1024x768.SetCheck(false);
radioFullScreen.SetCheck(false);
gl.setRes(RESOLUTION_800X600);
}
void CLesson4Dlg::OnBnClickedRadio1024x768()
{
// TODO: Add your control notification handler code here
radio640x480.SetCheck(false);
radio800x600.SetCheck(false);
radio1024x768.SetCheck(true);
radioFullScreen.SetCheck(false);
 

gl.setRes(RESOLUTION_1024X768);
}
void CLesson4Dlg::OnBnClickedRadioFullscreen()
{
// TODO: Add your control notification handler code here
radio640x480.SetCheck(false);
radio800x600.SetCheck(false);
radio1024x768.SetCheck(false);
radioFullScreen.SetCheck(true);
gl.setRes(RESOLUTION_FULLSCREEN);
}
void CLesson4Dlg::OnBnClickedRadioUpperLeft()
{
// TODO: Add your control notification handler code here
radioUpperLeft.SetCheck(true);
radioCenter.SetCheck(false);
gl.setPos(POSITION_UPPER_LEFT);
}
void CLesson4Dlg::OnBnClickedRadioCenter()
{
// TODO: Add your control notification handler code here
radioUpperLeft.SetCheck(false);
radioCenter.SetCheck(true);
gl.setPos(POSITION_CENTER);
}
 

Compile and run your program and you should be able to toggle the radio buttons inside of each group box independently of the other group box. Test out the various radio button options. You should be able to control the position and size of the glut window.

We have gone through adding two sets of startup option groups. See if you can add a third group to let the user pick whether the background color of the glut window will be black or white.

Try it on your own first and then check it against the code I have listed below.

Here is what you need.

1. A group box named "Background Color.

2. Two radio button controls named  "Black" and "White". Make their resource ids, IDC_RADIO_BLACK and IDC_RADIO_WHITE.

3. Set auto to false, tab stop to true and set "Black" group property to true.

4. Add two member variables of type CButton to MFCopenGL named radioBlack and radioWhite.

5. Map the radioBlack to IDC_RADIO_BLACK and map radioWhite to IDC_RADIO_WHITE in the DoDataExchange function.

6. Create a class called Color with 4 public member variables of type int named r, g, b, a.

7. Add a private member variable named backColor of type Color to MFCopenGL.

8. Add a public member function named setBackColor to MFCopenGL that takes one parameter of type int named color.

9. Add a enum named col to the stdafx.h with constant names COLOR_BLACK AND COLOR_WHITE.

10. Create a mapped event handling function for the to new radio buttons.

11. Call the setBackColor function from these mapped functions and pass in the correct constant for the color you want.

12. Add radio button toggling code to the mapped member functions.

12. In the setBackColor create a case statement that checks the color parameter against the constant list and sets the backColor accordingly.

13. Change the call to glClearColor in the display function so that it uses the values stored in backColor.

When you are done it should look like this:

The CButton declarations.

Mapping the variables.:

Mapping the functions.

The enum.

The Color header.

Color constructor.

The MFCopenGL header.


The setBackColor with the switch statement.

Calling it from the Dialog.

The modified display function.

We have covered a  lot of ground in this lesson. If you got lost in the middle don't worry it gets easier adding controls with practice.

I have left you some extra space in the Dialog so feel free to add your own controls.

You can download the source code for this lesson here.