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:
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:
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:
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):
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:
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:
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 :
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:
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:
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():
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():
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:
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:
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:
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:
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:
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):
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:
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:
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:
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 ;-) :
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!