0

We have a series of dialogs in our application for which the dialog template defines 4 buttons along the bottom of the screen. However (depending which version of our hardware the application is running on) we sometimes create 2 additional buttons and then line up the 6 buttons (4 from the template, 2 created by calling CButton::Create()) along the bottom.

The problem I have is that usually the user can move the focus between these buttons using the left / right arrow keys (there is no mouse or touchscreen, just a restricted keyboard). This follows the control TAB-order as you would expect for the 4 buttons from the template. However the 2 dynamically create controls appear to be inserted at the beginning of the TAB-order, and that means (because they are put at the right-hand side of the screen) that they are in the "wrong" order as far as the cursor keys goes. In other words, when the focus gets to the left-hand button (TAB order 1) pressing the left arrow jumps the focus to the button on the right-hand side, which is just plain confusing...

There appears to be some link between Z-order (which I can affect with SetWindowPos()) and TAB-order but it does not seem to be a simple 1-to-1: by changing the Z-order I can move the sequence around so that the buttons are completely in the wrong order, so I can change the Z-order, but I can't work out how to get them in the right order.

Can anyone give a concise explanation of how TAB-order works, and how to control the sequencing of controls at runtime?

Edit: kol suggested below using SetWindowPos() to set the Z-order. This I had tried before but it does not let the cursor keys control the focus as expected.

