The source and files are in Visual Studio 2013 format.
Introduction
In this second article, we will expand the simple OpenGL application from Part 1 into a standard MDI interface application. Here, each MDICHILD
window will contain the fuctionality of the single application from part 1.
Background
In part one, we had a single render context which belonged to the only application window. What we will now do is move much of the functionality away from the application window and onto the MDICHILD
window. So we will have multiple render contexts operating at once and the organization we undertook in lesson 1 becomes the key. In later lessons, we will add threads and the drawing complexity will go up another level.
The key part to understand this lesson is understanding the MDI Application and behaviour. MDI Applications are based around an invisible or transparent Window
class that exists in the area that is drawn in a normal application window. This special Window
class is called an MDIClient
and although invisible, it is involved in controlling the behaviours of the MDIChildren
windows that are inserted into it.
The MDIClient
is responsible for all those MDI special things like the minimize and maximize behaviours, tiling and cascading and a wealth of other unique features. They are worth reading some articles on if you want to use this sort of application. For our MDI application, we will make our OpenGL windows and insert them into the MDIClient
and allow the standard behaviours to work.
Using the Code
Our pseudo code from lesson 1 remains unchanged except it is moved from the application window and onto the MDIChild
window and we add a cleanup step when closing. Thus, each MDIChild
runs the sequence:
1.) Initialize OpenGL for window (Called Once)
2.) Scale the OpenGL viewPort (Initial Call)
repeat
3.) Draw the scene
4.) Transfer the scene to screen
until window closes
5.) Window closing cleanup OpenGL memory and stuff used
** Note Item 2) the scale process also gets called if the window changes size
We use the same structure to hold our data as per Lesson 1, only this time each MDIChild
creates a structure and it is attached to each MDI child. The initialization of step 1 returns a render context specific to the MDIChild
and thus each MDIChild
has its own render context.
The MDIChild
handler becomes the site of all the OpenGL calls. The create of the MDI child will cause creation of a new render context which will be stored in its own database structure on the window itself. Each MDIChild
will therefore have its own data structure and messages which create operations on the windows becomes unique to the data structure attached to each MDIChild
. So each MDI child can be doing different things without having to track any complexity for the OpenGL system itself.
What seemed like a slightly complex data holding arrangement in lesson 1 makes data management obvious and simple in our MDI. There are other faster ways to attach data to Windows but they are more complex than SetProp
/GetProp
but this is the easiest for our target audience of beginners. Later, as we move into games and faster render situations, we will discuss other methods to deal with this.
For this application, the MDIChild
shown below is where all the OpenGL is called and controlled from and it is worth looking at the process and equating it back up to the pseudocode above.
static LRESULT CALLBACK OpenGLMDIChildHandler (HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg){
case WM_CREATE: { GLDATABASE* db = (GLDATABASE*) malloc(sizeof(GLDATABASE)); db->Rc = InitGL(Wnd); db->glTexture = 0; db->xrot = 0.0f; db->yrot = 0.0f; SetProp (Wnd, DATABASE_PROPERTY, (HANDLE) db); ReSizeGLScene (Wnd); }
break;
case WM_DESTROY: { wglMakeCurrent(NULL, NULL); GLDATABASE* db = (GLDATABASE*) GetProp(Wnd, DATABASE_PROPERTY); if (db != 0) {
if (db->Rc != 0) wglDeleteContext(db->Rc); if (db->glTexture != 0)
glDeleteTextures(1, &db->glTexture); free(db); }
}
break;
case WM_PAINT: { PAINTSTRUCT Ps;
GLDATABASE* db = (GLDATABASE*) GetProp(Wnd, DATABASE_PROPERTY); BeginPaint (Wnd, &Ps); DrawGLScene(db, Ps.hdc); SwapBuffers(Ps.hdc); EndPaint(Wnd, &Ps); return 0;
}
break;
case WM_TIMER: { GLDATABASE* db = (GLDATABASE*) GetProp(Wnd, DATABASE_PROPERTY); db->xrot += 1.0f; db->yrot += 1.0f; InvalidateRect(Wnd, 0, TRUE); }
break;
case WM_WINDOWPOSCHANGED: if ((lParam == 0) || ((((PWINDOWPOS) lParam)->flags & SWP_NOSIZE) == 0)){
ReSizeGLScene(Wnd); InvalidateRect(Wnd, 0, TRUE); }
break;
case WM_ERASEBKGND: return (FALSE);
}
return DefMDIChildProc(Wnd, Msg, wParam, lParam); }
History
- Version 1.00 - Initial release
- Version 1.10 - Small bug changing wrong textures on MDIChild fixed, MDI DragDrop functionality added.