分享

今天在学习用内存DC画图,终于有了初步了解。现将收集的关于内存DC介绍及其相关操作的资料贴出来共享一下。

 行走在理想边缘 2014-08-13

DC 即Device Context,是GDI内部的一个资料结构,一个DC会和某个特定的显示设备(如打印机、屏幕等)产生关联。我们如果能取得该DC的handle 那我们便可以在这显示设备上写字、画图。
在Form 或Picturebox中都有一个hdc的属性,指的便是这东西,但是,怎么又会有一个Memory DC呢?这是一个存在记忆体内的 dc ,它除了不像form picturebox能将图形、文字显示出来之外,其他的几乎都相同,它也可以用在所有的 GDI API 呼叫之上,其实我们在VB中早就有使用上这 Memory DC 了,只是没有自觉。当我们设 form picturebox的AutoRedraw = True时, hdc所指的便是Momoory DC,这时我们在其上作绘图动作,都没有显示在form上,这便是先前说的,它只是在记忆体中,不会真的画出图。而我们下 refresh指令时,便是将这MemoryDC上的图,copy到 form/PictureBox上。
  另外我们也可以使用CreateCompatibleDC() API 它传入一个 hDc ,代表产生的 Memory DC和 hdc相容,若传0则是与屏幕相容的 Memory DC hMemDC = CreateCompatibleDC(0)
  这时候,该hMemDC所指的绘图区有多大呢?其实只有一个单色Pixel,直到我们使用SelectObject( hMemDC, hBitmap)
  那hMemDC显示区就会有和hBitmap一样的宽度、高度、颜色选择等。 而且我们在hMemDC上的任何绘图,也都会反映在 hBitMap上,也就是说,原本hBitMap所指的图,在SelectObject(hMemDC, hBitMap)后,我们使用gdi函式在hMemDC上画一条线,那么该hBitmap所指的图也会有一条线了。
那么Memory DC又有什么作用呢?
我们知道, 在使用VC开发图形相关的应用程序时,常常需要使用MFC的CDC类直接把图形画在窗口上。这通常是通过响应Windows的WM_PAINT消息实现的。如果要画的图形比较复杂,或者比较大,那么画图过程可能会造成窗口的闪烁。当窗口调整大小时,这种闪烁由为明显。
解决窗口闪烁问题的有效办法就是使用内存DC,也称为缓冲DC。在内存中准备一个和窗口DC相同属性的DC,在这个内存DC上执行画图操作。完成画图以后,把画图输出的内容整体复制到目标窗口DC上。因为画图操作不在窗口DC上进行,所以在画图的过程中窗口可以保持原来的内容。当画好的内容被复制到窗口DC时,因为复制操作执行的非常快,所以用户感觉窗口仿佛被立刻被画好,从而消除了从旧画面到白板再到新画面的闪烁现象。
生成内存DC主要用到以下四个函数:
CreateCompatibleDC(CDC* pDC )。CDC类的成员函数,用于创建一个和pDC指向的DC兼容的内存DC。
CreateDiscardableBitmap( CDC* pDC, int nWidth, int nHeight)。CBitmap类的成员函数,用于按指定尺寸创建一个和pDC指向的DC兼容的位图。    
SelectObject(CBitmap * pBitmap)。CDC类的成员函数,执行以后,所有在该DC上的图像输出都将被画到pBitmap指向的位图上。
BOOL BitBlt (int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop )。CDC类的成员函数,用于从源DC(pSrcDC)复制一个矩形的图象到当前DC中。
 好了,目前我所能理解到的原理就是上面的样子,下面来看看具体应该怎样操作吧。
对于一个窗口,我们可以用下面的代码来创建内存DC,在内存DC上输出,并最终复制到窗口DC上。
C/C++ code?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void PaintWnd(CWnd * pWnd)
{
     CDC * pWndDC = pWnd->GetWindowDC();
     CRect WndRect = pWnd->GetWindowRect();
     CDC MemDC;
     CBitMap MemBitmap; 
     MemDC.CreateCompatibleDC(pWndDC);  // 创建内存DC
     MemBitmap.CreateCompatibleBitmap(  // 创建兼容的位图
         pWndDC,
         WndRect.Width(),
         WndRect.Height());
  MemDC.SelectObject(MemBitmap);  // 让内存DC输出到位图(我的理解就是选择画布)
     // 使用MemDC画图
     ……
    pWndDC->BitBlt(// 从内存DC复制到窗口DC
         0,0,
         WndRect.Width(),
         WndRect.Height(),
         &MemDC, 
         0,0,
         SRCCOPY);
}

