前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >MFC Windows 程序设计->取消对GDI对象的选定

MFC Windows 程序设计->取消对GDI对象的选定

作者头像
井九
发布2024-10-12 08:38:28
发布2024-10-12 08:38:28
780
举报
文章被收录于专栏:四楼没电梯四楼没电梯

It's important to delete the GDI objects you create, but it's equally important to never delete a GDI object while it's selected into a device context. Code that attempts to paint with a deleted object is buggy code. The only reason it doesn't crash is that the Windows GDI is sprinkled with error-checking code to prevent such crashes from occurring.

Abiding by this rule isn't as easy as it sounds. The following OnPaint handler allows a brush to be deleted while it's selected into a device context. Can you figure out why?

void CMainWindow::OnPaint () { CPaintDC dc (this); CBrush brush (RGB (255, 0, 0)); dc.SelectObject (&brush); dc.Ellipse (0, 0, 200, 100); }

Here's the problem. A CPaintDC object and a CBrush object are created on the stack. Since the CBrush is created second, its destructor gets called first. Consequently, the associated GDI brush is deleted before dc goes out of scope. You could fix this by creating the brush first and the DC second, but code whose robustness relies on stack variables being created in a particular order is bad code indeed. As far as maintainability goes, it's a nightmare.

The solution is to select the CBrush out of the device context before the CPaintDC object goes out of scope. There is no UnselectObject function, but you can select an object out of a device context by selecting in another object. Most Windows programmers make it a practice to save the pointer returned by the first call to SelectObject for each object type and then use that pointer to reselect the default object. An equally viable approach is to select stock objects into the device context to replace the objects that are currently selected in. The first of these two methods is illustrated by the following code:

CPen pen (PS_SOLID, 1, RGB (255, 0, 0)); CPen* pOldPen = dc.SelectObject (&pen); CBrush brush (RGB (0, 0, 255)); CBrush* pOldBrush = dc.SelectObject (&brush); dc.SelectObject (pOldPen); dc.SelectObject (pOldBrush);

The second method works like this:

CPen pen (PS_SOLID, 1, RGB (255, 0, 0)); dc.SelectObject (&pen); CBrush brush (RGB (0, 0, 255)); dc.SelectObject (&brush); dc.SelectStockObject (BLACK_PEN); dc.SelectStockObject (WHITE_BRUSH);

The big question is why this is necessary. The simple truth is that it's not. In modern versions of Windows, there's no harm in allowing a GDI object to be deleted a split second before a device context is released, especially if you're absolutely sure that no drawing will be done in the interim. Still, cleaning up a device context by deselecting the GDI objects you selected in is a common practice in Windows programming. It's also considered good form, so it's something I'll do throughout this book.

Incidentally, GDI objects are occasionally created on the heap, like this:

CPen* pPen = new CPen (PS_SOLID, 1, RGB (255, 0, 0)); CPen* pOldPen = dc.SelectObject (pPen);

At some point, the pen must be selected out of the device context and deleted. The code to do it might look like this:

dc.SelectObject (pOldPen); delete pPen;

Since the SelectObject function returns a pointer to the object selected out of the device context, it might be tempting to try to deselect the pen and delete it in one step:

delete dc.SelectObject (pOldPen);

But don't do this. It works fine with pens, but it might not work with brushes. Why? Because if you create two identical CBrushes, 32-bit Windows conserves memory by creating just one GDI brush and you'll wind up with two CBrush pointers that reference the same HBRUSH. (An HBRUSH is a handle that uniquely identifies a GDI brush, just as an HWND identifies a window and an HDC identifies a device context. A CBrush wraps an HBRUSH and stores the HBRUSH handle in its m_hObject data member.) Because CDC::SelectObject uses an internal table maintained by MFC to convert the HBRUSH handle returned by SelectObject to a CBrush pointer and because that table assumes a one-to-one mapping between HBRUSHes and CBrushes, the CBrush pointer you get back might not match the CBrush pointer returned by new. Be sure you pass delete the pointer returned by new. Then both the GDI object and the C++ object will be properly destroyed. 

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-10-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档