How should I create controls on my dialog box that has a tab control?
The tab control from the shell common controls provides the tab selector control that is popular in tabbed dialogs. You’d be tempted to create the content of the tab control’s display area as children of the tab control, but that’s the wrong thing to do.
You should create them as siblings of the tab control.
What you want to do is create your tab control to cover the portion of the dialog box that you want to be tabbed. You then use the
ADJUSTRECT message (or the equivalent
AdjustRect macro) to determine the display area of the tab control. Inside that display area, you can place your content, but do it with the dialog box as the parent, not the tab control.
|Tab control||Content 1||Content 2|
Depending on which tab in the tab control is selected, you show exactly one of the content windows and hide the others.
If you think about how focus works in dialog boxes, you’ll realize that it has to be this way.
The tab control itself is focusable, and presumably you want to be able to put focus on your content, too. Now consider what happens if you create the content as children of the tab control:
|Content 1||Content 2|
By default, the tab order in a dialog box follows the dialog box’s immediate children. In this case, it means that the tab control can receive focus, but the content cannot, since they are not immediate children of the dialog box.
You can alter this behavior with the
CONTROLPARENT extended window style, which means “I’m just a container. My children are the things that can get focus, not me.” So let’s try that and put the
CONTROLPARENT extended window style on the tab control.
|Content 1||Content 2|
This time, the tab control drops out of the tab order, and its children, the content controls, join in.
With this window hierarchy, no amount of fiddling with the
CONTROLPARENT extended window style will allow the tab control and its children to all be part of the tab order. Because a window and its children cannot both be part of the tab order.
The only solution is to move the content controls out, so they aren’t children of the tab control. Making them siblings of the tab control, as they are in the original diagram, allows all three to participate in the tab order.
Bonus chatter: The content windows are typically nested dialogs which are marked with the
CONTROLPARENT extended window style. This permits the children of the nested dialogs to participate in the tab order, but keeping them inside a nested dialog lets you hide and show the controls in bulk by hiding and showing the nested dialog.
Why does WS_EX_CONTROLPARENT block the ability to join the tab order? You can already toggle that with WS_TABSTOP. It would just go: “Whatever” -> Container ->First child.
Huh, that goes against everything I’ve ever experienced?
I always thought the tab order going hierarchically down the tree of controls.
The samples also show creating the window inside the tab control (https://docs.microsoft.com/en-us/windows/win32/Controls/create-a-tab-control-in-the-main-window)
Also, Windows.Forms also has TabPages inside the TabControl. As far as I feel Windows.Forms is a mostly thin layer upon Win32.
In the “How to Create a Tab Control in the Main Window” sample, the child window of the tab control is a static control, so it would not receive the focus; and the tab control is not in a dialog box, so nobody calls IsDialogMessage and keyboard navigation won’t work anyway. It is somewhat misleading, though.
In the “How to Create a Tabbed Dialog Box” sample, the OnSelChanged function creates the child dialog as a child window of the main dialog, not as a child window of the tab control.
Windows Forms appears to implement keyboard navigation on its own, in ContainerControl.ProcessDialogKey.
A third-party GUI builder I used to use would automatically create both an outer parent control that contained the tab control plus all of the panes as separate nested dialogs, which also allowed you to think of the entire tab control as its own window as well.
I just used WS_EX_CONTROLPARENT to join a nested dialog into a parent one.
The tab order works great, alt keys work as well.
However, buttons in the nested dialog now don’t respond to the Enter Key. (when the tab focus is on them)
Without WS_EX_CONTROLPARENT they respond correctly.
Hitting the Space bar on the button does work, enter does not.
How to address that?