怎么在一个由MFC AppWizard(EXE)建立的标准MFC框架里“放置”一个OpenGL程序呢? ——ZwqXin.com
说到MFC,就有文档类视图类那些关键字眼。本人虽然喜欢WIN32多于MFC,但是也无法不承认MFC能带给程序员的便利。一直写OpenGL代码所用的NEHE VC6框架其实也是基于MFC的(但是很简洁,没有虾米文档类视图类那些东西),它其实是利用了MFC的一些交互性而已,实质还是构造了一个渲染循环。具体框架的自剖可看:
本文来源于 ZwqXin (http://www.zwqxin.cn/), 转载请注明
原文地址:http://www.zwqxin.cn/archives/MFC/formal-mfc-in-opengl.html
但是有时候,你不得不在标准MFC下写OPENGL代码。而这时候应该注意些什么呢?在一般写三维程序的时候,都会很留意它的实时性,因此会在程序的MAIN类函数中构建一个死循环,让程序不断重复运行渲染部分的代码,也因此有了FPS之类的概念。这样的循环也是WIN32的骨架[WINDOWS游戏编程大师技巧笔记之——WIN32编程] ,那么,封装了WIN32的MFC应该也是这样吧?也许我对MFC真的不熟悉,但按我所理解的,MFC所封装的死循环不会轻易漏出真面目给我们放东西进去,代之的是MFC提供了VIEW类这么一种东西,让我们把绘图逻辑都放进这种基于消息的结构中。恩,在消息处理部分完成绘图。这符合绝大部分应用类程序的需求:不做无用功。
消息就是这么一种东西,它等到一定事件发生才被激活,譬如鼠标点击、移动……绘图消息WM_PAINT也是,只有当系统或者我们告诉它窗口需要重绘时,它才动手。如果我们把我们的OPENGL绘图逻辑直接搬到这里,结果将是:只有你做一些迫使MFC窗口重绘的事,譬如最小化后又复原,譬如鼠标乱点乱移,譬如更改窗口位置或大小……它才会“继续渲染下去”,否则将是静止在那儿,一动不动。
因此得像NEHE框架一样靠我们自己另外“安装”一个含消息处理的死循环进去吗?其实我们可以在每次WM_PAINT消息被发送时执行的代码片段中依然加入渲染部分的代码,然后在代码末尾手动强迫让程序再次发送WM_PAINT消息:
- //在重绘消息WM_PAINT被发送时执行:
- void CSampleView::OnDraw(CDC* pDC)
- {
- CTSampleDoc* pDoc = GetDocument();
- ASSERT_VALID(pDoc);
- //OPENGL前序工作顺利完成时,设置此标志为TRUE
- if (!m_bAppIsActive)
- return;
- RenderAll();//全部的渲染代码
- SwapBuffers(m_hDC);//双缓冲交换
- Invalidate(FALSE); //发送WM_PAINT
- }
MFC中,VIEW类的成员函数OnDraw(CDC *pDC)实质是对回调响应OnPaint内部,BeginPaint和EndPaint之间那段代码的封装函数在VIEW类下的重载,因此其实就是程序的绘图函数。Invalidate的调用则是强制发送重绘消息,这样程序马上又进入本函数了——这就通过MFC内置机制模拟了死循环这个大饼,另外,参数为FALSE是为了让GDI的背景重绘失效。
其他的都跟NEHE框架差不多了。像素设置,DC-RC关联和初始化放在void CSampleView::OnInitialUpdate()里就就可以了,反正就是让程序进入“渲染循环”之前完成,设m_bAppIsActive为TRUE。设置一个KillGLWindow() 函数能让程序在设置失败或者程序关闭的时候能执行,以删除DC,RC和窗口~(上面提及的设置的定义和作用见[自剖一下自己用的NEHE OpenGL框架(下篇)])
补充一下,OPENGL的程序一定要放在CView类或由CView类派生的VIEW类中啊~!不知道为什么啊~!
最后走题一下,说说MFC分割窗口(这东西用WIN32做就苦了~)。在标准MFC的CMainFrame类里,利用CSplitterWnd对象作为成员变量(CSplitterWnd m_wndSplitter),对整个外观框架进行分割:
- BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
- {
- if (!m_wndSplitter.CreateStatic(this, 1, 2))
- return FALSE;
- if (!m_wndSplitter.CreateView(0, 0, RUNTIME_CLASS(CLeftView), CSize(224, 600), pContext)
- || !m_wndSplitter.CreateView(0, 1, RUNTIME_CLASS(CRightView), CSize(800, 600), pContext))
- {
- m_wndSplitter.DestroyWindow();
- return FALSE;
- }
- return TRUE;
- // return CFrameWnd::OnCreateClient(lpcs, pContext);
- }
这样出来的是两个左右分割的VIEW(对应同一个DOCUMENT)。这样,我在左框架下的视图放个树控件,在右框架下的视图进行OPENGL绘图,好,好~
如果两个视图需要沟通呢?譬如在左视图树控件内点击某项(模型名称),右视图的模型便被选中(选中状态下可进行鼠标拖动模型在XY平面移动,嘛,这处理细节不重要)。这样的话,右视图的VIEW类起码得能够接收从左视图VIEW类发来的信息啊~它得知道有左视图的存在!我们在右视图里添加一个连接用的函数,在初始化时调用以建立与左视图的“连接”:
- class CLeftView;
- class CRightView : public CView
- {
- ..................
- CLeftView *MyTreeView;
- };
- bool CRightView ::ConnectTreeView()
- {
- CMainFrame *MyMainFrame = (CMainFrame *)AfxGetApp()->m_pMainWnd;
- if(!MyMainFrame)return false;
- MyTreeView = (CLeftView *)MyMainFrame->m_wndSplitter.GetPane(0,0);
- if(!MyTreeView)return false;
- return true;
- }
在初始化时调用上函数后,现在MyTreeView这个成员指针变量就指向了左视图VIEW类(作为实例)了~注意GetPane的参数跟之前创建分割窗口的代码段中生成左视图时CreateView的头两个参数是一致的。
在一个之前弄的小DEMO里,有“把OPENGL放入标准MFC框架”以及分割窗口的例子:
Picking Demo by zwqxin 2008.9 & 2009.6
(在 downloads下的 PickingDemo by ZwqXin.rar压缩包)
确实,发觉我已经穿越了 - -。
本文来源于 ZwqXin (http://www.zwqxin.cn/), 转载请注明
原文地址:http://www.zwqxin.cn/archives/MFC/formal-mfc-in-opengl.html