However, by rigging this up so I can use TAB (as a test -- this isn't practical for the end-user solution) I find that kol's solution does resolve the TAB-order. The problem I have is that this is not the same as the order used by the cursor keys!

So, revised question: how can I specify the order in which left / right cursor keys move focus between controls?

Solution: With assistance from kol and MarkRansom's input, I have got this working now.

I used SetWindowPos() as suggested by kol to put my new buttons after the existing buttons in the TAB order, and then (as Mark suggested) made the first button WS_GROUP | WS_TABSTOP but cleared those flags from the other buttons.

However this was not enough to solve the problem, with the 2 new buttons still appearing to come before the first rather than after the second when using arrow keys (not TAB) to move around.

I looked at my dialog template, and it was like this:

IDD_QUERY DIALOG  0, 0, 156, 34
STYLE DS_SETFONT | WS_POPUP | WS_CAPTION
FONT 8, "MS Sans Serif"
BEGIN
    PUSHBUTTON      "+++Skey1+++",IDC_SKEY_1,1,21,36,12
    PUSHBUTTON      "+++Skey2+++",IDC_SKEY_2,40,21,37,12
    PUSHBUTTON      "+++Skey3+++",IDC_SKEY_3,79,21,36,12
    PUSHBUTTON      "+++Skey4+++",IDC_SKEY_4,118,21,36,12
    LTEXT           "Static",IDC_QUERY_MSG,2,1,153,15
END

So the static IDC_QUERY_MSG, which is used to display information to the user, was coming after the 4th button in the template. To resolve the problem, I moved IDC_QUERY_MSG before the first button (IDC_SKEY_1): this means the 6 buttons are not split up by a static inbetween, and has solved the problem.

Thanks to everyone for their assistance!

AAT
  • 3,286
  • 1
  • 22
  • 26
  • Consider WM_GETDLGCODE so you can just do it yourself. – Hans Passant Dec 11 '11 at 21:54
  • @HansPassant - yes, that is my fallback plan, just hoping to be able to set it up correctly without resorting to too much manual intervention. – AAT Dec 11 '11 at 22:29
  • Z-order and Tab-order are identical. When you call SetWindowPos as shown, what's the behavior? You should check all return codes to make sure things are actually working as planned. – Mark Ransom Dec 12 '11 at 13:53
  • @MarkRansom, as per my revised question, TAB-order is what I want it to be, the problem is that Left / Right arrow keys don't follow it as I would expect. As per my comment & yours on kol's answer below, I think WS_GROUP is likely at the root of the problems. – AAT Dec 12 '11 at 19:35

5 Answers5

1

Use the SetWindowPos member of your buttons. Calling it on button A and setting its first parameter to button B, puts button A after button B in the TAB order. If you want to set the order of two controls, you have to know the controls before and after them in the TAB order - this example shows how to do this (its not MFC but pure WinAPI, but it's easy to understand).

== UPDATE ==

I created a dialog template with four buttons at the bottom and an editbox at the top, with TAB order button1 -> button2 -> button3 -> button4 -> editbox -> button1 -> ... In OnInitDialog, I added two additional buttons at runtime, and inserted them into the existing TAB order between button4 and editbox using SetWindowPos and GetNextWindow. Pressing TAB repeatedly shows that the TAB order is correct: button1 -> button2 -> button3 -> button4 -> button5 -> button6 -> editbox -> button1 -> ...

class CTestDlg : public CDialogEx
{
public:
    CTestDlg() : CDialogEx(CTestDlg::IDD) {}
    enum { IDD = IDD_TESTDIALOG };

protected:
    CButton button5;
    CButton button6;
    virtual BOOL OnInitDialog();
};

BOOL CTestDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    CButton* button4 = (CButton*)GetDlgItem(IDBUTTON4);
    CWnd* next = button4->GetNextWindow(GW_HWNDNEXT);

    button5.Create("Button5", WS_CHILD|WS_VISIBLE|WS_TABSTOP|BS_PUSHBUTTON, CRect(340, 172, 415, 195), this, 1005);
    button5.SetWindowPos(button4, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); 

    button6.Create("Button6", WS_CHILD|WS_VISIBLE|WS_TABSTOP|BS_PUSHBUTTON, CRect(422, 172, 497, 195), this, 1006);
    button6.SetWindowPos(&button5, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); 

    if (next != NULL)
        next->SetWindowPos(&button6, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); 

    return TRUE;
}

void CDynamicMfcButtonTestApp::OnTestRun()
{
    CTestDlg testDlg;
    testDlg.DoModal();  
}

== UPDATE2 ==

The order of controls can be set using SetWindowPos and GetNextWindow, as above. The "tab order" and the "arrow order" can be set by setting the WS_TABSTOP and WS_GROUP styles of the controls:

  • The WS_TABSTOP style specifies the controls to which the user can move by pressing the TAB key or SHIFT+TAB keys.

  • The WS_GROUP style marks the beginning of a group of controls. If a control in the group has the input focus when the user begins pressing direction keys, the focus remains in the group. In general, the first control in a group must have the WS_GROUP style and all other controls in the group must not have this style. All controls in the group must be contiguous—that is, they must have been created consecutively with no intervening controls.

More details can be found on MSDN, here.

kol
  • 27,881
  • 12
  • 83
  • 120
  • I already knew about `SetWindowPos()` and mentioned this in my post. However it does not appear to directly affect TAB-order, from my experiments. (I had in fact found, and used, the exact example you referenced, but without satisfactory results.) – AAT Dec 11 '11 at 22:30
  • I'm currently working on a test code and will update my post when it's ready :) – kol Dec 11 '11 at 22:43
  • thanks, I'm not sure what you've done that I haven't, I'll try it tomorrow! – AAT Dec 11 '11 at 23:43
  • You can download the Visual C++ solution from here: http://dl.dropbox.com/u/32851010/StackOverflow_DynamicMfcButtonTest.zip No executables. Only 144 kB in total. – kol Dec 11 '11 at 23:50
  • still can't get it to work -- it still does exactly what I had before, i.e. the dynamically created buttons appear to be earlier in the TAB-order than the template-created buttons. I see your solution is for VS2010, whereas since I am working on Windows CE I can't go past VS2008 (and in fact for historical reasons we are still on VS2005 for this project). I am not convinced that explains the difference, but perhaps it is a WinCE vs desktop issue. – AAT Dec 12 '11 at 13:38
  • this _is_ solving the issue of TAB-order - but it turns out that is not enough, and cursor-key movement of focus does not appear to follow TAB-order. – AAT Dec 12 '11 at 13:51
  • You can have groups among which you can jump by pressing Tab/Shift+Tab. Between elements of a group you can move using RightArrow/LeftArrow. This is circular: pressing Tab on the last group moves the focus to the first one, and pressing RightArrow on the last item in a group moves the focus to the first element of the group. I could fix your code if I would exactly know (1) what kind of GUI elements do you have on the dialog, (2) which elements should be in the same group, (3) what should be the tab order of the groups, and (4) what should be the "arrow order" of items in each group. – kol Dec 12 '11 at 14:07
  • I found a detailed description on MSDN, pls. see at the bottom of my answer. – kol Dec 12 '11 at 14:23
  • @AAT, try removing the WS_GROUP property from every control. – Mark Ransom Dec 12 '11 at 17:50
  • @kol - the only GUI elements I want to arrange are buttons. – AAT Dec 12 '11 at 19:31
  • @MarkRansom -- I think you might be onto something here, and I see kol has also mentioned WS_GROUP. I will have a go at this tomorrow & will report back! – AAT Dec 12 '11 at 19:32
  • Many thanks for the input, I have accepted this as the answer because it helped a lot, though I had to do some more tinkering... see "Solution:" section to my question. – AAT Dec 13 '11 at 21:57
