{steinsoft.net}
 
 
 
 
 
Home/News
Code Snippets
Board
Projects
What's That?
I am using it actively
I planning to use/learn it
I don't plan to lean/use it
5th option
Leipzig
Home :: Programming :: Tutorials :: Setting up OpenGL with Mfc

[ Setting up OpenGL with Mfc ]

Welcome to the first SteinSOFT.net-tutorial. I will explain here how to set up OpenGL in a Mfc using application with the ability to switch between fullscreen and windowed mode. This is relatively easy because I will use the class wrappers SScreenmode and OpenGLDevice from the Code-section, which make this much more easier. So let's begin at the beginning.

First you have to create a new project with the Mfc application wizard. This is the easiest way. You could also program the framework yourself, but this would take you more time. So let the wizard generate a new SDI-application and disable most of the things you don't need(e.g. toolbars, info dialogs, load/save support etc..).

IMPORTANT: You have to disable the Document/View Architecture in the first dialog. We could also use this architecture and draw our OpenGL scene using the CView Mfc-class, but we would only have problems to switch into fullscreen mode.

So now the first step is done. The next thing to do is to eliminate all the unneceassary things we don't need. You should see 4 classes in the Class inspector (in MSDev), CChildView and CMainFrame and CMyApp (or how you called your project). You should now remove the classes CChildView and CMainFrame as we don't need them and we will create a new class instead of them. You can remove the classes by deleting the source and header files in the MSDev's File inspector (on the left side).

Okay now our project is only a simple Application without a main window. So let's create it! Add a new class named CMainWindow to the project that is derived from CWnd. So you could ask why we removed CMainFrame from your project and now we create a new class. That's illogical. No, it's very simple. CMainFrame is derived from CFrameWnd and I also first tried to use this class for my OpenGL applications but always when I went into fullscreen mode there was an ugly border at every side. But when I used CWnd, everything went good. It seems to be a problem in the implementaion of the class. If someone knows the problem, mail me! I am interested why it is so;).

Now we have our main window. The next step is to create an OpenGL rendering context and to set the screenmode. You should now download the two simple class wrappers SScreenmode and OpenGLDevice from the Code-section. You will see they make your life much easier;). Simply copy them to \Include in your MSVC++ directory, so you won't have to copy them in your projects dir.

In the header file of the CMainWindow add the following:

//The header files
//OpenGL
 
#include <GL/gl.h>
#include <GL/glu.h>
 
//Our helper classes
#include <SScreenmode.h> 
//or #include "SScreenmode.h" if file is in the same folder
#include <OpenGLDevice.h> //same as above

Now we have to define an instance of SScreenmode and OpenGLDevice and an instance of CClientDC, the Drawing Context of your window. We also define a variable fullscreen that keeps track whether fullscreen is on or off:

class CMainWindow: public CWnd
{
//.... 
//.... 
protected:
   CClientDC* clientDC;
   SScreenMode screenMode;
   OpenGLDevice openGLDevice; 
   bool fullscreen;
   //....
   //.... 

Now comes the funny part. We will add some code to CMainWindow that MAKES something ;-). First we will initialize the constructor and we will cleanup everything in the destructor:

CMainWindow::CMainWindow()
{
   //Everything has now a standard value
   clientDC = NULL;
   //Standard is to go fullscreen
   fullscreen = true;
}
 
CMainWindow::~CMainWindow()
{
   //Only delete clientDC from memory when
   //it has been created (otherwise it is NULL)
   //It will be created in OnCreate
   if (clientDC)
   {
      delete clientDC;
   }
} 

The next thing to do is to handle all window messages that are important for us at the moment: WM_CREATE, WM_SIZE, WM_CLOSE and WM_SYSCOMMAND. We will now add a message handler for all these window messages (to do this right click on CMainWindow in the class browser and click then in the popup menu on Add message handler for windows messages or something like this):

