VC GDI编程

来源:互联网 发布:剑网三眉间雪捏脸数据 编辑:程序博客网 时间:2024/06/11 00:18

 正文 
所谓GDI(Graphics Device Interface,图形设备接口)其实就是API函数中专门针对于图形开发的函数集合这些函数都是Microsoft公司编写好的,为了能让开发人员快速地开发图形程序,开发人员只需要调用就行

在谈GDI函数之前,一定要先讲一下数据,因为图形开发,肯定离不开数据以简单的二维图形为例,你想在窗口中显示一个正弦曲线,就必须有这个正弦曲线的数据,然后用GDI提供的画图函数,讲数据显示成图形
二维图形其实就是以点线面三种元素组成 

点 
图形中最基本的元素就是点,C++中对于点的结构定义是 
typedef struct tagPOINT 

   LONG x; 
   LONG y; 
} POINT; 
就是由横坐标x和纵坐标y组成的一个点(这个中学生就懂,呵呵,那也不能省) 
GDI中将点画到窗口中的函数是SetPixel 


线 
线就是有序的点集合,可以用一个点数组表示一条线,也可以用链表来表示一条线,两者的区别就在于添加和删除线中一点的方便程度数组的优点在于存取方便,但添加和删除确比较麻烦,链表优点在于添加和删除方便,但系统开销比数组大具体大家在开发中是利用数组还是链表,需要根据具体需求来决定GDI中使用MoveToLineTo和PolyLine函数来绘制线

面 
面也是由一组有序点组成的,它和线的唯一区别在于绘制的时候需要用不同的函数来实现而且绘制面的时候,需要将数据以数组方式存储,因为GDI函数绘制面的函数Polygon和PolyPolygon不支持链表

GDI函数 
纯API开发C++程序,开发效率有限,而利用MFC对于VC开发效率有很大的提高,所以我们这以MFC开发为例说明GDI开发 

GDI开发中第一个概念就是DC(Device Context),大部分的书都翻译成设备上下文,这其实是一种直译,很多新手(我当初就是其中之一,呵呵)看到设备上下文的时候,都不知道是什么东西可以简单理解为画布,或当前窗口中绘图控制的一个接口它是一个抽象集合或抽象概念,用它可以有效地管理画笔刷子字体图片和区域
CDC就是MFC中,对应DC的类 
以CDC来绘制窗口内图形的步骤 
1. 获取当前窗口的DC,获取窗口DC可以用CDC* CWnd::GetDC()函数得到一个CDC指针, 
2. 得到DC指针后,先调用CDC::SelectObject 函数来设定画笔刷子字体图片和区域等绘图设置工作 
3. 调用绘图函数SetPixel绘制点,MoveToLineTo和PolyLine绘制线,Polygon和PolyPolygon绘制面 
4. 调用CDC::SelectObject恢复绘图前的设定 
5. 释放资源,其中就有DC的释放,CWnd::ReleaseDC(CDC* pDC),将最开始获取的DC指针释放

 

2.CDC与HDC的区别

CDC是MFC的DC的一个类 
HDC是DC的句柄,API中的一个类似指针的数据类型. 
MFC类的前缀都是C开头的 
H开头的大多数是句柄 
CDC是所有MFC中的DC的基类.常用的CClientDC dc(this);就是CDC的子类(或称派生类). 
CDC等设备上下分类,都含有一个类的成员变量:m_nHdc;即HDC类型的句柄. 
 
3.CDC对象提供处理显示器或打印机等设备上下文的成员函数,以及处理与窗口客户区对应的显示上下文的成员。
 
4.有关CDC类的继承  
        父类:从 CObject 直接继承而来。继承了CObject类的各种特性,如动态创建等等。
  子类:CClientDC-------代表操作窗口的DC ,是比较常用的一个子类
  CMetaFileDC ------响应Meta File的DC ,Meta File是一些GDI消息。
  CPaintDC-------响应WM_PAINT消息的DC。
  CWindowDC ------代表整个屏幕的DC
 
5.内存DC
在windows下搞图形界面的设计难免要使用到内存DC,将所有的绘制工作先绘制在内存DC上,然活一次性拷贝到屏幕DC上,就是这样了。可以消除一些图形的闪烁问题,当然还有其他的用处,比如简单游戏中的象素碰撞检测等等
5.1创建内存DC
    HDC hdc = ::GetDC(NULL);  
 CDC   *pDC = CDC::FromHandle(hdc);   //创建兼容DC(屏幕)
 CDC imageMemDC;
 imageMemDC.CreateCompatibleDC(pDC);
 
6.OnPaint与OnEraseBkgnd()
在MFC中 任何一個window元件的繪圖 都是放在這兩個member function中
在設定上 OnEraseBkgnd()是用來畫底圖的 而OnPaint()是用來畫主要物件的
舉例說明 一個按鈕是灰色的 上面還有文字
則OnEraseBkgnd()所做的事就是把按鈕畫成灰色
而OnPaint()所做的事 就是畫上文字


既然這兩個member function都是用來畫出元件的
那為何還要分OnPaint() 與 OnEraseBkgnd() 呢
其實OnPaint() 與 OnEraseBkgnd() 特性是有差的
1. OnEraseBkgnd()的要求是快速在裡面的繪圖程式最好是不要太耗時間
因為每當window元件有任何小變動都會馬上呼叫OnEraseBkgnd()
2. OnPaint() 是只有在程式有空閒的時候才會被呼叫
3. OnEraseBkgnd() 是在 OnPaint() 之前呼叫的
所以 OnPaint()被呼叫一次之前 可能會呼叫OnEraseBkgnd()好幾次
 
