Fixing an accessibility bug with the trackbar common control

The trackbar common control is a strange beast. 

The trackbar can be oriented either horizontally or vertically.  On LTR language machines, when the trackbar is horizontal, it works much as you’d expect it to: The minimum value of the trackbar is on the left, the maximum value is on the right (it’s reversed for RTL languages so it works consistently).

When the trackbar is vertical, it’s a different beast entirely.  For whatever reason, the trackbar control designers set the minimum value of the trackbar at the top, the maximum value at the bottom.  If you think about how the trackbar designers actually implemented the trackbar this makes sense.  If you orient the trackbar so that the minimum value is at the top, then when you need to draw the trackbar you can use the same drawing code to draw the horizontal trackbar – you just swap the X and Y axis (I have absolutely no idea if that’s how it works internally, it just seems to make sense that way).

While this works great for the implementer, for the consumer of the trackbar, it’s a pain in the neck.  That’s because the users who interact with the control expect the maximum value to be at the top of the control, not the bottom.

As a result, when you look at code that uses the trackbar control, you end up seeing a lot of:

LRESULT MyDialog::OnNeedTTText(int idCtrl, LPNMHDR pnmh)
if (idCtrl == IDD_TRACKBAR)
int nPos = (int)SendMessage(TBM_GETPOS, 0, 0);

StringCchPrintf(pTT->szText, ARRAYSIZE(pTT->szText), TEXT("%d"), 100 - nPos);
return 0;

In other words, retrieve the position from the trackbar, convert it from 0..100 to 100..0 and return that as a tooltip text.

All of this works great – you have a lot of 100 - <n> scattered throughout your code, but it’s not the end of the world.  And then one day a tester comes to you and says that when he uses the narrator tool to read the contents of your tool, it reports that the value reported by the control is wrong – when the slider’s at the top (tooltip 100), it reports that the value is 0, when it’s at the 1/4 point (tooltip 75), it reports that the value is 25.

Crud.  At this point many developers start scratching their heads and start thinking about subclassing the trackbar to replace the reported position.  However that’s way more work than they need to do.

It turns out that the designers of the trackbar control thought of this problem.  If you’re using version 5.8 or higher of the common controls, you can specify the TBS_REVERSED trackbar control style to your trackbar.  If you do, the visuals of the trackbar are unchanged as is the trackbar functionality, but the Microsoft accessibility framework will look for the presence of the TBS_REVERSED style and if it is found, it assumes that the control is “backwards” and it reports the position for the toolbar as if the maximum and minimum values were reversed.

And no, I didn’t know about this before today.  But it was too good a trick not to share.

Comments (9)
  1. Anonymous says:

    So, instead of creating a flag TBS_REVERSED that just switches the orientation internally in the control and be done with it once and for all, every programmer has to switch the direction manually outside of the control AND set a new special flag TBS_REVERSED that does nothing by itself, but is then in turn interpreted by a special case in the accesibility framework that then tries once more to immitate the additional calculations the programmer had to do.

    That is… mind boggling one many levels.

  2. Marcel: Changing the semantics of the control means breaking every single existing application that uses the control.  

    I’ll take adding hint for the accessibility tools instead of breaking applications.

    They could have made TBS_REVERSED change the semantics of the control, but if they did that it would dramatically increased the complexity of trackbar implementation (assuming that my guess about the implementation is correct).  And that in turn would increase the chances of bugs in the trackbar.

  3. Anonymous says:

    Of course I’m only suggesting that the new flag, which was introduced anyway, changes the semantics.  Old stuff works as expected. New stuff has to set the flag anyway. And regarding the internal complexity, what’s more to it than  replacing "currPos" with (paraphrased) "(flags & TBS_REVERSED)? maxPos-currPos: currPos" at some locations? It’s just what the programmer had to do outside of the control anyway put inside of it.

    The only reason I can imagine why it was done this way: after the accessibility problem was found, the flag was added as a quick hack so that old code that already employs the "max-curr" trick did not have to be changed on the expense of a clean design.

  4. Marcel, that might be the case.  On the other hand, it’s a tradeoff.  It’s not just "some locations" that would have to be updated in the control, I suspect the control has a lot of stuff that compares the current value with the max value to figure out positions and sizes.  So you’re trading off complexity in the control’s implementation with ease of implementation on the part of the controls users.  And the developers who write with the control are already used to this complexity.  

  5. Jiri Dvorak says:

    Why to do complex changes to internal implementation? I think that that having the control reversing the value when it is set and retrieved should be sufficient. Or I am missing something?

    Also I do not think that this is the right kind of tradeoff to make. If done in the control, it would be implemented and verified once. Otherwise it is replicated in thousands of programs.

    The additional thing is that having the flag do some "real" functionality would increase chance that it is really used. When it is only indication for the accessibility framework, chance that it will be properly set (or even used at all) decreases unless the developer really cares for accessibility of its program to check this.

  6. Jiri, please let me know when you get your time machine working so I can go back to 1999 (that’s when this field was introduced) and let the developers of the common controls know that they’re making a mistake.  They’ll then be able to explain to me exactly why they didn’t do it your way.

    At this point it’s a historical issue only. Sorry.

  7. Jiri Dvorak says:

    Sorry I did not meant to offend you. I understand that there likely was reasonable reason for that decision at that time (btw. I did some checks after that post and the reversing is more complicated that I initially thought because of the ability to change the range at any time, even to invalid values). Just few days ago I tried to find trackbar reversing within the .NET framework. I did not found anything (not even equivalent of the TBS_REVERSED style). So this article hit me on sensitive spot, sorry.

  8. Actually I don’t know why the .Net folks didn’t fix this – their forms version of the common controls have a number of changes from the stock controls, I suspect they could have fixed it.

    But they might have wanted to make it easier for unmanaged developers who were used to the control working in the unmanaged way.

  9. Anonymous says:

    They *did* fix it, Minimum is at the bottom.  But, they forgot about TBS_REVERSED.  Will you tell them?

Comments are closed.

Skip to main content