1

Try this:

  1. Create a vector of dlg ids and populate it in the order you want the tab order to be:

    typedef std::vector<int> TABLIST;
    
    TABLIST lst;
    lst.push_back( IDC_CONTROL1 );
    lst.push_back( IDC_CONTROL2 );
    lst.push_back( IDC_CONTROL3 );
    
  2. Then call the setTabOrder method with the list:

    void setTabOrder( TABLIST * plstTabs)
    {
        // Iterate through the list and put each item at the top of the tab order.
        // Remember to do this backwards so the last item is actually the first item
        // in the tab order
    
        HDWP hDefer = BeginDeferWindowPos( (int)plstTabs->size() );
    
        for ( TABLIST::reverse_iterator itTab = plstTabs->rbegin(); itTab != plstTabs->rend(); ++itTab )
        {
            DeferWindowPos( hDefer, GetDlgItem( *itTab )->m_hWnd, HWND_TOP, 0,0,0,0, 
                        SWP_NOSIZE | SWP_NOMOVE | SWP_NOREPOSITION );
        }
    
        EndDeferWindowPos( hDefer );
    
    }
    
snowdude
  • 3,854
  • 1
  • 18
  • 27
0

Modified version of @snowduded's code that makes sure the WS_TABSTOP style is in also.

// Build your order in a vector.
std::vector<WORD> vCtrlIds;
vCtrlIds.push_back(IDC_CONTROL1);
vCtrlIds.push_back(IDC_CONTROL2);
vCtrlIds.push_back(IDC_CONTROL3);
// ... keep doing it!

// Iterate through the list and put each item at the top of the tab order.
// Remember to do this backwards so the last item is actually the first item
// in the tab order.
HDWP vDefer = BeginDeferWindowPos(vCtrlIds.size());
for(auto vCtrlId(vCtrlIds.rbegin()); vCtrlId != vCtrlIds.rend(); ++vCtrlId){
    HWND vCtrl(GetDlgItem(*vCtrlId)); // Grab the handle.
    SetWindowLongPtr(vCtrl, GWL_STYLE, // Make sure we have WS_TABSTOP.
        GetWindowLongPtr(vCtrl, GWL_STYLE) | WS_TABSTOP);
    DeferWindowPos(vDefer, vCtrl, HWND_TOP, 0, 0, 0, 0, // Reorder.
        SWP_NOSIZE | SWP_NOMOVE | SWP_NOREPOSITION );
}
EndDeferWindowPos(vDefer);

Props to @snowdude for an excellent reusable solution.

CodeAngry
  • 12,760
  • 3
  • 50
  • 57
0

If you are talking about a tab order of different controls over a dialog:

Try this Open the dlg in resource and press Ctrl+D Now you can set order by selecting controls one after another.

-1

You can't change the tab order in runtime. What you can do is to put these two buttons in your dialog resource (with the right tab order), and make them invisible. Then you show/arrange the buttons as soon as you need them.

l33t
  • 18,692
  • 16
  • 103
  • 180
  • As far as I can make out, this just ain't so: you CAN change the tab order, by changing the Z-order. – AAT Dec 12 '11 at 19:31