虽然MFC已经落伍好多年,而且用来做界面非常的不好用。。。但是我既不会C#也不会QT,又需要使用OpenGL,就只能将就用了。。。
一、首先介绍Windows图像程序设计中几个重要的概念:
GDI(Graphics Device Interface,图形设备接口):这是Windows API的一个库。当Windows应用程序需要显示点、线、图像、文字等内容,在显示器或打印输入这些内容时,就需要用到GDI。Windows应用程序不能直接操作系统的硬件(比如显卡),GDI就为应用程序提供了相关的接口。
其相关的函数接口、数据类型等都在WinGDI.h中声明(已经由Windows.h引入),在程序开发时,需要链接到Gdi32.lib。
DC(Device Contexts,设备上下文):是GDI库中最基本也是最重要的概念。DC是一个对象,设定了图形输出的特性和属性。
系统中可以有多个DC,每一个DC都必须关联到一个特定的图像输出设备。这些设备可以是真实存在的物理设备(显示器、打印机、绘图仪等),也可以使虚拟设备。这些反应在DC的类型上,DC具有4种类型:“显示”、“打印机”、“内存”、“信息”。
其中显示类型DC是最常用的,它被关联到了显示设备上,所有的图像输出操作将直接反映在显示器上。
注:DC也可以只是设备全部输出范围的一部分。比如界面上某个窗口的客户区也可以有DC与之对应,对这样的DC进行操作只会影响到窗口客户区。
如果要将图像输出到特定的设备只需要创建相应类型的DC即可(注:对不同类型DC的操作是统一的),我们只关注获取显示器相关DC的操作:(以下的函数都是GDI库中的接口函数)
1. 获取DC - GetDC(HWND hWnd):
调用该函数会返回hWnd参数所指定的窗口的客户区所对应的DC的句柄。
如果hWnd参数设置为NULL,那么函数会返回整个桌面的DC。
2. 另一种获取DC的方法 - CreateDC
该函数也是用来获取DC句柄,与GetDC不同的是,CreateDC可以获取非显示器输出DC。只需指定不同的参数即可。
获取显示器的DC:HDC hScreenDC = CreateDC("DISPLAY", NULL, NULL, NULL);
若想获取打印机的DC,一般是将第一个参数改为"WINSPOOL"。
3. 释放DC - ReleaseDC(HWND hWnd, HDC hDC)
该函数的作用是释放DC,使其他应用程序可以使用。
4. 释放DC的相关系统资源 - DeleteDC(HDC hDC)
这个函数并不常用,一般做图结束后,我们只需要调用ReleaseDC释放DC即可。
也许看到这里,对DC具体是个什么东西还没有什么清楚的概念。下面大概讲一下。。。
DC实际上是包含了一系列的图形对象,比如位图(Bitmap)、画刷(Brushe)、画笔(Pen)、字体(Font)、逻辑调色板(Logical palette)等等,GDI库中还定义了一系列接口函数,应用程序通过调用这些接口函数来操作当前DC中的图形对象,完成期望的绘图操作,最终影响放映到对应设备的输出上。
比如先创建一个窗口,然后得到该窗口对应的DC,这时候系统会为DC创建默认的图形对象(位图和路径除外),此时如果不进行任何操作,那么显示的窗口是一片白(也就是说刚开始是一块白色画布)。然后我们可以调用GDI库中的接口函数进行画图操作,或者载入位图(相当于在白色画布上作画)。DC中就是提供了对画布进行作画的工具。
注:DC对应设备的显示信息都存储在位图对象中。
为了更清楚DC的作用,下面举个很简单的例子:在屏幕上画一条线
首先,获得整个显示器的DC:
HDC hdC = GetDC(NULL);
创建新的画笔对象:
COLORREF cPen = RGB(0,0,0); //指定画笔颜色为黑色
HPEN hpen = CreatePen(PS_SOLID, 10, cPen); //创建新的画笔,返回新画笔的句柄
将新创建的画笔指定为DC的当前画笔:(对同一种类型的图形对象,DC中只能有一个当前对象)
HPEN hpenOld = SelectObject(hdc, hpen);
画线:
LineTO(hdc, 500, 500);
画图操作结束,还原画笔:
SelectObject(hdc, hpenOld);
释放画笔资源:
DeleteObject(hpen);
释放DC:
ReleaseDC(NULL, hdc);
二、下面来讨论使用OpenGL来绘图的相关知识:
使用OpenGL绘图与使用GDI库绘图是不同的,主要体现在:OpenGL采用的是RC(Render Context,渲染上下文)绘图。
DC和RC的区别和联系:
1. 在Windows中使用GDI绘图时必须指定在哪个DC中绘制,同样地,在使用OpenGL函数时也必须指定一个所谓的RC。正如设备上下文DC要存储GDI的绘制环境信息如笔、刷和字体等,RC也必须存储OpenGL所需的渲染信息如像素格式等。
2. Windows下的窗口和DC支持的位图格式(PIXELFORMAT)属性,和RC有位图结构上的一致。只要在创建RC时与一个DC建立联系,OpenGL函数就可以通过与RC对应的DC绘制到相应设备上。而实际上,RC只能通过建立了位图格式的DC来创建。
3. 一个DC对应的是一个图像输出设备,而一个RC对应的则是一个线程。一个线程只能拥有一个RC,而一个RC也只能属于一个线程,不能在线程中共有。若一个线程想要在不同的设备上绘图,只能通过更改与RC对应的DC来实现,而RC在线程总保持不变(当然也可以删除旧的RC,再利用其它设备的DC创建新的RC)。
下面来讲一下RC的创建。正如前面所说的,RC只能通过建立了位图格式的DC来创建。
1. 首先,我们需要创建一个窗口,然后使用GetDC函数得到这个窗口的DC。我们这里讨论在MFC使用OpenGL,因此窗口类是由工程自动创建的COpenglDemoView类,由于我们做的是绘图前的窗口初始化工作,因此我们将之后所有的操作都在OnCreate函数中完成。(至于一些必要的成员数据初始化工作,应放在View类的构造函数中)
HWND hWnd = this->GetSafeHwnd();
HDC hDC = ::GetDC(hWnd);
2. 然后,我们需要指定DC中的位图像素格式。这个工作是由GDI库中名为PIXELFORMATDESCRIPTOR的类来实现的。
第一步是填充这个数据结构:
PIXELFORMATDESCRIPTOR pixelDesc={
sizeof(PIXELFORMATDESCRIPTOR), //nsize:像素格式描述子结构的大小
1, //nVersion:PIXELFORMATDESCRIPTOR结构的版本,一般设为1
//dwFlags:一组表明像素缓冲特性的标志位
PFD_DRAW_TO_WINDOW | //使之能在窗口或者其他设备窗口画图
PFD_SUPPORT_OPENGL,// | //使之能使用OpenGL函数
//PFD_DOUBLEBUFFER, //指明使用了双缓冲
PFD_TYPE_RGBA, //PixelType:定义了显示颜色的方法
24, //cColorBits:指定了一个颜色的位数
0,0, //cRedBits, cRedShift:每个RGBA颜色缓冲区中红色位平面的数目和偏移数
0,0, //cGreenBits, cGreenShift:每个RGBA颜色缓冲区中绿色位平面的数目和偏移数
0,0, //cBlueBits, cBlutShift:每个RGBA颜色缓冲区中蓝色位平面的数目和偏移数
0,0, //cAlphaBits, cAlphaShift:每个RGBA颜色缓冲区中Alpah位平面的数目和偏移数
0, //cAccumBits:累加缓冲区中全部位平面的数目
0,0,0,0, //cAccumRedBits, cAccumGreenBits, cAccumBlueBits, cAccumAlphaBits
32, //cDepthBits:Z(深度)缓冲区的深度
0, //cStencilBits:模板缓冲区的深度
0, //cAuxBuffers:轴向缓冲区的数量(一般1.0版本不支持)
PFD_MAIN_PLANE, //iLayerType:忽略,为了一致性而包含的
0, //bReserved:表层和底层平面的数量
0,0,0 //dwLayerMask, dwVisibleMask, dwDamageMask
};
这个类中的大部分成员变量我们并不关心,最重要的是第三个参数dwFlags,其中指定的PFD_SUPPORT_OPENGL使得我们可以在这个窗口中使用OpenGL函数。
而指定的PFD_DOUBLEBUFFER则使得我们可以使用OpenGL中的双缓存机制(这个机制在制作动画时非常重要)。但奇怪的是当我选择了这个标志位时,生成的窗口没有图像(一片空白),而将这个标志位注释掉,就能显示我所绘制的图了。非常诡异,我也不明白为什么。。。
第二步是将这个像素格式选为当前DC的使用像素格式:
this->m_GLPixelIndex = ChoosePixelFormat(hDC, &pixelDesc);
if(this->m_GLPixelIndex==0){
this->m_GLPixelIndex = 1;
if(DescribePixelFormat(hDC,
this->m_GLPixelIndex,
sizeof(PIXELFORMATDESCRIPTOR),
&pixelDesc) == 0)
{
return false;
}
}
if(SetPixelFormat(hDC, this->m_GLPixelIndex, &pixelDesc) == false){
return false;
}
这段代码首先调用了ChoosePixelFormat函数来寻找OpenGL所支持的像素格式中,最接近所设置的像素格式,如果没有找到,就调用DescribePixelFormat函数来选择索引值为1的像素格式来填充设置的像素格式。这些操作完成后,保证了所设置的像素格式是OpenGL所支持的。
最后就可以调用SetPixelFormat函数来为指定当前DC的像素格式。
注:代码中的this->m_GLPixelIndex是我们自己添加的View类的protected型的成员变量,类型为int,用于保存所设置的像素格式在OpenGL所支持的像素格式列表中的索引值。
3. 设置像素格式完成后,就可以创建RC了。这里面要用到两个GDI库中的函数 wglCreateContext 和 wglMakeCurrent。
首先创建RC:
this->m_hGLContext = wglCreateContext(hDC);
然后选择新创建的RC成为当前DC对应的RC
wglMakeCurrent(hDC, this->m_hGLContext);
注:代码中的this->m_hGLContext是我们自己添加的View类的protected型的成员变量,类型为HGLRC,用于保存当前RC的句柄。
这样,RC就创建成功了。
RC创建成功后,我们就可以在View窗口中执行绘图操作了。其中,视窗、投影方式等设置放在OnSize函数中,而绘制操作则放在OnPaint函数中。
这里需要注意的是,MFC中没有提供类似GLUT中glutIdleFunc()的函数,而OnPaint函数只会在窗口创建或者窗口需要重绘的时候才会被调用。因此OnPaint函数中的绘图操作一般都是静态的,如果想绘制动画,则需要程序员自己写定时函数来控制窗口的重绘。
最后列一下参考的资料:
- MFC中添加OpenGL
WINDOWS下展示OpenGL有多种形式: MFC 或 win32,该如何向MFC中添加OpenGL?下面是介绍最简单OpenGL框架. 1.首先通过VS建立MFC应用程序-MyOpenGL,选择单 ...
- MFC中添加ToolTip提示框
PART 1 MFC 对话框中的 Buttton添加提示 例如我们想在一个对话框中的一个button控件添加tooltip,实现的方法如下: 1. 在该对话框的类中添加一个CToolTipCtrl类型 ...
- MFC中的主窗口修改标题
MFC中的主窗口修改标题 如何去掉“无标题”1.在主程序中的InitInstance(): m_pMainWnd->SetWindowText("你要显示的东西如果不想显示置空就行&q ...
- MFC中添加消息响应函数
转自:http://blog.csdn.net/eddy_liu/article/details/8474677 目前,用MFC设计的Windows应用程序几乎都采用文档/视图结构.这种程序框架与简单 ...
- MFC框架下Opengl窗口闪屏问题解决方案
转自https://blog.csdn.net/niusiqiang/article/details/43116153 虽然启用了双缓冲,但是仍然会出闪屏的情况,这是由于OpenGL自己有刷新背景的函 ...
- Windows MFC 两个OpenGL窗口显示与线程RC问题
问题为:背景界面是一个OpenGL窗口(对话框),在其上弹出一个OpenGL窗口(模态对话框)时, 1.上方的OpenGL窗口能响应鼠标操作等并刷新: 2.当移动或放大缩小上方的OpenGL窗口时,其 ...
- MFC中无标题栏窗口的移动
原文链接: http://blog.sina.com.cn/s/blog_6288219501015dwa.html 移动标准窗口是通过用鼠标单击窗口标题条来实现的,但对于没有标题条的窗口,就需要 ...
- MFC中添加了一个dialog,并创建了相应的类,初始化函数没有怎么办?
1.在头文件中添加 virtual BOOL OnInitDialog();2.在对于的CPP文件中添加 BOOL CXXDlg::OnInitDialog() { return TR ...
- MFC中获取各个窗口之间的句柄或者指针对象的方法
MFC在非常多的对话框操作中,我们常常要用到在一个对话框中调用还有一个对话框的函数或变量.能够用例如以下方法来解决. HWND hWnd=::FindWindow(NULL,_T("S ...
随机推荐
- MySQL内存结构
实际上MySQL内存的组成和Oracle类似,也可以分为SGA(系统全局区)和PGA(程序缓存区). mysql>show variables like "%buffer%" ...
- mysql init password centos
https://www.cnblogs.com/FlyingPuPu/p/7783735.html
- 太白金星的考验----python while循环的执着
我们知道 while循环的使用意味着 反复的执行一些操作,而且在while循环体中 从第一行代码执行到最后一行代码,(请您务必留心这句话!) 直到不再满足while后面给出的限定条件,才结束循环跳出到 ...
- 51nod 1843 排列合并机(DP+组合)
题解链接 不过求ggg不用O(n2)DPO(n^2)DPO(n2)DP,g[n]g[n]g[n]直接就是卡特兰数的第n−1n-1n−1项.即: g[n]=(2(n−1)n−1)−(2(n−1)n−2) ...
- Codeforces Round #470 (rated, Div. 1, based on VK Cup 2018 Round 1) 923D 947D 948E D. Picking Strings
题: OvO http://codeforces.com/contest/947/problem/D 923D 947D 948E 解: 记要改变的串为 P1 ,记目标串为 P2 由变化规则可得: ...
- CF812C Sagheer and Nubian Market 二分+贪心
模拟赛给他们出T1好了~ code: #include <bits/stdc++.h> #define ll long long #define N 100006 #define setI ...
- 转载:appium踩过的坑
原文地址:http://blog.csdn.net/wirelessqa/article/details/29188665 自己的操作:由于在window上安装appium时,报各种错误:所以选择在u ...
- bash 基础之五 条件测试、read
一 条件选择if语句 顺序执行:逐条运行: 选择执行: 代码有一个分支:条件满足时才会执行: 两个或以上的分支:只会执行其中一个满足条件的分支: 循环执行:代码片断(循环体)要执行0.1或多个来回: ...
- 一个list<Map>里map其中的一个字段的值相同,如何判断这个字段相同,就把这个map的其他字段存入另一个map中
//不建议使用Map保存这些,使用实体bean更好 package com.rxlamo.zhidao; import java.util.*; public class Main { ...
- 常用SQL之日期格式化和查询重复数据
本文列举一些工作中常用的SQL,以提升工作效率. 1 日期格式化 使用 DATE_FORMAT(get_date, '%Y-%m-%d') 函数进行格式化.其中:get_date 是需要被格式化的字段 ...