Why doesn't my custom-drawn trackbar get a paint notification when the position changes from 1 to 0?


A customer reported that under certain conditions, their custom-drawn trackbar does not generate a NM_CUSTOM­DRAW message.

We have found that the trackbar control in the shell common controls library does not generate a NM_CUSTOM­DRAW message when the position changes from 1 to 0 and the trackbar's range is sufficiently high.

We start with the trackbar position at 1.

−1000   +1000
   
| | |
 
  Current value: 1

And then we send the TBM_SET­POS message to set the trackbar position to zero. The result is this:

−1000   +1000
   
| | |
 
  Current value: 1

Observe that the "Current value" is reported as 1, even though we changed the value to 0. On the other hand, if we start with the position at −1:

−1000   +1000
   
| | |
 
  Current value: −1

then when we send the TBM_SET­POS message to change the position to zero, we do get a NM_CUSTOM­DRAW message, and the "Current value" updates.

−1000   +1000
   
| | |
 
  Current value: 0

We have been able to reproduce this problem on every version of the trackbar as far back as we tested.

Everything is working as it should.

The NM_CUSTOM­DRAW notification lets you customize how the common control draws itself. If there is nothing that needs to be redrawn, then there is no WM_PAINT message and consequently no NM_CUSTOM­DRAW notification.

When the trackbar range is large, then multiple positions have the same visual appearance. This is a natural consequence of the pigeonhole principle: There are 500 (say) pixel positions that the thumb could be drawn, but there are 2001 possible positions, so around four thumb positions all correspond to the same visual appearance.

What appears to be happening is that positions 0 and 1 share the same visual appearance, so when the thumb position changes between 0 and 1, there is no visual change and therefore no NM_CUSTOM­DRAW message.

On the other hand, it appears that positions −1 and 0 have different visual apperances, which is why you get a NM_CUSTOM­DRAW message when the position changes from −1 to 0.

It sounds like the application is using the NM_­CUSTOM­DRAW notification to detect when the trackbar position has changed. That's not what it's for. The NM_­CUSTOM­DRAW notification is for letting you customize the way the trackbar is drawn.

If you want to know when the trackbar position changes, listen for the WM_HSCROLL message. Note, however, that the WM_HSCROLL message is not generated if the program itself changes the position via the TBM_SET­POS message, on the theory that since the program itself changed the value, it can update its own state right there. No need to tell the program what it already knows.

Bonus chatter: Not generating a notification for program-generated position changes also helps avoid infinite loops. After the program changes the trackbar position, it receives the change notification, and in response to the notification, the program tries to update some state. But the state update fails, so the program tries to undo the change and set the position back. This reset generates its own change notification, and the program responds to the notification by trying to update that same state (to the old value), which still fails, so the program tries to undo the change and set the position back, which generates yet another change notification, and so on.

The theory here is that the code that is listening for the WM_HSCROLL or WM_HSCROLL message is also the code that is sending the TBM_SET­POS message, so there's no point in telling the program something it already knows.

Exercise: Suppose you have a trackbar, and you want to let anybody send it a TBM_SET­POS message to change the trackbar position, but you also want to be notified when that happens. How would you do that?

