一、CPaintDC的使用及各种DC的使用
摘自
在c++ 编程中常会见到HDC,CDC,CClientDC,CPaintDC,CWindowDC这样的类。
HDC是DC的句柄,API中的一个类似指针的数据类型。
CDC是MFC的DC的一个类。
CDC等设备上下分类,都含有一个类的成员变量:m_nHdc;即HDC类型的句柄。
CDC及其派生类的继承视图:
CObject
public |------CDC
public |------|------CClientDC
public |------|------CPaintDC
public |------|------CWindowDC
public |------|------CMetaFileDC
(注意: 除CMetaFileDC以外的三个派生类用于图形绘制.)
1.CPaintDC:封装BeginPaint和EndPaint两个API的调用。
(1)用于响应窗口重绘消息(WM_PAINT)的绘图输出。
(2)CPaintDC在构造函数中调用BeginPaint()取得设备上下文,在析构函数中调用EndPaint()释放设备上下文。 EndPaint()除了释放设备上下文外,还负责从消息队列中清除WM_PAINT消息。因此,在处理窗口重画时,必须使用CPaintDC,否则 WM_PAINT消息无法从消息队列中清除,将引起不断的窗口重画。
(3)CPaintDC也只能用在WM_PAINT消息处理之中。
2.CClientDC(客户区设备上下文): 处理显示器描述表的相关的窗体客户区域。
构造时自动调用GetDC函数,析构时自动调用ReleaseDC函数.一般应用于客户区窗口的绘制。
当需要处理一个鼠标的单击,然后马上画出一个圆,你不能等到下一个WM_PAINT的消息到来才画图,而是马上,这是就需要CclientDC了。它可以在OnPaint的外面创建一个客户区域DC。
void CMainWindow::OnLButtonDown (UINT nFlags, CPoint point)
{
CRect rect;
GetClientRect (&rect);
CClientDC dc (this);
dc.MoveTo (rect.left, rect.top);
dc.LineTo (rect.right, rect.bottom);
dc.MoveTo (rect.right, rect.top);
dc.LineTo (rect.left, rect.bottom);
}
3.CWindowDC: 处理显示器描述表相关的整个窗体区域,包括了框架和控件(子窗体)。
(1)可在非客户区绘制图形,而CClientDC,CPaintDC只能在客户区绘制图形。
(2)坐标原点是在屏幕的左上角,CClientDC,CPaintDC下坐标原点是在客户区的左上角。
(3)关联一特定窗口,允许开发者在目标窗口的任何一部分进行绘图,包含边界与标题,这种DC同WM_NCPAINT消息一起发送。
4.CMetaFileDC:与元文件相关的设备描述表关联。
二、为什么你的窗口不能立即重绘
关于 WM_PAINT事件
系统会在多个不同的时机发送WM_PAINT消息:当第一次创建一个窗口时,当改变窗口的大小时,当把窗口从另一个窗口背后移出时,当最大化或最小化窗口时,等等,这些动作都是由系统管理的,应用只是被动地接收该消息,在消息处理函数中进行绘制操作;大多数的时候应用也需要能够主动引发窗口中的绘制操作,比如当窗口显示的数据改变的时候,这一般是通过InvalidateRect和InvalidateRgn函数来完成的。InvalidateRect和 InvalidateRgn把指定的区域加到窗口的UpdateRegion中,当应用的消息队列没有其他消息时,如果窗口的Update Region不为空时,系统就会自动产生WM_PAINT消息。
系统为什么不在调用Invalidate时发送WM_PAINT消息呢?又为什么非要等应用消息队列为空时才发送WM_PAINT消息呢?这是因为系统把在窗口中的绘制操作当作一种低优先级的操作,于是尽可能地推后做。不过这样也有利于提高绘制的效率:两个WM_PAINT消息之间通过 InvalidateRect和InvaliateRgn使之失效的区域就会被累加起来,然后在一个WM_PAINT消息中一次得到更新,不仅能避免多次重复地更新同一区域,也优化了应用的更新操作。像这种通过InvalidateRect和InvalidateRgn来使窗口区域无效,依赖于系统在合适的时机发送WM_PAINT消息的机制实际上是一种异步工作方式,也就是说,在无效化窗口区域和发送WM_PAINT消息之间是有延迟的;有时候这种延迟并不是我们希望的,这时我们当然可以在无效化窗口区域后利用SendMessage发送一条WM_PAINT消息来强制立即重画,但不如使用Windows GDI为我们提供的更方便和强大的函数:UpdateWindow和RedrawWindow。UpdateWindow会检查窗口的Update Region,当其不为空时才发送WM_PAINT消息;RedrawWindow则给我们更多的控制:是否重画非客户区和背景,是否总是发送 WM_PAINT消息而不管Update Region是否为空等。
三、OnPaint()的使用:我从打开一个bmp文件导入一个图片在窗口显示
先了解下CBitmap,HBitmap,Bitmap区别及联系
(1)区别
HBITMAP是bitmap的指针:
msdn中如是:Handle to a bitmap.typedef HANDLE HBITMAP;
CBitmap是mfc中封装bitmap的类:
msdn中:Encapsulates(囊括) a Windows graphics device interface (GDI) bitmap and provides member functions to manipulate(操作) the bitmap.
BITMAP是一个结构体,封装着bitmap的一些信息。定义了逻辑位图的高,宽,颜色格式和位值。
MSDN中如是:This structure defines the type, width, height, color format, and bit values of a bitmap.
(2)三者之间的关系转换:
HBITMAP hBitmap;
CBitmap bitmap;
BITMAP bm;
//下面是三者之间的联系:
bitmap.Attach(hBitmap);//由HBITMAP 得到关联的CBitmap
bitmap.GetBitmap(&bm); // 由CBitmap
BITMAP hBitmap=(HBITMAP)bitmap.GetSafeHandle();//由CBitmap得到相关的HBITMAP
(3)延伸理解下Attach/Detach:
attach是把一个C++对象与一个WINDOWS对象关联,直到用detach则把关联去掉。
如果attach了以后没有detach,则C++对象销毁的时候WINDOWS对象跟着一起销毁。attach了以后,C++对象的指针和WINDOWS对象的HWND会有一个映射关系,其作用相当于你直接用一个C++对象去Create一个WINDOWS对象,例如 CEdit edit; edit.create(…) 并且此映射是永久的,知道此对象销毁为止。
如果用类似GetDlgItem函数也可以返回一个指针,并可以强制转换。GetDlgItem会到映射表里找。有2种映射表,一中是永久的,一种是临时的。直接用C++对象创建的WINDOWS对象或者是通过attach的对象的映射关系都被放到永久表中,否则就在临时表中创建映射。所以GetDlgItem不推荐你保存返回的指针,因为你很难保证你的WINDOWS对象跟C++对象的关联是否放在永久表中。如果映射是放在临时表中,那么在空闲时间会被自动删除。 用attcah完全是为了方便用MFC类的成员函数去操纵WINDOWS对象。
代码
// CRotateBmpDlg 对话框
class CRotateBmpDlg : public CDialogEx
{
// 构造
public:
CRotateBmpDlg(CWnd* pParent = NULL); // 标准构造函数
virtual ~CRotateBmpDlg();
// 对话框数据
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_ROTATEBMP_DIALOG };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
//LPVOID lpSrcBits;
//LONG SrcHeight;
//LONG SrcWidth;
HBITMAP m_hBitmap;
CBitmap m_bitmap;
// 实现
protected:
HICON m_hIcon;
// 生成的消息映射函数
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnBnClickedButton1();
CString strFilePath;
};
初始化m_hBitmap;m_bitmap;strFilePath;
CRotateBmpDlg::CRotateBmpDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(IDD_ROTATEBMP_DIALOG, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
strFilePath = "";
m_hBitmap = NULL;
}
CRotateBmpDlg::~CRotateBmpDlg()
{
/*if (lpSrcBits)
{
delete lpSrcBits;
}*/
if (m_bitmap.m_hObject)
{
m_bitmap.Detach();
}
DeleteObject(&m_hBitmap);
DeleteObject(&m_bitmap);
}
void CRotateBmpDlg::OnBnClickedButton1()
{
CFileDialog dlgFileOpen(TRUE, NULL, _T("*.bmp"), OFN_HIDEREADONLY,
_T("打印文件(*.bmp)|*.bmp|所有文件(*.*)|*.*|"), this);
if (dlgFileOpen.DoModal() == IDCANCEL)
{
return;
}
strFilePath = dlgFileOpen.GetPathName();
CRect rect;
this->GetClientRect(rect);
m_hBitmap = (HBITMAP)LoadImage(NULL, strFilePath, IMAGE_BITMAP, rect.Width(), rect.Height() - 60, LR_LOADFROMFILE | LR_CREATEDIBSECTION);
Invalidate();//使窗口无效,产生WM_PAINT消息,WM_PAINT消息在消息队列的优先级低
//想让窗口立即重绘,可以sendmessage(WM_PAINT)或者
/*InvalidateRect(NULL, TRUE);*/
/*UpdateWindow();*/
}
void CRotateBmpDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
if (strFilePath.GetLength() > 4 && m_hBitmap)
{
if (m_bitmap.m_hObject)
{
m_bitmap.Detach();
}
m_bitmap.Attach(m_hBitmap);
CPaintDC dc(this);//构造会调用BeginPaint()和析构EndPaint(),并从消息队列中清除WM_PAINT消息。
CRect rect;
GetClientRect(rect);
CDC memDC;
memDC.CreateCompatibleDC(&dc);
BITMAP bmp;
m_bitmap.GetBitmap(&bmp);
CBitmap *pBmpOld = memDC.SelectObject(&m_bitmap);
/* int panelsize = 0;
if (bitmapinfo.bmBitsPixel < 16)
{
panelsize = pow(2, bitmapinfo.bmBitsPixel) * sizeof(RGBQUAD);
}*/
dc.StretchBlt(0, 0, bmp.bmWidth, bmp.bmHeight, &memDC, 0, 0, bmp.bmWidth, bmp.bmHeight, SRCCOPY);
memDC.SelectObject(pBmpOld);
DeleteDC(memDC);
}
else
{
CDialogEx::OnPaint();//进去OnPaint()会调用BeginPaint()和EndPaint(),并从消息队列中清除WM_PAINT消息。
}
}
}
OnPaint()进去了后一定要清除队列的WM_PAINT这样才不会重复响应,所以每个分支必须调用且只能调用一次CPaintDC()或者CDialogEx::OnPaint();