|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionRecently there have been a number of questions about drawing techniques. Almost all of these were using code far more complex than is required. This essay tells about how I do drawing in Windows. Being lazy, I wish to do it with as little effort as possible. I've developed some effective techniques over the years. Tool or Tool *?One of the most frequent misconceptions I've seen is that you have to allocate a drawing object in order to use it. So I see code of the form CPen * myPen = new CPen; CPen->Create(...); CPen * OldPen = dc->SelectObject(myPen); ... delete myPen; This is unnecessarily complex code. At least part of the confusion is that the CPen myPen(...); CPen * OldPen = dc->SelectObject(&myPen); ... dc->SelectObject(OldPen); This is much simpler code; it doesn't call the allocator. And the parameter When Allocation is NecessaryThe only time you need to keep objects around is if you need to return them to a context outside your own. For example, the following will not work properly: HBRUSH MyWnd::OnCtlColor(...)
{
CBrush MyBackground(RGB(255, 0, 0));
return (HBRUSH)MyBackground;
}
This does not work because upon exiting the context in which the brush was created, the HBRUSH MyWnd::OnCtlColor(...)
{
CBrush * MyBackground = new CBrush(RGB(255, 0, 0));
return (HBRUSH)*MyBackground;
// or return (HBRUSH)MyBackground->m_hObject;
}
This is erroneous because a brush is allocated each time the routine is called, and is never deleted. Not only do you clutter up your application space with a lot of unreclaimed In cases like this, you must add a member variable to your class, the background brush, e.g., CBrush * MyBackground; initialize it in the constructor, and delete it in the destructor: MyWnd::MyWnd()
{
...
MyBackground = new CBrush(RGB(255,0,0));
}
MyWnd::~MyWnd()
{
...
delete MyBackground;
}
When Deletion Won't Workhe implicit deletion of objects in the destructor doesn't work if the object is selected into an active DC when it goes out of scope. The void OnDraw(CDC * pDC) { CPen RedPen(PS_SOLID, 0, RGB(255, 0, 0)); ... pDC->SelectObject(&RedPen); ... } At the time we leave scope, the The correct solution to this is to be certain that none of your GDI objects are selected into the DC when the destructors are called. The usual way is to save the old object. This means you have to remember to save it, and remember to restore it, and you don't need to save any but the original, for example, {
CPen RedPen(PS_SOLID, 0, RGB(255, 0, 0));
CPen GreenPen(PS_SOLID, 0, RGB(0, 255, 0));
CPen BluePen(PS_SOLID, 0, RGB(0, 0, 255)):
CPen * OldPen = dc->SelectObject(&RedPen);
...
dc->SelectObject(&GreenPen);
...
dc->SelectObject(&BluePen);
...
dc->SelectObject(OldPen);
}
Note that only the original pen needs to be restored. But what if there were a loop? You'd need to store the original pen the first time, or always restore it at the end of the loop so the next iteration was correct, etc. And what if you needed to change pen, brush, ROP, fill mode, etc., etc. Very tedious. And what if you decided to change pens earlier in the code? The hazards of compromising what I call "robustness under maintenance" are considerable. The simplest way to handle this is to use {
CPen RedPen(PS_SOLID, 0, RGB(255, 0, 0));
CPen GreenPen(PS_SOLID, 0, RGB(0, 255, 0));
CPen BluePen(PS_SOLID, 0, RGB(0, 0, 255)):
int saved = dc->SaveDC();
dc->SelectObject(&RedPen);
...
dc->SelectObject(&GreenPen);
...
dc->SelectObject(&BluePen);
...
dc->RestoreDC(saved);
}
Note that there is no reason to maintain a bunch of variables whose sole purpose is to restore the DC to what it was, and remember which ones, and how to manage them, and a lot of other needless complexity. Just do a
{
CPen RedPen(PS_SOLID, 0, RGB(255, 0, 0));
CPen GreenPen(PS_SOLID, 0, RGB(0, 255, 0));
CPen BluePen(PS_SOLID, 0, RGB(0, 0, 255)):
int saved = dc->SaveDC();
dc->SelectObject(&RedPen);
...
dc->SelectObject(&GreenPen);
int saved2 = dc->SaveDC();
for(int i = 0; i < something; i++)
{
dc->SelectObject(...);
...
dc->SelectObject(...);
}
dc->RestoreDC(saved2);
...
dc->SelectObject(&BluePen);
...
dc->RestoreDC(saved);
}
The only requirement is that any GDI objects you create must be at the same or enclosing scope of the int saved2 = dc->SaveDC(); for(int i = 0; i < something; i++) { CBrush br(RGB(i, i, i)); dc->SelectObject(&br); ... dc->SelectObject(&GreenPen); } dc->RestoreDC(saved2); because when the destructor is called for int saved2 = dc->SaveDC(); for(int i = 0; i < something; i++) { CBrush br(RGB(i, i, i)); CBrush * oldBrush = dc->SelectObject(&br); ... dc->SelectObject(&GreenPen); dc->SelectObject(oldBrush); } dc->RestoreDC(saved2); int saved2 = dc->SaveDC(); for(int i = 0; i < something; i++) { int save = dc->SaveDC(); CBrush br(RGB(i, i, i)); dc->SelectObject(&br); ... dc->SelectObject(&GreenPen); dc->RestoreDC(save); } dc->RestoreDC(saved2); I have not done any performance measurement, so I don't know if There are some interesting pieces of code in the GUI for my Hook DLL; it draws a cute picture of a cat. You may find some interesting ideas reading this code as well. The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft. Send mail to newcomer@flounder.com with questions or comments about this article.
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| You must Sign In to use this message board. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|