本文英文原版见http://www.codeproject.com/Articles/1988/Guide-to-WIN-Paint-for-Beginners
1.介绍
略
2.Device Context设备上下文
浅显的说,DC是用来绘制的画布。同时,DC也是操作系统的资源,用来在显示器上绘制图形的画笔、画刷和字体等等均属于DC范畴。DC是Windows系统图形设备接口的抽象层,对于开发者而言,它屏蔽了显示设备的具体实现细节。通过使用并初始化不同的DC,可以使用相同的代码和方法在屏幕、打印机、甚至内存位图上进行绘制。相比于直接操作视频卡、打印机驱动而言,这种方式对于开发者而言更加简洁高效。
DC分为三大类:
- Client DC 客户区DC:一个特定的窗口拥有一个客户区DC,对于开发者而言对于目标窗口具有对客户区DC的访问权限。客户区DC对于开发者而言也是使用最为频繁和重要的DC。当产生WM_PAINT消息时,客户区DC就可以获得;
- Window DC 窗口DC:一个特定的窗口拥有一个窗口DC,该DC允许开发者绘制窗口的任何部分,包括窗口边框及标题。当WM_NCPAINT消息产生时,窗口DC就可以获得;
- Memory DC 内存DC:该DC只存在于内存中,不和窗口绑定。该DC也是唯一一种能操作用户定义位图的DC。内存DC在缓存图像、后台缓冲、复杂图形绘制上极为有效;
- General Device DC 一般设备DC:绘图仪、打印机、监视器采用该DC。
3.Obtaining a Client Device Context获得客户区设备上下文
在使用Win32 Paint API时,经常需要获取一个DC句柄。任何一种类型的DC关于其描述均存储在一个HDC中。
对于MFC框架或WTL框架而言,DC的基类是CDC,CDC持有HDC。CPaintDC和CClientDC则继承自CDC类,它们具有各自特殊的用途。
3.1 BeginPaint获取Client DC
BeginPaint是一种普遍的创建DC的方式,但是该函数只能在WM_PAINT消息句柄中使用!这是因为BeginPaint会使窗口无效或过期的区域有效,当某一窗口存在无效区域时,WM_PAINT消息会产生通知窗口重绘此无效区域。如果在WM_PAINT句柄函数之外调用BeginPaint,任何无效的窗口去将变得有效,此时将不会有WM_PAINT消息产生!对于像subclass控件的人而言,存在一些副作用。
此外,当调用BeginPaint时,如果有必要的话Windows会产生WM_ERASEBKGND和WM_NCPAINT消息。如果在WM_PAINT之外调用BeginPaint,你地窗口边框可能不会正常更新。
调用BeginPaint的示例:
PAINTSTRUCT ps;
HDC hdc;
hdc = ::BeginPaint(hWnd, &ps);
释放由BeginPaint获得的Client DC的函数是EndPaint。BeginPaint将调用HideCursor将鼠标指针隐藏,EndPaint在结束绘制后将鼠标指针再设成可见。如果在调用BeginPaint后不调用EndPaint将会隐藏鼠标指针。
调用EndPaint的示例:
::EndPaint(hWnd, &ps);
对于MFC和WTL框架而言,Client DC的获取和释放是在CPaintDC类的构造和析构函数中自动完成的。在栈上创建一个CPaintDC对象的实例,该DC也会自动销毁。CPaintDC的构造和析构函数代码如下。
CPaintDC::CPaintDC(CWnd* pWnd)
{
...
if (!Attach(::BeginPaint(m_hWnd = pWnd->m_hWnd, &m_ps)))
AfxThrowResourceException();
}
CPaintDC::~CPaintDC()
{
...
::EndPaint(m_hWnd, &m_ps);
Detach();
}
3.2 GetDC/GetDCEx获取Client DC
另外一种获取客户区DC的方式是通过调用GetDC/GetDCEx。GetDCEx还允许设置一个裁剪区域,这样GetDCEx将不会绘制这些裁剪区域。
GetDC在WM_PAINT句柄函数之外使用很频繁。当需要绘制一些图形时,可以通过GetDC获取DC,这些图形往往不是目标窗口所具有的永久性的图形。比如,在Zoom in和Zoom out操作时用鼠标拉出的像橡皮筋一样的选择区域框,还比如选择一个窗口中的多个对象时的效果,这些图形或效果并不是永久存在的。
通过调用GetDC获得DC和释放DC的一个示例:
HDC hdc;
hdc = GetDC(hWnd);
...
::ReleaseDC(hWnd, hdc);
GetDC和ReleaseDC封装在MFC和WTL框架的CClientDC类中。
CClientDC::CClientDC(CWnd* pWnd)
{
...
if (!Attach(::GetDC(m_hWnd = pWnd->GetSafeHwnd())))
AfxThrowResourceException();
}
CClientDC::~CClientDC()
{
...
::ReleaseDC(m_hWnd, Detach());
}
4.使用DC
使用DC非常方便,下面是采用不同的GDI函数使用DC 的示例:
Rectangle(hdc, 100, 100, 200, 300);
Ellipse(hdc, 100, 100, 200, 300);
TCHAR szMessage[] = "Paint Beginner";
UINT nLen = _tcslen(szMessage);
TextOut(hdc, 100, 325, szMessage, nLen);
对于CPaintDC而言:
CPaintDC dc;
dc.Rectangle(10, 10, 150, 200);
对于ClientDC而言:
CClientDC dc;
dc.Rectangle(10, 10, 150, 200);
5.例程
该例子程序使用了两种获取DC的方式
1.BeginPaint:用户在WM_PAINT消息句柄中绘制客户区
2.GetDC:用于绘制鼠标按下左键拖动时的橡皮筋效果
在绘制橡皮筋效果时,采用了DC模式的概念,将DC模式设置为R2_NOT后,第二次相同的绘制操作将会完全清除第一次的绘制操作,从而消除第一次的绘制!
在绘制橡皮筋效果时,采用了DC模式的概念,将DC模式设置为R2_NOT后,第二次相同的绘制操作将会完全清除第一次的绘制操作,从而消除第一次的绘制!
void DrawRubberBand(HWND hWnd)
{
HDC hdc;
//C: Get a client DC.
hdc = ::GetDC(hWnd);
//C: Set the current drawing mode to XOR, this will allow us
// to add the rubber band, and later remove it by sending the
// exact same drawing command.
::SetROP2(hdc, R2_NOT);
//C: Select a NULL Brush into the DC so that no fill is performed.
::SelectObject(hdc, ::GetStockObject(NULL_BRUSH));
//C: Get the current shape mode.
HMENU hMenu = ::GetMenu(hWnd);
HMENU hShapeMenu = ::GetSubMenu(hMenu, 1);
if (::GetMenuState(hShapeMenu, ID_SHAPE_RECTANGLE, MF_BYCOMMAND) & MF_CHECKED)
{
::Rectangle(
hdc,
ptStart.x,
ptStart.y,
ptCurrent.x,
ptCurrent.y
);
}
else
{
::Ellipse(
hdc,
ptStart.x,
ptStart.y,
ptCurrent.x,
ptCurrent.y
);
}
//C: Release the DC.
::ReleaseDC(hWnd, hdc);
}
具体可研究一下代码。