当然,实际的情况下,我们需要考虑的更多,因为内存DC、位图的创建都可能会失败。为了简化代码,此处定义了一个类CMemoryDC,包装了内存DC创建过程中的出错处理,内存DC的事后清理等操作,并自动复制内存DC的内容到目标DC上。
声明CMemoryDC类的头文件MemoryDC.h如下:
C/C++ code?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#pragma once
 #include "Afxwin.h"
   
 class CMemoryDC
 {
 public:
      CMemoryDC(CDC *dc, RECT * rect,bool autoRender = false);
      ~CMemoryDC(void);
   
      bool IsOK();
      void Render(CDC * p_objectDC = NULL);
      CDC* GetMemoryDC();
      operator CDC * ();
 private:
     bool m_bAutoRender;
  CRect m_DCRect;
   CDC* m_pOriginalDC;
     CDC m_MemoryDC;
     CBitmap m_MemoryBmp;
};

类的实现文件CMemoryDC.cpp如下:
C/C++ code?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
 #include ".MemoryDC.h"
   
 CMemoryDC::CMemoryDC(CDC *dc, RECT * rect, bool autoRender)
  {    
      m_bAutoRender = autoRender;
      m_pOriginalDC = dc;
      if (dc==NULL || rect==NULL)
          return;
      if (!m_MemoryDC.CreateCompatibleDC(dc))
          return;
      m_DCRect.SetRect(rect->left, rect->top, rect->right, rect->bottom);   
      if (!m_MemoryBmp.CreateCompatibleBitmap(dc, m_DCRect.Width(), m_DCRect.Height()))
          return;
      m_MemoryDC.SelectObject(m_MemoryBmp);
 }
   
 CMemoryDC::~CMemoryDC(void)
  {
      if (m_bAutoRender)
          Render();
      if (m_MemoryDC.m_hDC!=NULL)
          m_MemoryDC.DeleteDC();
      if (m_MemoryBmp.m_hObject!=NULL)
          m_MemoryBmp.DeleteObject();
 }
   
 bool CMemoryDC::IsOK()
  {
      return m_MemoryDC.m_hDC!=NULL && m_MemoryBmp.m_hObject != NULL;
   
 }
 void CMemoryDC::Render(CDC * p_objectDC)
  {
      if (!IsOK())
          return;
   
      CDC * pDC = (p_objectDC==NULL ? m_pOriginalDC : p_objectDC);
      CSize Size = m_MemoryDC.GetViewportExt() ;
      pDC->BitBlt(
          m_DCRect.left, 
          m_DCRect.top,
          m_DCRect.Width(),
          m_DCRect.Height(),
          &m_MemoryDC, 
          0,0,
          SRCCOPY);
 }
 CDC* CMemoryDC::GetMemoryDC()
  {
      return & m_MemoryDC;
 }
 CMemoryDC::operator CDC * ()
  {
      return & m_MemoryDC;
}

使用这个类可以大大简化内存DC的创建操作。如果我们在窗口消息WM_PAINT的响应函数中使用内存DC,只要用如下这样简便的代码便可实现:
 
C/C++ code?
1
2
3
4
5
6
7
8
9
CRect Rect;
 GetClientRect(Rect);
 CPaintDC dc(this); // device context for painting
      CMemoryDC MemDC(&dc, Rect, true);
      if (MemDC.IsOK())
       {
          // 使用MemDC画窗口
       }    
 // MemDC析构时会自动把图像复制到dc,无需其它操作
使用CMemoryDC创建内存DC防止窗口闪烁,编程的代码和不使用内存DC时相比,数量和复杂性几乎没有增加。


另外,关于此文章的word文档我也上传了,需要的朋友可去下载。http://download.csdn.net/source/1381542

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多