Why do I have to add 1 to the color index when I set it as the hbrBackground of a window class?


Our scratch program sets the background color to COLOR_WINDOW by setting the class background brush as follows:

    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

What’s with the +1?

Okay, first of all, let’s backtrack a bit.

The real first question is, “What’s the deal with taking an integer (COLOR_WINDOW) and casting it to a HBRUSH and expecting anything sane to happen?”

The window manager wants to provide multiple ways of setting the class background brush.

  1. The application can request that no automatic background drawing should occur at all.

  2. The application can request custom background drawing and provide that custom drawing by handling the WM_ERASE­BKGND message.

  3. The application can request that the background be a specific brush provided by the application.

  4. The application can request that the background be a specific system color.

The first three cases are easy: If you don’t want automatic background drawing, then pass the hollow brush. If you want custom background drawing, then pass NULL as the brush. And if you want background drawing with a specific brush, then pass that brush. It’s the last case that is weird.

Now, if Register­Class were being invented today, we would satisfy the last requirement by saying, “If you want the background to be a system color, then use a system color brush like this:

    wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW);

System color brushes match the corresponding system color, so this sets your background to whatever the current system window color is.”

But just as NASA couldn’t use the Space Shuttle to rescue the Apollo 13 astronauts, the Register­Class function couldn’t use Get­Sys­Color­Brush for class brushes: At the time Register­Class was designed, system color brushes had not yet been invented yet. In fact, they won’t have been invented for over a decade.

Therefore, Register­Class had to find some way of smuggling an integer inside a pointer, and the traditional way of doing this is to say that certain numerically-small pointer values are actually integers in disguise. We’ve seen this with the HINSTANCE returned by Shell­Execute, with the MAKE­INT­ATOM macro, with the MAKE­INT­RESOURCE/IS_INT­RESOURCE macro pair, and with the second parameter to the Get­Proc­Address function. (There are plenty of other examples.)

The naïve solution would therefore be to say, “Well, if you want a system color to be used as the brush color, then just cast the COLOR_XXX value to an HBRUSH, and the Register­Class function will recognize it as a smuggled integer and treat it as a color code rather than an actual brush.”

And then you run into a problem: The numeric value of COLOR_SCROLL­BAR is zero. Casting this to a HBRUSH would result in a NULL pointer, but a NULL brush already means something else: Don’t draw any background at all.

To avoid this conflict, the Register­Class function artificially adds 1 to the system color number so that none of its smuggled integers will be mistaken for NULL.