Comments (21)
  1. pc says:

    Exercise answer: I've only ever dabbled in straight Win32 programming, and that a long time ago, but I suspect that you could subclass the window to handle TBM_SETPOS yourself, do whatever internal bookkeeping you need to, and then forward it on to the actual control.

  2. viila says:

    What if you want to be fancy and do sub-pixel rendering or anti-aliasing so that the four positions aren't actually identical? Granted AA would probably be a terrible idea since it would make the thumb look blurry, but subpixel positioning could be done with close to perfect fidelity and you could triple your horizontal resolution.

    (I'll leave exercise to the reader how to let the user position it that accurately with the mouse...)

    1. The MAZZTer says:

      Then you invalidate the appropriate draw region when the position changes, which will generate your paint message.

      If you do this you probably want to disable the default behavior to avoid getting two messages. Or possibly build your own control from scratch if it's practical.

      1. Brian_EE says:

        >Or possibly build your own control from scratch if it’s practical.

        That's a good way to get featured on thedailywtf

        1. Yukkuri says:

          Gripe time: The Daily WTF is <= 80% made up (with the dubious excuse of "anonymizing"), which, just really? The Real WTF, to use their meme, is why people continue to read it and react as if it isn't mostly fake.

          1. Yukkuri says:

            And then I spoil my gripe by using =

            Sheesh

          2. Joshua says:

            We had a real investigation as to a source code leak when some code that looked like ours ended up there. Turned out it was from somebody else who hired the same consultant. So, not quite as made up as people believe.

          3. xcomcmdr says:

            You've got any source for your claim ?

          4. smf says:

            The problem with the daily wtf is that they've done it to death, so it feels like it's the same stories just going round and round. The only thing that changes is the stories they build around them.

          5. Mike S says:

            I've seen a system that I had to interface with featured on tdwtf. I think that everyone involved acknowledged that they had made some horrible design decisions early on. (At least one of the project managers did to me.)

          6. Brian_EE says:

            >And then I spoil my gripe by using =

            Not to mention that you also used the less-than sign when I presume you were trying to say greater than 80%. Kind of ruins your argument.

          7. Yukkuri says:

            It sure does :(

          8. Yukkuri says:

            Joshua: Wel let me clarify that I am sure that the technical details are generally in at least the same ballpark as the submissions, but the stories about people involved they wrap around them I don't believe, they smell like someone's creative writing assignments. When I hit one such story that was an elaborate description of a meeting about the problem and omg what do we do where the submitter turned up and said "yeah none of that ever happened I just thought it was wacky!" that was enough for me.

  3. ChDF T says:

    It seems like the customer was trying to paint the numerical value as part of the trackbar, which results in a different look for every value, because there is a different number written below the actual control.
    Of course the customer could just add some kind of label below the trackbar and remove the number rendering code from the custom draw method. However this would break (potentially a lot of) code that updates the trackbar and now would have to update the trackbar and the label. Since the exercise is solving this problem, I guess this is what the customer went with and Raymond chose a very elegant way to implicitly include this in his post.

    1. Joshua says:

      I was about to post something like this. However on reasoning it out I discovered that creating a slider with precision of greater than 1 pixel (that is, more stops than pixels) is silly.

      1. ChDF T says:

        It kind of is, but sometimes you can't avoid it: Imagine a Surface Pro 4 (high pixel density) connected to an external screen (standard pixel density). The window may be opened on the internal screen with a 1-on-1 correspondence of *your unit here* to pixels. Then the window is later moved to the external screen, the same trackbar becomes much smaller pixel wise (though the apparent size stays the same) - now you have a trackbar where 1 pixel maps to 2 (or more) of *your unit here*.
        To make it even more complicated, the new 1 unit distance still requires the same amount of mouse movement.
        So you can't really ensure that a trackbar is non-silly in a per-screen-dpi-aware world. Coincidentally this is also an example for why it is bad to rely on a trackbar for precise input.

      2. alegr1 says:

        Sometimes number of pixels increase (after resize), and you don't want two pixels have the same position number after that.

      3. Alex Cohn says:

        The best approach is to have exactly one stop per pixel (adjusted to DPI). E.g. a slider that represents video progress in integer seconds is OK for typical video files of 10 minutes or more, but looks ugly for instant clips of les than 5 sec, which are so popular nowadays.

  4. Ray Koopa says:

    I think you meant "...the code that is listening for the WM_HSCROLL or WM_VSCROLL message..."

  5. Ben Voigt (Visual Studio and Development Technologies MVP with C++ focus) says:

    "multiple positions have the same visual appearance" should be "multiple positions have the same visual appearance under the default drawing logic". When custom draw is added, that rule ceases to be correct. But the new common controls don't have explicit owner-draw window styles the way that listbox and combobox did, so the control doesn't have any good way to know whether or not the rule applies.

    Which may be why the terminology is different between "owner-drawn" and "custom draw".

  6. cheong00 says:

    The same reason why changing value of html controls with javascript won't generate onchange event.

Comments are closed.

Skip to main content