//...
//...
//Our WM_SIZE message handler
void CMainWindow::OnSize(UINT nType, int cx, int cy) 
{
   CWnd::OnSize(nType, cx, cy);
  
   //Height must not be null
   if (cy == 0) 
   {
      cy = 1; 
   }
 
   //Standard OpenGL Viewport initialization
   glViewport(0, 0, cx, cy); 
 
   glMatrixMode(GL_PROJECTION); 
   glLoadIdentity(); 
 
   gluPerspective(45.0f,(GLfloat)cx/(GLfloat)cy,0.1f,100.0f);
 
   glMatrixMode(GL_MODELVIEW); 
   glLoadIdentity();
}

This was very easy. We only set up the OpenGL viewport. This is nothing amazing, it's like in every other OpenGL application. Next step is to set up our OpenGL device in the OnCreate(..) function:

int CMainWindow::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
   if (CWnd::OnCreate(lpCreateStruct) == -1)
      return -1;
   
   //Create our Device Context
   clientDC = new CClientDC(this);
   
   //Create our OpenGL Rendering Context
   openGLDevice.create(clientDC->m_hDC);
   
   //Function initializes OpenGL and other OGL tasks
   InitGL();
 
   return 0;
}

As you can see, the creating of our rendering context is done in only one line of code. Nothing more. First we create the Device Context of our window(clientDC). CClientDC is a class that makes all operations with the Device Context much more easier than in the Windows API. We will create then the OGL device with create(..) of the OpenGLDevice-class. OpenGL is then inited with InitGL(). Here is the code that destroys and cleans up everything, in the message handler of WM_CLOSE:

void CMainWindow::OnClose() 
{
   //Reset our Screenresolution
   screenMode.reset();
 
   //Tell the application to quit
   PostQuitMessage(0);
 
   //Makes Cursor visible(is hidden when the window is created)
   ShowCursor(true);  
   CWnd::OnClose();
}

First we reset the screen resolution with reset() of the SScreenmode class. If we are in windowed mode the function will do nothing. It checks automatically if the screen resolution changed before. Then we will tell the application to quit with PostQuitMessage(0) and the mouse cursor will be made visible with ShowCursor(..).

The last message we want to handle is WM_SYSCOMMAND. This message is called when the screensaver or monitor power wants to start. We don't want to be "interrupted" by them so we will "chase" them away :

void CMainWindow::OnSysCommand(UINT nID, LPARAM lParam) 
{
   //If Screensaver or monitor power wants to start...
   if (nID == SC_SCREENSAVE || 
      nID == SC_MONITORPOWER)
   {
      //Return => The System commands won't be executed
      return;
   } 
    
   CWnd::OnSysCommand(nID, lParam);
}

Our framework is now nearly ready to be used. The window has only to be created. This is done in CreateGLWindow(). Add this function to CMainWindow. It must be a public function as it will be called from the application class. Here it is:

void CMainWindow::CreateGLWindow()
{
   //Sets the display to 640x480 with 16 color bits
   screenMode.setDisplayMode(640,480,16);
 
   //Registers our Windows class
   CString className = AfxRegisterWndClass(
     CS_HREDRAW | CS_VREDRAW | CS_OWNDC, //Own Device Context
     NULL,
     (HBRUSH)GetStockObject(BLACK_BRUSH),  
     AfxGetApp()->LoadIcon(IDR_MAINFRAME));
 
   //Finally Create our Window
   CreateEx(
     0,
     className, //Registered class name
     "OpenGL", //Title of our Window(can be set to everything you want)
     WS_POPUP, //No borders
     CRect(0,0,640,480), //Should fill the whole screen
     NULL,
     0);
 
   //Show Window and make active
   ShowWindow(SW_SHOW);
   SetForegroundWindow();
   SetFocus();
 
   //No cursor visible
   ShowCursor(false);
}

This is finally the function that creates our window. It automatically goes to fullscreen with a resolution of 640x480. The rest is trivial. It registers the window class and creates our window. The window has the style WS_POPUP as we don't want to see borders in fullscreen mode. The next thing to do is to initialize OpenGL. For this we will add a private function InitGL(). It will be called when the window has been created:

//OpenGL initialization code
void CMainWindow::InitGL()
{
   //Standard OpenGL setup
   glShadeModel(GL_SMOOTH);
   glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
   glClearDepth(1.0f); 
   glEnable(GL_DEPTH_TEST); 
   glDepthFunc(GL_LEQUAL); 
   glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
}

