« 快速傅立叶变换视锥类CFrustum .zwqxin ver »

标准MFC在OpenGL

怎么在一个由MFC AppWizard(EXE)建立的标准MFC框架里“放置”一个OpenGL程序呢?    ——ZwqXin.com

说到MFC,就有文档类视图类那些关键字眼。本人虽然喜欢WIN32多于MFC,但是也无法不承认MFC能带给程序员的便利。一直写OpenGL代码所用的NEHE VC6框架其实也是基于MFC的(但是很简洁,没有虾米文档类视图类那些东西),它其实是利用了MFC的一些交互性而已,实质还是构造了一个渲染循环。具体框架的自剖可看:

[自剖一下自己用的NEHE OpenGL框架(上篇)]

 [自剖一下自己用的NEHE OpenGL框架(中篇)]

[自剖一下自己用的NEHE OpenGL框架(下篇)]

本文来源于 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消息:

  1.  
  2. //在重绘消息WM_PAINT被发送时执行:
  3. void CSampleView::OnDraw(CDC* pDC)
  4. {
  5.     CTSampleDoc* pDoc = GetDocument();
  6.     ASSERT_VALID(pDoc);
  7.  
  8. //OPENGL前序工作顺利完成时,设置此标志为TRUE
  9.     if (!m_bAppIsActive)
  10.         return;
  11.  
  12.     RenderAll();//全部的渲染代码
  13.  
  14.     SwapBuffers(m_hDC);//双缓冲交换
  15.  
  16.     Invalidate(FALSE); //发送WM_PAINT
  17. }

 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),对整个外观框架进行分割:

  1. BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) 
  2. {
  3.     if (!m_wndSplitter.CreateStatic(this, 1, 2))    
  4.       return FALSE; 
  5.  
  6.   if (!m_wndSplitter.CreateView(0, 0, RUNTIME_CLASS(CLeftView), CSize(224, 600), pContext) 
  7.     || !m_wndSplitter.CreateView(0, 1, RUNTIME_CLASS(CRightView), CSize(800, 600), pContext)) 
  8.     {               
  9.        m_wndSplitter.DestroyWindow(); 
  10.        return FALSE; 
  11.     } 
  12.     return TRUE; 
  13.     
  14. //  return CFrameWnd::OnCreateClient(lpcs, pContext);
  15. }

这样出来的是两个左右分割的VIEW(对应同一个DOCUMENT)。这样,我在左框架下的视图放个树控件,在右框架下的视图进行OPENGL绘图,好,好~

如果两个视图需要沟通呢?譬如在左视图树控件内点击某项(模型名称),右视图的模型便被选中(选中状态下可进行鼠标拖动模型在XY平面移动,嘛,这处理细节不重要)。这样的话,右视图的VIEW类起码得能够接收从左视图VIEW类发来的信息啊~它得知道有左视图的存在!我们在右视图里添加一个连接用的函数,在初始化时调用以建立与左视图的“连接”:

  1.  
  2. class CLeftView;
  3. class CRightView : public CView
  4. {
  5. ..................
  6.     CLeftView  *MyTreeView;
  7. };
  8.  
  9. bool CRightView ::ConnectTreeView()
  10. {
  11.     CMainFrame *MyMainFrame = (CMainFrame *)AfxGetApp()->m_pMainWnd;
  12.     if(!MyMainFrame)return false;
  13.  
  14.     MyTreeView = (CLeftView  *)MyMainFrame->m_wndSplitter.GetPane(0,0);
  15.     if(!MyTreeView)return false;
  16.  
  17.     return true;
  18. }

在初始化时调用上函数后,现在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

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

IE下本页面显示有问题?

→点击地址栏右侧【兼容视图】←

日历

Search

网站分类

最新评论及回复

最近发表

Powered By Z-Blog 1.8 Walle Build 100427

Copyright 2008-2024 ZwqXin. All Rights Reserved. Theme edited from ipati.