Rotating the Z-order

Raymond Chen

Sometimes you run into a conflict where you need the controls laid out in one Z-order to get hit-testing to work, but need a different order to get keyboard TAB order working.

For example, consider this dialog template, which has been simplified for expository purposes.

////////////////////////////////////////////////////////////////////////
//
//  Note: The ListView control has to be listed BEFORE the tab control in
//  order for drag-drop to work properly.
//
IDD_MAIN DIALOGEX  0, 0, 335, 270
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
                    | DS_CONTEXTHELP | DS_SHELLFONT
CAPTION "Blah blah"
FONT 8, "MS Shell Dlg"
BEGIN
    CONTROL         "List",IDC_LIST,WC_LISTVIEW,LVS_REPORT |
                    WS_BORDER | WS_TABSTOP |
                    LVS_SHOWSELALWAYS,15,46,305,111
    CONTROL         "Tab",IDC_TAB,WC_TABCONTROL,
                    WS_TABSTOP,7,24,321,141
    PUSHBUTTON      "&Import...",IDC_IMPORT,7,172,51,14
    PUSHBUTTON      "&Export...",IDC_EXPORT,62,172,51,14
    PUSHBUTTON      "&Remove",IDC_REMOVE,117,172,51,14
    DEFPUSHBUTTON   "&Close",IDOK,277,249,51,14
END

The dialog looks like this:

  _______  _______
 / Tab   \/ Tab   \_____________________
|                                       \
| +-[List]-----------------------------+ |
| |                                    | |
| |                                    | |
| |                                    | |
| |                                    | |
| +------------------------------------+ |
|________________________________________|
[ Import ]   [ Export ]  [ Remove ]
                                 [ Close ]

Drag/drop will call WindowFromPoint() to determine which window receives the drop. The window closest to the top of the Z-order (towards the beginning of the dialog template) will be chosen.

We want drops to go to the listview, not the tab control, so the ListView (IDC_LIST) needs to go ahead of the Tab control (IDC_TAB).

However, doing this breaks accessibility, because the tab order follows the Z-order, too. Consequently, the Tab order on the dialog is

List -> Tab -> Import -> Export -> Remove -> Close

This is an undesirable tab order, since it causes focus to jump upwards (from List to Tab). To get the tab order right, you need to put Tab before List.

This is where rotation comes in.

Start by putting the controls on the page in the order necessary for tabbing.

Tab -> List -> Import -> Export -> Remove -> Close

Recall that the tab order is circular. When you are on the last control and hit TAB, you go to the first one. So you really should view the tab order as a circle:

   /-->  Tab  ---\
   |             |
  Close          v
   ^            List
   |             |
  Remove         v
   ^            Import
   |             |
   \-- Export <--/

Once you view it this way, you realize that you can rotate the circle and the tab order remains unchanged. So let’s rotate it so List comes first, since we need List to come first in the Z-order.

   /-->  List ---\
   |             |
  Tab            v
   ^            Import
   |             |
  Close          v
   ^            Export
   |             |
   \-- Remove <--/

Now cut the circle open, yielding the linear diagram:

List -> Import -> Export -> Remove -> Close -> Tab

This gives us our final dialog template:

////////////////////////////////////////////////////////////////////////
//
//  Note: The ListView control has to be listed BEFORE the tab control in
//  order for drag-drop to work properly.
//
IDD_MAIN DIALOGEX  0, 0, 335, 270
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
                    | DS_CONTEXTHELP | DS_SHELLFONT
CAPTION "Blah blah"
FONT 8, "MS Shell Dlg"
BEGIN
    CONTROL         "List",IDC_LIST,WC_LISTVIEW,LVS_REPORT |
                    WS_BORDER | WS_TABSTOP |
                    LVS_SHOWSELALWAYS,15,46,305,111
    PUSHBUTTON      "&Import...",IDC_IMPORT,7,172,51,14
    PUSHBUTTON      "&Export...",IDC_EXPORT,62,172,51,14
    PUSHBUTTON      "&Remove",IDC_REMOVE,117,172,51,14
    DEFPUSHBUTTON   "&Close",IDOK,277,249,51,14
    CONTROL         "Tab",IDC_TAB,WC_TABCONTROL,
                    WS_TABSTOP,7,24,321,141
END

Now you get the best of both worlds. List is at the top of the Z-order, and the tab order is still correct.

0 comments

Discussion is closed.

Feedback usabilla icon