This wasn't very difficult. We only set some render properties. It is like in every other OpenGL app. We have now nearly finished our first step. We will add now the drawing function. It's the private function DrawGLScene():

//OpenGL drawing code
void CMainWindow::DrawGLScene()
{
   //************** 
   //Draw Something
   //**************
 
   //Standard OpenGL, you have to know this;)
   //without comments... ;)
   glClear(GL_COLOR_BUFFER_BIT |
           GL_DEPTH_BUFFER_BIT);
 
   glLoadIdentity();
   glTranslatef(0.0f,0.0f,-5.0f);
 
   //Triangle
   glBegin(GL_TRIANGLES);
      glColor3f(1.0f,0.0f,0.0f); glVertex3f(1.0f,-1.0f,0.0f);
      glColor3f(0.0f,1.0f,0.0f); glVertex3f(0.0f,1.0f,0.0f);
      glColor3f(0.0f,0.0f,1.0f); glVertex3f(-1.0f,-1.0f,0.0f);
   glEnd();
   
   //Done!!
   // .. but don't forget that one..
   SwapBuffer(clientDC->m_hDC);
}   

So this is a simple drawing function. Nothing special. The last step we have to do in the first "round" is to add a function that is called in the message loop. It's the public function Update():

void CMainWindow::Update()
{
   //Test is Escape is pressed
   if (GetAsyncKeyState(VK_ESCAPE))
   {
      PostMessage(WM_CLOSE);
   }
 
   //Draw the scene
   DrawGLScene();
}

This function will be called within the Application's message loop as we'll see soon. The function GetAsynKeyState(..) checks if the key specified by the virtual key code is pressed. It returns TRUE if yes and FALSE if not. If Escape is pressed a WM_QUIT will be sent and the application knows that it should exit.

So this was the first round! The code that changes between fullscreen and windowed will be added later. First let's see if everything works as it should...

We don't have to do very much in the COpenGLTestApp class (I called the project OpenGLTest so it is possible that your application class has a different name). First we'll add a CMainWindow instance to the application class. Open it by double-clicking on COpenTestApp in the classbrowser:

...
//Don't forget this;)
#include "MainWindow.h"
 
class COpenGLTestApp : public CWinApp
{
public:
   COpenGLTestApp(); 
 
   //... OTHER THINGS ....
 
private:
   CMainWindow* mainWindow;
};
 ...

This is nothing special. First we include the headerfile of CMainWindow (very important) and then we add a private variable mainWindow. This is our OpenGL-window.

Next thing to do is to clean up the code a little bit in COpenGLTestApp.cpp. First you can completely delete the CAboutDlg class in the file. We absolutely dont need that. You also can delete the code within InitInstance(). It will be replaced by the following:

COpenGLTestApp::COpenGLMfcApp()
{ 
   //Initialize the pointer with NULL
   mainWindow = NULL;
}
 
BOOL COpenGLTestApp::InitInstance()
{
   //The instance will be created
   mainWindow = new CMainWindow();
   //A member variable of CWinApp; should be set
   //whatever the mainwindow is
   m_pMainWnd = mainWindow;
 
   //..and the window itself
   mainWindow->CreateGLWindow();
   return TRUE;
} 

We first initialize mainWindow with NULL in the constructor. It is always better to do that with ALL your class members so that mysterious errors dont occur very often (but your are never sure .. why? Windows! ;-) ). Next we setup the window. It is not very difficult here; we did everything in CMainWindow itself.

Next thing to do is to add two virtual functions to COpenGLTestApp. These are functions declared in the base-class CWinApp but we will override them and write our own functionality. So right click on COpenGLTestApp in the class-window and click on "Add virtual function" or something like this. A window should appear where you can select functions you want to override. Select Run and ExitInstance in the list.

MSDev should now add the two functions to the class a nd we'll write the following code into them:

int COpenGLTestApp::ExitInstance() 
{
   //Only delete mainWindow from Heap if really allocated
   if (mainWindow)
   {
      delete mainWindow;
   }
   
   //Let the base code do the real work;)
   return CWinApp::ExitInstance();
}
 
