Say you’re trying to write your own tabbed dialog. You start with the Win32 tab control to get the tabs at the top of the window. But how do you position the controls that belong to each of the pages?
Let’s say, for simplicity, that your tab control has three tabs, and they let you select among three different edit controls.
Your natural inclination would be to make the page controls children of the tab control:
Main dialog | ||||
↓ | ||||
Tab control | ||||
↓ | ↓ | ↓ | ||
Edit 1 | Edit 2 | Edit 3 |
When the user is on a particular tab, you show that tab’s edit control and hide the other two.
The problem with this arrangement is that navigating the dialog with the Tab key doesn’t work.
Recall that the windows which participate in the tab order are those which are immediate children of the dialog frame, but with the special rule that if any of those children is marked WS_
, then it disappears from the tab order, replaced by its children. The WS_
rule applies recursively, so if any of the children have the style, then they too disappear and are replaced by their own children.
In the above diagram, if we assume that none of the windows is marked as WS_
, then the tab order consists only of the tab control itself. You can’t tab into the children.
Main dialog | ||||
↓ | ||||
Tab control | ||||
↓ | ↓ | ↓ | ||
Edit 1 | Edit 2 | Edit 3 |
I’ve highlighted in blue the windows that participate int he tab order. By default, the windows that participate in the tab order are the immediate children of the main dialog, which in this case is the tab control.
On the other hand, if you mark the tab control as WS_
, then you can tab into the children, but the tab control itself disappears from the tab order. The WS_
style means “Replace me in the tab order with my children.”
Main dialog | ||||
↓ | ||||
Tab control (WS_EX_CP) |
||||
↓ | ↓ | ↓ | ||
Edit 1 | Edit 2 | Edit 3 |
Win32 doesn’t let a control and its children all participate in the tab order. You have to choose whether you want the control or its children, but not both.
The solution is to take the page controls out from under the shadow of the tab control.
Main dialog | ||||||
↓ | ↓ | ↓ | ↓ | |||
Tab control | Edit 1 | Edit 2 | Edit 3 |
Setting the controls as siblings of each other gets them all into the tab order. We set the edit controls at a higher z-order, so they draw on top of the tab control.
In practice, you probably have a lot of controls on each page, not a single edit control. Hiding and showing all of those controls can be a hassle, but you can use WS_
to save you some effort:
Main dialog | ||||||||||||
↓ | ↓ | ↓ | ↓ | |||||||||
Tab control | Page 1 (WS_EX_CP) |
Page 2 (WS_EX_CP) |
Page 3 (WS_EX_CP) |
|||||||||
↓ | ↓ | ↓ | ↓ | ↓ | ↓ | |||||||
1a | 1b | 2a | 2b | 3a | 3b |
Wrap each group of controls inside a parent page control, and mark the page control as WS_
. This is easy to arrange by making the page controls themselves be dialog boxes. That also gives you the convenience of having a separate dialog procedure for each page.
You can then hide or show an entire group of controls by hiding or showing the page parent. This also has the advantage of preserving the hide/show states of the inner controls, so if the rule was that control 1b is shown only if control 1a is checked, then hiding page 1 will hide everything, and then showing page 1 will bring them back in the same state they were in as they were previously.
The tab background is styled, e.g. with a fancy color gradient on Windows XP. Not obeying the sibling principle or screwing up the Z order leads to problems: Obvious ones like controls not redrawing properly, but also pretty weird ones like the tab background becoming a blocky low-resolution version of the gradient, as even better seen here.
So the really interesting question becomes: How do controls inside of tabs draw their background?
They cannot request the background...
That would be a very nice follow-up blog post.
Over the years I solved the issue of gradient background in a several different ways.
But I always wondered what’s the proper way, i.e. how the PropertySheet common dialog does it.
But why was it designed this way? WS_TABSTOP already existed when WS_EX_CONTROLÂPARENT was added and it would be natural for both styles to be set for the tab control.
In Delphi and their VCL wrappers around the WinApi, the tab control is actually the parent.