在本文中,将探讨如何将一个简单的OpenGL应用程序扩展为一个标准的多文档界面(MDI)应用程序。每个MDI子窗口都将包含第一部分中单一应用程序的功能。
在第一部分中,有一个单一的渲染上下文,它属于唯一的应用程序窗口。现在,将把许多功能从应用程序窗口转移到MDI子窗口。因此,将同时运行多个渲染上下文,在第一课中所做的组织将成为关键。在后续课程中,将添加线程,绘图复杂性将进一步提高。
理解本课程的关键部分是理解MDI应用程序及其行为。MDI应用程序基于一个不可见或透明的窗口类,该类存在于正常应用程序窗口绘制的区域中。这个特殊的窗口类被称为MDIClient,尽管它是不可见的,但它参与控制插入其中的MDI子窗口的行为。
MDIClient负责所有那些MDI特殊的事情,比如最小化和最大化行为、平铺和级联以及其他许多独特的功能。如果想使用这种类型的应用程序,值得阅读一些相关文章。对于MDI应用程序,将制作OpenGL窗口并将它们插入到MDIClient中,并允许标准行为起作用。
从第一课中保留的伪代码没有改变,只是从应用程序窗口转移到了MDI子窗口,并且在关闭时添加了一个清理步骤。因此,每个MDI子窗口运行以下序列:
1. 初始化OpenGL窗口(仅调用一次)
2. 缩放OpenGL视口(初始调用)
重复
3. 绘制场景
4. 将场景传输到屏幕
直到窗口关闭
5. 窗口关闭时清理OpenGL内存和其他使用的东西
注意:第2步的缩放过程也会在窗口大小改变时被调用。
使用与第一课相同的结构来保存数据,只是这次每个MDI子窗口创建一个结构,并且它附加到每个MDI子窗口上。第1步的初始化返回一个特定于MDI子窗口的渲染上下文,因此每个MDI子窗口都有自己的渲染上下文。
MDI子窗口处理程序成为所有OpenGL调用的场所。创建MDI子窗口将导致创建一个新的渲染上下文,它将存储在窗口本身的自己的数据库结构中。因此,每个MDI子窗口将有自己的数据结构和消息,创建操作的窗口变得独特于附加到每个MDI子窗口的数据结构。因此,每个MDI子窗口可以执行不同的操作,而不需要跟踪OpenGL系统本身的复杂性。
在第一课中看起来有点复杂的数据持有安排使MDI中的数据管理变得明显和简单。还有其他更快的方法将数据附加到窗口,但它们比SetProp/GetProp更复杂,但这对初学者的目标受众来说是最简单。稍后,当进入游戏和更快的渲染情况时,将讨论其他处理方法。
下面的MDI子窗口是所有OpenGL调用和控制的地方,值得一看这个过程,并将其与上面的伪代码相提并论。
static LRESULT CALLBACK OpenGLMDIChildHandler(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch(Msg){
case WM_CREATE: {
// WM_CREATE MESSAGE
GLDATABASE* db = (GLDATABASE*) malloc(sizeof(GLDATABASE));
// Allocate structure
db->Rc = InitGL(Wnd);
// Initialize OpenGL & get render context
db->glTexture = 0;
// Zero the texture
db->xrot = 0.0f;
// Zero x rotation
db->yrot = 0.0f;
// Zero y rotation
SetProp(Wnd, DATABASE_PROPERTY, (HANDLE) db);
// Data structure hold as property
ReSizeGLScene(Wnd);
// Rescale the OpenGL window
}
break;
case WM_DESTROY: {
// WM_DESTROY MESSAGE
wglMakeCurrent(NULL, NULL);
// Make the rendering context not current
GLDATABASE* db = (GLDATABASE*) GetProp(Wnd, DATABASE_PROPERTY);
// Get data struct
if(db != 0) {
if(db->Rc != 0) wglDeleteContext(db->Rc);
// If valid delete context
if(db->glTexture != 0)
glDeleteTextures(1, &db->glTexture);
// If valid delete the texture
free(db);
// Release the data structure memory
}
}
break;
case WM_PAINT: {
// WM_PAINT MESSAGE
PAINTSTRUCT Ps;
GLDATABASE* db = (GLDATABASE*) GetProp(Wnd, DATABASE_PROPERTY);
// Get data struct
BeginPaint(Wnd, &Ps);
// Begin paint
DrawGLScene(db, Ps.hdc);
// Draw the OpenGL scene
SwapBuffers(Ps.hdc);
// Swap buffers
EndPaint(Wnd, &Ps);
// End paint
return 0;
}
break;
case WM_TIMER: {
// WM_TIMER MESSAGE
GLDATABASE* db = (GLDATABASE*) GetProp(Wnd, DATABASE_PROPERTY);
// Get data struct
db->xrot += 1.0f;
// Inc x rotation
db->yrot += 1.0f;
// Inc y rotation
InvalidateRect(Wnd, 0, TRUE);
// Redraw now so invalidate us
}
break;
case WM_WINDOWPOSCHANGED:
// WM_WINDOWPOSCHANGED
// Check if window size has changed .. window move doesnt change aspect ratio
if((lParam == 0) || ((((PWINDOWPOS) lParam)->flags & SWP_NOSIZE) == 0)){
ReSizeGLScene(Wnd);
// Rescale the GL window
InvalidateRect(Wnd, 0, TRUE);
// We need a redraw now so invalidate us
}
break;
case WM_ERASEBKGND:
// WM_ERASEBKGND MESSAGE
return (FALSE);
}
return DefMDIChildProc(Wnd, Msg, wParam, lParam);
// Unprocessed messages to DefMDIChildProc
}