int COpenGLTestApp::Run() 
{
   //The mainloop
   for (;;)
   {
      //Get the next Message in the Windows messsage loop
      if (PeekMessage(&m_msgCur , NULL , 0, 0, PM_NOREMOVE))
      {
         //CWinApp::PumpMessage() handles the messages for us
         if (!PumpMessage())
         {
            //If error: quit app 
            return ExitInstance();
         }
      }
      else
      {
         //When messages have been handled -> Update our Window
         mainWindow->Update();
      }
   }
}

Not Much to add to the two functions. COpenGLTestApp::ExitInstance() is called when the application quits. So we check here if the CMainWindow instance has been allocated correctly (it should be not NULL) and delete it then from the Heap. The main functionality is done in the base code, so we call ExitInstance() of CWinApp.

Run() is the core function of our application. It's the application's mainloop and first checks if there are messages in the message loop. If there are some, they are handled by CWinApp::PumpMessage() that does all this for us. If not, CMainWindow::Update() will be called and the triangle will be drawn and all the other stuff we do in CMainWindow.

So now if you compile and start the app you should have the basic framework for your OpenGL applications. At the moment it only supports fullscreen. If you don't want an application that supports switching between fullscreen and windowed mode, you can stop here. Otherwise let's read the rest ;-)

The second Step: Switching between Fullscreen and Windowed mode

Okay we will implement now Fullscreen/Windowed mode switching. If the user hits F9 the application will switch to fullscreen mode if we are in windowed mode and vice versa. We have to add some functions and to modify CreateGLWindow() a little bit. It's not very much so lets get to work ;-)

First we will modify CMainWindow::CreateGLWindow(). The sections which are changed are marked:

void CMainWindow::CreateGLWindow()
{
   //Sets the display to 640x480 with 16 color bits
   // CHANGED: checks whether fullscreen is true or not
   if (fullscreen)
   {
      screenMode.setDisplayMode(640,480,16);
   } 
 
   //Registers our Windows class
   CString className = AfxRegisterWndClass(
     CS_HREDRAW | CS_VREDRAW | CS_OWNDC, //Own Device Context
     NULL,
     (HBRUSH)GetStockObject(BLACK_BRUSH),  
     AfxGetApp()->LoadIcon(IDR_MAINFRAME));
  
   //Finally Create our Window
   CreateEx(
     0,
     className, //Registered class name
     "OpenGL", //Title of our Window(can be set to everything you want)
     // CHANGED: Sets window style according to the value of fullscreen
     (fullscreen ? WS_POPUP : WS_POPUP|WS_CAPTION), 
     // CHANGED: Sets window size depending on fullscreen
     (fullscreen ? CRect(0,0,640,480) : CRect(50,50,640,480)), 
     NULL,
     0);
  
   //Show Window and make active
   ShowWindow(SW_SHOW);
   SetForegroundWindow();
   SetFocus();
 
   //No cursor visible
   ShowCursor(false);
}

So now the window creation code is more flexible. First it is tested whether fullscreen is true. If yes the screen resolution is set otherwise nothing will be done. The next two modifications are done in the creation of the window. Depending on the value of fullscreen, the window style will be set and the size of it too. If we are in fullscreen mode, the window has no border (styleflag WS_POPUP) and if are in windowed mode it has a border and a title (WS_POPUP AND WS_CAPTION).

The both tests use a special C instruction. Its a short form of if/else. If you don't know it, here is the syntax:

( statement ? if_it_is_true : if_it_is_false)

If statement is true, the instruction if_it_is_true will be executed otherwise if_it_is_false. This was only a little "trip" into non-OpenGL worlds, so lets return quickly;).

Two new functions have to be added to CMainWindow for our purposes. First we will add a function that does all the switching between the two modes for us. It will set the displaysettings if necessary and it will change the window-styles so that e.g. the fullscreen window has no border. The next function will be called when the window loses focus. I programmed the window that it minimizes to the traybar whenever another window or application gets the focus. But this is up to you. I only show you who to do that.

We will now add the next 2 functions to CMainWindow (both protected):