7.Onpanit与OnEraseBkgnd()
1. 系统的Onpaint中调用了OnDraw,但如果我们自己继承了一个OnPaint函数又没有显式调用OnDraw,则OnDraw就不会被调用
2.OnPaint()是CWnd的类成员,负责响应WM_PAINT消息。OnDraw()是CVIEW的成员函数,没有响应消息的功能.当视图变得无效时(包括大小的改变,移动,被遮盖等等),Windows发送WM_PAINT消息。该视图的OnPaint 处理函数通过创建CPaintDC类的DC对象来响应该消息并调用视图的OnDraw成员函数.OnPaint最后也要调用OnDraw,因此一般在OnDraw函数中进行绘制。
3.
 
8.OnCtlColor 返回HBRUSH句柄,用该句柄来绘制窗口背景色,
例如
HBRUSH CtestOnPaintDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
 HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
//m_brBk.CreateSolidBrush(RGB(113 , 111 , 100)); //创建刷子,在初始化或者其他函数中调用
    
 return (HBRUSH)m_brBk.GetSafeHandle();
 return hbr;
}
 
9.资源释放
在windows系列上做编程,gdi是一个很重要的技术点,有很多程序在运行多次后出现异常,除了众所周知的内存泄露以外,gdi资源泄露也是一个很直接的原因.今天就把我自己在编程中总结的一些经验给大家分享,欢迎高手补充.
1.Create出来的gdi对象,一定要用DeleteObject来释放,释放顺序是先Create的后释放,后Create的先释放.
这里的Create指的是以它为开头的gdi函数,比如,CreateDIBitmap,CreateFont等等,最后都要调用DeleteObject来释放.

2.Create出来的dc要用DeleteDC来释放,Get到的要用ReleaseDC释放.

3.确保释放DC的时候DC中的各gdi对象都不是你自己创建的;确保个gdi对象在释放的时候不被任何dc选中使用.
假如我们要使用gdi函数画图,正确的步骤应该如下:
a.创建一个内存兼容dc(CreateCompatibleDC)
b.创建一个内存兼容bitmap(CreateCompatibleBitmap)
c.关联创建的内存兼容dc和bitmap(SelectObject)
d.画图
e.BitBlt到目的dc上
f.断开内存兼容dc和bitmap关联(SelectObject)
g.销毁内存兼容bitmap
h.销毁内存兼容dc
由于SelectObject在选入一个新的gdi对象的时候会返回一个原来的gdi对象(假如成功的话),所以需要在步骤c的时候保存返回值,在步骤f的时候当作入口参数使用.还有,步骤g和步骤h实际上顺序可以随意,因为他们两个此刻已经没有关系了,但是为了结构清晰,我建议按照"先Create的后释放,后Create的先释放"的原则进行.
关于步骤f,可能会有争议,因为即使省略这一步,步骤g和步骤h看起来照样可以返回一个成功的值.但实际上可能并没有执行成功,至少boundschecker会报告有错,错误信息大致是说,在释放dc的时候还包含有非默认的gdi对象,在释放gdi对象的时候又说这个gdi对象还被一个dc在使用.所以,我建议保留步骤f.
 
 
案例:
 HWND playWnd = GetPlayWnd();
 HDC hdc = ::GetDC(playWnd);
 CDC *playDC = CDC::FromHandle(hdc);
::ReleaseDC(playwnd,hDC);  //释放
CDC *playDC = CDC::FromHandle(hdc); 要不要释放呢?  不是 "可以不释放 ",而是 "不可以释放 ".   FromHandle   是通过   HDC   来创建了一个   CDC   对象,以方便操作,释放   DC   的操作应该针对于   HDC   而非此   CDC   ,   如果释放了它   pDC-> ReleaseDC   ,就会造成隐患. 只能释放一次.
 
8.  HBITMAP使用心得
HBITMAP  m_hBitmap = (HBITMAP)::LoadImage(NULL, "D:\\fdf.bmp", IMAGE_BITMAP, 0, 0, 
   LR_DEFAULTSIZE | LR_CREATEDIBSECTION | LR_LOADFROMFILE);
HBITMAP在初始化后就 保留图片数据,即便图片被删除,m_hBitmap里的数据也不会丢失,指导它被删除前.
 
 
9.关于CDialog::OnPaint。

如果自己的OnPaint代码什么也没做的话(至少没有声明过CPaintDC类型的变量),还必须调用一下CDialog::OnPaint,否则BeginPaint和EndPaint就没有办法被调用了。

总之,在响应WM_PAINT消息的时候,必须调用一遍BeginPaint和EndPaint。调用的方法有三种:
1、声明一个CPaintDC类型的变量(即使你什么也不画),CPaintDC的构造函数就是调用BeginPaint,析构函数就是调用EndPaint。
2、调用基类的OnPaint(实际上就是调用API的DefWindowProc,它会自动调用BeginPaint和EndPaint)。
3、自己直接调用BeginPaint和EndPaint。
上述三种方法,必须选择其一,而且也只能选择其一(因为在一个WM_PAINT消息内不能调用两次BeginPaint和EndPaint)。
原创粉丝点击