加入收藏 | 设为首页 | 会员中心 | 我要投稿 吕梁站长网 (https://www.0358zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 搭建环境 > Windows > 正文

windows窗体 基于MVC模式的OpenGL Windows GUI 应用程序

发布时间:2022-10-15 05:31:31 所属栏目:Windows 来源:互联网
导读:
本文是关于在Windows平台上用MVC(模型-视图-控制器)框架来创建一个OpenGL GUI应用程序。MVC架构对于GUI应用程序来说是一个常见的设计框架,用于很多GUI库,如.NET,MFC,Qt,JavaFx等等

本文是关于在Windows平台上用MVC(模型-视图-控制器)框架来创建一个OpenGL GUI应用程序。MVC架构对于GUI应用程序来说是一个常见的设计框架,用于很多GUI库,如.NET,MFC,Qt,JavaFx等等。这种MVC框架的主要优点在于:从Windows系统中和用于多个窗口的通用消息路由中将系统无关的OpenGL调用完全分离出来。简介

windows窗体_windows virtual pc 如何变大窗体_windows窗体应用程序

MVC设计示意图

MVC示例把一个应用程序分成三个独立组件:模型,视图和控制器组件,这是为了让三者之间的依赖关系最弱。

模型组件是应用程序的大脑部分,它包含了所有应用程序数据和实现,用于告诉应用程序应该怎样表现。更重要的是,模型组件没有任何视图或控制器组件的引用,这意味着模型组件是单纯独立的,它不知道控制器或者视图组件与谁有关联。模型组件只是处理来自任何控制器或视图组件的请求。

视图组件负责将视觉内容渲染到屏幕上。同时,视图模块没有任何控制器组件的引用(在控制器中独立)。当任何控制器组件请求更新视觉内容时,视图组件只是实现渲染处理。但是,视图组件应该引用某个模型组件,因为它必须知道从哪里获得数据,只有这样它才能在屏幕上渲染数据。

控制器组件是用户和应用程序之间的桥梁,它可以通过接收和处理所有的用户事件,如键盘输入和鼠标输入。这个模块应该知道要访问哪个模型和视图组件。为了处理一个用户事件,控制器组件请求模型来处理新数据,同时告诉视图组件更新视觉信息。

windows virtual pc 如何变大窗体_windows窗体_windows窗体应用程序

一个GUI应用程序:货币转换

这是一个非常简单的场景,想象你正在做一个加元到美元的货币转换程序,当用户点击“转换”按钮,你的应用程序应该做什么?

1.控制器首先得到按钮的单击事件。

2.控制器向模型发送输入值并且请求转换。

3.模型把输入转成美元并且保存结果。

4.控制器请求视图显示结果。

5.视图得到来自模型的结果。

6.视图在屏幕上显示结果。

MVC设计的主要优点是清晰性和模块性。因为MVC示例把一个应用程序干干净净地分解成三个逻辑模块,这样一来更清晰、更容易理解每个组件的作用,让不同开发者分别维护各个模块。由于MVC高效的模块性,组件是可交互的、可扩展的。例如:你可以自定义视图组件look-and-feel,无需修改模型和控制器模块,或者你可以同时添加多个不同视图(表格和图表)。

例1:glWinSimple

windows窗体应用程序_windows virtual pc 如何变大窗体_windows窗体

这是一个窗体OpenGL应用程序,它除了鼠标交互外没有GUI控件。但是,这个例子可以帮助我们更好地理解怎样在一个OpenGL应用程序中实现MVC设计。之后,我们会在后面的部分讨论更复杂的例子。

windows窗体_windows virtual pc 如何变大窗体_windows窗体应用程序

glWinSimple的MVC示例图

从glWinSimple.zip下载源文件和二进制(64位)文件

(更新日期:2016-07-18,包含VS2015项目)

这个应用程序包括三个独立的

C++类:ModelGL,ViewGL和ControllerGL。对于OpenGL应用程序来说,所有系统无关的OpenGL命令都可以放在ModelGL组件中,所以,模型组件本身可以无需修改就在其它平台上重用。因此,模型在控制器和视图模块上是单纯独立的。

视图组件用于向屏幕渲染视觉结果。因此,所有的显示设备属性(渲染上下文,颜色位数等等)都会放在这个组件中。同时,系统特定的OpenGL命令会放在这个组件中,如wglCreateContext()何wglMakeCurrent()。此外,视图组件不会引用控制器(独立于控制器),但是可能会引用到模型,例如:要得到模型数据来更新视图内容。

控制器组件首先用于接收所有用户事件,之后更新模型状态,并且通知视图组件渲染场景。对于键盘和鼠标左/右键,控制器有基本的输入处理函数。请看一下ControllerGL类中keyDown(), lButtonDown(), rButtonDown(), mouseMove()等方法的源代码。ControllerGL类从Controller.cpp的基类继承,如果你需要重写默认方法,你可以向ControllerGL类中加入事件处理器。

这三个对象是在main方法中创建的,之后会创建一个引用(指针)ControllerGL对象的单个窗口。我使用了一个帮助类Window.cpp来创建一个窗体。注意,main函数还是非常简单的,所有的详细实现都挪到了三个独立的组件中:ModelGL,ViewGL和ControllerGL.

windows窗体应用程序_windows virtual pc 如何变大窗体_windows窗体

创建OpenGL窗体

创建一个OpenGL窗体和创建其他通用窗体是一样的,除了OpenGL渲染上下文(RC)。OpenGL渲染上下文是一个将OpenGL连接到Windows系统的端口,所有的OpenGL命令都可以通过这个渲染上下文。渲染上下文必须和一个设备上下文(DC)关联,同时DC与RC有相同的像素格式,所以,可以在设备层面上进行OpenGL绘制。

在你的WM_CREATE处理器中,你可以创建一个RC:

1.用GetDC()和窗体句柄获取OpenGL的DC。

2.用SetPixelFormat()和DC设置想要的像素格式。

3.用wglCreateContext()和DC创建新的RC。

4.用ReleaseDC()释放DC。

在你的WM_CLOSE控制器中,你可以删除RC:

1.用wglMakeCurrent()和空参数释放当前RCand。

2.用wglDeleteContext()删除RC。

在你的渲染循环中,在调用任何OpenGL命令前,用wglMakeCurrent()把渲染上下文设置为当前RC,我为渲染循环使用了一个单独的工作线程,请看下面的部分“针对渲染OpenGL的独立线程”。

通过用DescribePixelFormat()查找所有可用的格式来找到需要的像素格式,一种查找最好像素格式的标准评分机制在ViewGL类的findPixelFormat()方法中。

windows virtual pc 如何变大窗体_windows窗体_windows窗体应用程序

针对渲染OpenGL的独立线程

MS Windows是一个事件驱动的系统,如果没有任何事件触发,一个事件驱动的应用程序会休息,不会做任何事。但是,即使没有事件到达应用程序,OpenGL渲染窗体可能也需要不断更新。不断更新OpenGL窗体的解决方案之一是在窗体消息循环中使用PeekMessage()。

windows virtual pc 如何变大窗体_windows窗体应用程序_windows窗体

但是,为渲染OpenGL使用独立工作线程还有一个更好的解决方案。多线程的优点是你可以让消息循环做它自己的工作,只处理窗体事件,独立工作线程会自己处理渲染OpenGL场景。同时,我们不需要在这个消息循环中暴露任何OpenGL渲染方法。所以,主要消息循环可以尽可能保持简单。

windows窗体_windows virtual pc 如何变大窗体_windows窗体应用程序

当创建了窗体时(触发了WM_CREATE事件),ControllerGL对象会初始化ModelGL和ViewGL对象windows窗体,之后,为OpenGL渲染启动一个独立工作线程。std::threadC++类对象用于创建一个工作线程。请阅读ControllerGL::create()和ControllerGL::runThread()来获取更多详细信息。

windows virtual pc 如何变大窗体_windows窗体应用程序_windows窗体

例2:glWin

windows窗体_windows virtual pc 如何变大窗体_windows窗体应用程序

这个例子中有三个窗体:OpenGL渲染子窗体,另一个包含所有空间的子对话窗体,一个主窗体包括了上述两个子窗体。(在主窗体中有菜单条和状态条,但是在这篇文章中我们不用这些)

从glWin.zip下载源文件和64位二进制文件。

(更新日期:2016-7-18,包含VS2015项目)

windows窗体_windows窗体应用程序_windows virtual pc 如何变大窗体

glWin的MVC示意图

既然有三个窗体,那么这个应用程序就需要三个控制器对象,每个窗体一个控制器:ControllerMain,ControllerGL和ControllerFormGL。另外还有两个视图组件,需要ViewGL和ViewFormGL,一个对应OpenGL渲染,一个对应对话子窗体。因为主窗体只是一个容器,它没有视图组件。注意,这里只需要有一个模型对象,有三个控制器和两个视图引用它。

ViewFormGL是一个对话(或者表单)窗体,包含着一些控件(按钮,文本框等等)。因此所有的控件都在ViewFormGL类中定义。我为通用的控件实现了一些类,它们在Controls.h文件中声明。目前支持的控件包括Button, RadioButton, CheckBox, TextBox, EditBox, ListBox, TrackBar,ComboBox和TreeView。

当用户点击了Animate按钮时,以下的场景描述了会发生什么:

1.ControllerFormGL首先得到BN_CLICKED事件。

2.ControllerFormGL在ModelGL中将动画标志设为true,这样一来地球可以转动了。

3.ControllerFormGL告诉ViewFormGL把按钮的标题改为"Stop"。

消息路由

当你创建一个窗体的时候,更准确地说是当你注册一个窗体类时(RegisterClass()或RegisterClassEx()),一个窗体应用程序必须向回调函数(窗体程序)提供一个指针。当你的程序发生一些事时,比如,用户单击按钮控件,Windows系统会向你的程序发送一个消息,消息会传递到你指定的窗体程序中。

总体来说,窗体程序看起来像这样:

windows窗体应用程序_windows窗体_windows virtual pc 如何变大窗体

这种MVC框架的另一个优点是动态消息路由,它可以将消息分发至与窗体句柄相关的控制器。所以,我们可以只创建窗体程序一次,之后为你创建的所有窗体多次重用。这种技术的灵感来自于可靠性软件的Windows API教程。基本思想是:当你创建一个窗体的时候,把指针传递到CreateWindow()或者CreateWindowEx()的IpParam中。当WM_NCCREATE消息被调用时,我们会从lpCreateParams中抽取出指针值,并把值存储为窗体的GWL_USERDATA属性。之后,触发了其他消息,我们只是查找一个窗体的GWL_USERDATA来确定哪个控制器与窗体关联。因为这个窗体程序会用于你创建的所有窗体,你不需要担心再写另一个窗体程序。

实际的消息路由长这样:

windows窗体应用程序_windows virtual pc 如何变大窗体_windows窗体

你在主函数中创建了三个如下的窗体。注意,当我们初始化一个窗体的参数时,我们需要向控制器对象提供指针,这个指针值会存储在每个窗体的GWL_USERDATA中,之后,消息路由会动态地把窗体事件分配给给定的控制器对象。

windows virtual pc 如何变大窗体_windows窗体应用程序_windows窗体

对话窗体程序有一点不同。当调用WM_INITDIALOG消息时,我们把控制器的指针存储到GWL_USERDATA中,而不是WM_NCCREATE。请查看procedure.cpp获取更多信息。

进一步了解控制器类

这种MVC框架提供了一个基础控制器类,它是默认的事件处理器。确实,它什么都没做就返回了0。为了在来消息时做一些有意义的事,你需要创建一个类继承基类,并且重写(覆写)虚拟函数。控制器基类中虚拟函数的名字与没有前缀的消息ID是一样的。例如:WM_LBUTTONDOWN消息对应lButtonDown()方法。

对于上面的glWin程序,ControllerMain,ControllerGL和ControllerFormGL都是继承自控制器基类的。

当用户单击关闭按钮或者从主菜单选择“退出”时,ControllerMain负责处理WM_CLOSE消息来中止程序。

ControllerGL只是为摄像头操作(缩放或者旋转摄像头)处理鼠标交互。另外,当WM_CREATE消息到达时,ControllerGL会为OpenGL渲染器创建一个工作线程。

ControllerFormGL管理所有的控制接口。

例3:多个OpenGL窗口

windows窗体应用程序_windows窗体_windows virtual pc 如何变大窗体

在一个应用程序中有多个OpenGL窗体是可能的,下面的例子包括两个OpenGL子窗体来渲染来自两个不同角度的场景。

单击OrbitCamera.zip获得源代码和二进制文件(64位)

(更新时间:2016-7-30,包含VS2015项目)

windows窗体应用程序_windows virtual pc 如何变大窗体_windows窗体

OrbitCamera的MVC示意图

对于每个OpenGL窗体,它包含两个控制器/视图,加上一个额外的控制器表单/视图表单,后面这一对是为了控件(按钮,追踪条等等)的对话框。但是,所有三个控制器/视图仍然引用单个模型组件来更新OpenGL场景。

因为两个OpenGL窗体都渲染相同的场景,这里只创建了一个OpenGL渲染上下文(RC),它被两个窗体共享,因此,ControllerGL1和ControllerGL2的实现有稍许不同。ControllerGL1同时创建了设备上下文(DC)和OpenGL RC,但是ControllerGL2只创建了新设备上下文本身,并且共享已经被ControllerGL1创建好了的OpenGL RC。

这个应用程序不使用独立线程来自动渲染OpenGL,取而代之的是,ControllerForm保留了对于ControllerGL1和ControllerGL2的引用。并且,每当触发了一个用户事件时,它都调用会两个控制器的paint()函数。

注意,第二个窗体的DC必须与第一个窗口的DC有相同的像素格式,这是为了共享相同的OpenGL RC。在绘制的时候,你可以用调用wglMakeCurrent()轻松地转换DC。

windows窗体应用程序_windows virtual pc 如何变大窗体_windows窗体

你可以创建多个OpenGL渲染上下文,之后在多个RC中共享OpenGL对象。例如:一个在第一个RC中创建的纹理对象可以用于第二个RC中,因为多个RC可以用多个线程和窗口异步运行,你必须适当同步OpenGL渲染管道和GL_ARB_sync扩展(阻止进一步的OpenGL调用,直到当前命令已经完成)。

关于OpenGL中摄像机的实现,请阅读OpenGL Camera。

?2007-2016Song Ho Ahn (???)

(编辑:吕梁站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!