Attaching and Detaching Objects

Optimization and fonts

Optimization and Correctness

Back in the days of Win16, GDI resources were scarce and precious. They were carefully hoarded. Programmers went to a lot of extremes to avoid running out of GDI resources. This meant that if you ever created a font, you created it once and used it as often as possible, and deleted it when your program terminated.

This was a great optimization, and it was necessary then. It is not really a good idea in modern Win32 systems. The reason is that it violates abstraction. If I create a control that expects a special font, I should create that font for that control, and not require the programmer create it for me. The CFont object is therefore in the control subclass, not a global variable or a variable of the CDialog class or CWinApp class. If all instances of the control share the same font requirement, you can simplify things a bit by using a static class member to hold the CFont, although you will probably have to reference-count it to know when to delete it.

An optimization which renders a program incorrect, or potentially incorrect, is not an optimization. Beware! Because the consequence of some optimizations to "save space" actually can leak space. This Is Not Good

Changing Fonts in a Control

Changing a font in a control requires that you delete the existing font. This is why it is a Good Idea to have a font used in only one control, and not shared. The following code is correct, and does not leak fonts everywhere:

void CMyControl::ChangeFont()
    {
     CFont f;
     f.CreateFont(...);
     CFont * oldfont = GetFont();
     if(oldfont != NULL)

        oldfont->DeleteObject(); // Be Careful! See discussion on below!
     SetFont(&f);
     f.Detach();
    }

This has the advantage that it doesn't leak HFONTs each time the font is changed. It is left as an Exercise For The Reader to figure out how the parameters to CreateFont are specified; I find that I usually provide as parameters to my ChangeFont method the size, the face name, and a bold flag, and that encompasses 99% of what I do in changing fonts in controls. In generalizing the above example, you have to figure out what your needs are.

The GetFont returns a reference to a CFont object, unless there was no font associated with the control, in which case the result is NULL. But if the result is non-NULL, I delete the HFONT by calling DeleteObject. If the font was one of mine, it is now deleted (and if it was shared, the other users of the font are in trouble--but we'll get to that shortly). If the font was a stock font, the DeleteObject has no effect. The SetFont then sets the font, and the Detach, as I just explained, keeps the HFONT from following the CFont into the Great Bit Bucket In The Sky.

Whoa! What about that CFont * we got? Aren't we leaking something there? No, because the CFont * that was created by GetFont is a temporary MFC Object, which means that it is added to a list of garbage-collectable objects. The next time there is some idle time, the default OnIdle method goes around and cleans up all the temporary objects by deleting them. In fact, if you say "delete oldfont" you will eventually get an ASSERT failure from the temporary-object collector, which tells you that you've Done Something Bad to its data.

A caveat of the above code

There's a bug in the above code. It is not an apparent bug, but a reader of this essay found it and pointed it out to me. I need to be more explicit here. You should do DeleteObject only to fonts you have created. The situation was this: the reader did exactly what I showed, and complained that although his buttons how had the desired font, all the other buttons now had the wrong font. Whoops. This was my error. What happens in a dialog is that a font is created for the controls in the dialog, and a SetFont is done for each control as it is created.

 What had happened was that by following my advice, he managed to delete the dialog font, so all the other controls fell into the default case. The proper specification is that you should delete the old font only for fonts you have created, and not for the default font of a control. How can you tell the difference? Well, if you are in the OnInitDialog handler, you know you didn't create the font, so you shouldn't delete the existing font. I have not checked this out, but I believe the dialog will delete its copy of this font when it is closed.

 What if you are changing the font later, and don't know if you created the font the last time or not? Well, the obvious way is to keep some sort of Boolean around saying whether or not you changed the font. A typical case might be when you are using a CListBox as a logging control, and want the user to be able to change the font. This is needlessly complex. When I've had to do this (note that I already knew better when I wrote the example! What I did was do a GetFont for the existing control. Then I created a new font which was identical to it and set that font in the control. The code to do this is shown below:

     // Create a new font so we can change it later
     CFont * f = c_MyLogListBox.GetFont();
     CFont newfont;
     LOGFONT lf;
     if(f != NULL)
         { /* Set up duplicate font */
          f->GetObject(sizeof(LOGFONT), &lf);
          newfont.CreateFontIndirect(&lf);
         } /* Set up duplicate font */
     else
         { /* Use default font spec */
          newfont.CreateStockObject(ANSI_VAR_FONT);
         } /* Use default font spec */
     c_MyLogListBox.SetFont(newfont);
     newfont.Detach();

After you do this, you can safely use my previous example to delete the previous font because you know it is one that you created yourself.

Thanks, and a wave of the Flounder Fin to Michael Mueller for taking the time to point out this problem. 

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“PHP is a minor evil perpetrated and created by incompetent amateurs, whereas Perl is a great and insidious evil perpetrated by skilled but perverted professionals.” - Jon Ribbens