Comments (23)
  1. Joshua says:

    Q: Who wants scrollbar color as back color.

    A: The scrollbar.

    Personally I'd have made scrollbars part of the chrome (non client area) and an independent scrollbar made by setting HSCROLL or VSCROLL on button but oh well.

    [Without COLOR_SCROLLBAR, how would you make a custom control that matches the scroll bar color? Why does COLOR_SCROLLBAR have to be the one loser? (Also, if the scroll bar control was all-nonclient, how would you customize the color?) -Raymond]
  2. Jason Warren says:

    Another great insight into why things are they way they are!

  3. Parker says:

    These kind of tricks are still a common way to extend things without redesigning…unfortunately.

  4. Joshua Ganes says:

    I like the awkward phrasing that starts to emerge as you attempt to describe an event that is in the past from the perspective of a time even further in the past causing it to be both past and future at the same time. It reminds me of a great Douglas Adams quote:

    The major problem [with time travel] is simply one of grammar, and the main work to consult in this matter is Dr. Dan Streetmentioner's Time Traveler's Handbook of 1001 Tense Formations. It will tell you, for instance, how to describe something that was about to happen to you in the past before you avoided it by time-jumping forward two days in order to avoid it. The event will be descibed differently according to whether you are talking about it from the standpoint of your own natural time, from a time in the further future, or a time in the further past and is futher complicated by the possibility of conducting conversations while you are actually traveling from one time to another with the intention of becoming your own mother or father.

  5. Medinoc says:

    Nowadays the +1 wouldn't be necessary even if we still used the cast to HBRUSH, because COLOR_SCROLLBAR seems to have died ( social.msdn.microsoft.com/…/fac3ab17-2de9-45f9-b6c2-8afa87ed999a ).

  6. Joshua says:

    [(Also, if the scroll bar control was all-nonclient, how would you customize the color?) -Raymond]

    The same way you customize the menu bar color.

  7. * says:

    Why is the numeric value of COLOR_SCROLL­BAR zero? By convention, zero is normally reserved for absent or error values. Utilizing zero is asking for trouble and I'm sure the problem here is not by accident.

  8. Medinoc says:

    @*: Arrays are zero-indexed, in which case the error value is -1 rather than zero. I wouldn't be surprised if the system colors were an array, especially when the parameter of GetSysColor() is called "nIndex".

  9. * says:

    @Medinoc: So the intention is to save one array entry? As -1 is the error value, one oversight in error checking and you are indexing -1 into the array and get screwed.

  10. * says:

    @Medinoc: Memory address is also an index into an array and you should probably use 0xFFFFFFFF instead of NULL as the error value.

  11. Maurits says:

    > wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW);

    We can still do this, we just have to implement GetSysColorBrush as a client-side function.

    [Except that there is no way to change the color of a solid brush once it has been created, so if the window color changes, you're stuck. -Raymond]
  12. Joshua says:

    [Except that there is no way to change the color of a solid brush once it has been created, so if the window color changes, you're stuck. -Raymond]

    What do you think SetClassLong is for?

  13. Azarien says:

    I've seen countless of times code like this:

       wc.hbrBackground = (HBRUSH)COLOR_WINDOW;

    and no one seems to notice.

    People don't read documentation. Most of WinAPI code in the wild is copy-pasted from online tutorials, with the same quirks and mistakes repeated over and over.

  14. Joshua says:

    Well to be fair, in Win16, COLOR_MENU and COLOR_WINDOW were the same color. Now, it's approximately the same as BTNFACE which probably makes it look more like a dialog box. I suppose it's possible somebody likes their menus inverted, but it's unlikely anybody would discover this particular bug.

  15. Cesar says:

    Why not a MAKESMUGGLEDBRUSH macro, which would hide the + 1 within it? That would make the code more readable, reduce the chance of errors, and compile to the exact same machine code.

  16. Medinoc says:

    @*: Error checking where? You only need to write on GetSysColor() function and one GetSysColorBrush() function for internal use. And there is no function that *returns* a system color index.

  17. Medinoc says:

    System colors are an array "small enough for signed indexes", which means checking for "the error value" can be abridged into checking for *any* negative value, simply by checking the sign status flag (after *any* operation that affects it, not just comparison).

    You can't do this with memory, where you have to explicitly compare with either 0xFFFFFFFF or 0x00000000. The latter is easier.

    I know this isn't an argument in favor of using zero as a valid index for small arrays; it's only a rebuttal of the argument against it "because memory doesn't does so": Memory has different requirements.

    Also, "saving one array entry" was a pretty big deal back in Win16's days.

  18. Matt says:

    @Raymond: [Except that there is no way to change the color of a solid brush once it has been created, so if the window color changes, you're stuck. -Raymond]

    If Microsoft was implementing GetSysColorBrush, there's no reason why it has to return a SolidColorBrush – only that it must return a HBRUSH. It could return a HBRUSH that always maps to COLOR_WINDOW trivially (either via a fudge like "return (HBRUSH)(param + 1)" or via something better like if(param == COLOR_WINDOW) return NtGdi_CreateSystemColorBrush(SYSCOLOR_WINDOW) ).

    [Um, you're responding to a side thread about how to get the effect of GetSysColorBrush before GetSysColorBrush was invented. -Raymond]
  19. Karellen says:

    @Cesar, how does a DBRUSH differ from an HBRUSH, and why would a muggle want one? :-)

  20. Maurits says:

    I propose some simple syntactic sugar:

    // .h

    inline HBRUSH WindowClassBrush_From_SysColor(int c) { return (HBRUSH)(c + 1); }

    // .cpp

    wc.hbrBackground = WindowClassBrush_From_SysColor(COLOR_WINDOW);

  21. Brian_EE says:

    @Maurits: Too much sugar will make you fat and have diabetes. Perhaps now we know why Windows is so bloated (too much syntactic sugar in the source).

  22. voo says:

    @Brian_EE: How exactly would an inline function that does only what you have to do anyhow, increase the code size?

  23. Brian_EE says:

    @Voo… it's just a joke. Go with it.

Comments are closed.