void CMainWindow::UpdateView()
{
   //If we go fullscreen
   if (fullscreen)
   {
      //Set Displaymode...
      screenMode.setDisplayMode(640,480,16);
     
      //..and remove the style WS_CAPTION from the window
      // => only WS_POPUP is enabled = no border
      ModifyStyle(WS_CAPTION,0);
      //Set Size of window
      MoveWindow(CRect(0,0,640,480));
   }
   else
   { 
      //Reset Screenmode
      screenMode.reset();
 
      //Add WS_CAPTION style to window (as above) 
      ModifyStyle(0,WS_CAPTION);
      MoveWindow(CRect(50,50,640,480));
   }
}

This function is called when F9 is pressed. First it will be checked if we intend to go into fullscreen or windowed mode. If we go into fullscreen mode, the displaymode is set and the window style is changed with the function CWnd::ModifyStyle(DWORD dwRemove,DWORD dwAdd) where dwRemove removes and dwAdd adds the specified style from/to the window style. This is a a lazy way to do switching. Using this method, you don't have to recreate the window and the OpenGL rendering context every time you switch the mode. The only disadvantage might be that this is not compatible with older graphic cards... But I didn't have any problems yet. So lets have a look a the second function we'll add to our window class:

void CMainWindow::Minimize()
{
   //If window already minimized...
   if (IsIconic())
   {
      return;
   }
   
   //Get the infos that tell us who the
   //window is positioned, minimzed etc.
   WINDOWPLACEMENT placement;
   GetWindowPlacement(&placement);
   
   //If we are in fullscreen mode
   if (fullscreen)
   {
      //Reset display settings
      screenMode.reset();
   }
   
   //Tell the window that it should be minimzed
   placement.showCmd = SW_SHOWMINIMIZED;
 
   //Save the window's placement/display options
   SetWindowPlacement(&placement);
}

Okay.. this function is called when the window loses the focus. First we check if the window isn't already minimized with CWnd::IsIconic() and if yes(return value true) we simply leave the funtion. Next we get the infos of the window's placement(position, size etc.). Then we check if we are in fullscreen mode and we reset the display mode if neccessary. The instruction placment.showCmd = SW_SHOWMINIMIZED tells Windows that the window should be only in the traybar, iconic. The new window placment will be saved and applied with SetWindowPlacement(..). If you want to know more about these functions I would suggest you to take look at the good MSDN library.

We're nearly at the finish line. Only 3 functions to go. These are 3 message handlers that finally use the functions we implemented before. So we'll add the handlers for the following messages to CMainWindow: WM_KEYDOWM, WM_SETFOCUS and WM_KILLFOCUS. Lets see what we have to do in the WM_KEYDOWN message handler:

void CMainWindow::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
   //Test which key is pressed
   switch (nChar)
   {
   case VK_F9:
      fullscreen = !fullscreen;
      UpdateView();
      break;
   case VK_F7:
      Minimize();
      break;
   }
 
   CWnd::OnKeyDown(nChar, nRepCnt, nFlags);
}

Not much to add to this function. When F9 is pressed fullscreen will be inverted and UpdateView() will be called. If fullscreen was true it is now false and vice versa. You can also manually minimze the window with F7. The next message, WM_SETFOCUS:

void CMainWindow::OnSetFocus(CWnd* pOldWnd) 
{
   //Call the base class' function
   CWnd::OnSetFocus(pOldWnd);
 
   //Update window depending on the value of fullscreen
   UpdateView();
}

This function does not very much. It only calls UpdateView() that will change the display settings if necessary. Finally ... the very last message handler for today ;-) :

void CMainWindow::OnKillFocus(CWnd* pNewWnd) 
{
   //=> Base class
   CWnd::OnKillFocus(pNewWnd);
 
   //Minimze window
   Minimize();
}

The code is also very easy. It calls the function Minimze() that minimzes our window to the Windows traybar.

Yes, finally we're done... If you compile this project you'll have a working Mfc/OpenGL framework for all your OpenGL games and demos. So I hope you did understand everything and if not, simply mail me and tell me what I didn't explain very well. I am also interested in your feedback!

Downloads:
OpenGL/Mfc basecode

 Last edited 2006-07-10 16:12:00 by André Stein - printable version
» copyright by andré stein
» using stCM v1.0
» steinsoft.net revision 5.0