Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

The Tab Order Helper Class

0.00/5 (No votes)
13 Oct 2009 2  
A class for manipulating tab order with sub dialogs

Introduction

In case of Windows applications, you can change focus of control by pressing the Tab key. A tab order means the sequence of focus when you press the tab key.

From the resource editor of the Visual C++ 6.0, you can see and change the tab order by pressing Ctrl+D key.

In case of a simple dialog, the tab order is easy to use. But, in case of a complicated dialog such as multi subdialog environment, the tab order doesn't work properly. The focus moves in one dialog and never moves to a sub dialog.

Complicated Dialog

All in all, a tab order works fine. And it's pretty easy. But, when you create a sub child dialog, the problem will occur. Let's imagine one dialog that has three buttons and one sub child dialog. And the sub child dialog has two buttons. In this situation, the expected tab sequence is as follows:

1. Main Dialog Button 1
2. Main Dialog Button 2
3. Main Dialog Button 3
4. Sub Dialog Button 1
5. Sub Dialog Button 2

But, without additional tab key handling, we can't set focus to the sub dialog by tab key and it works 1 > 2 > 3 > 1 > 2 > 3...

CFocusEx class solves this problem. It changes the wrong sequence of focus(1 > 2 > 3 > 1 > 2 > 3...) to the expected sequence. (1 > 2 > 3 > 4 > 5 > 1 > 2 > 3...).

How It Works

CFocusEx is simple and flexible. Moreover, it supports most applications which have multi sub dialogs. When a user presses the tab key, CFocusEx compares between registered window and focused window. CFocusEx class moves the focus of control by the registered sequence. If the next focus control is dialog, it will set focus on the first control of that dialog.

Using the Code

  1. Override parent dialog's PreTranslateMessage function to intercept the event of a tab key.
  2. Declare CFocusEx object as a class member variable and GetFocusableWindow function in order to manage the tab order:
    class CFocusDlg : public CDialog
    {
    // Construction
    public:
    	CFocusDlg();
    	.
    	.
    	.
    private:
    	static HWND GetFocusableWindow(int nPosition, LPVOID lParam);
    	CFocusEx m_objFocus;
    };
  3. Initialize CFocusEx object and call the ProcessKeyPressMessage to process the key event. The first parameter of CFocusEx::InitFocusEx is GetFocusableWindow function. This is a very important function because it determines the next focus.
    BOOL CFocusDlg::PreTranslateMessage(MSG* pMsg)
    {
    	m_objFocus.InitFocusEx( GetFocusableWindow, this );
    
    	if( m_objFocus.ProcessKeyPressMessage( this, pMsg ) ) {
    		return TRUE;
    	}
    
    	return CDialog::PreTranslateMessage(pMsg);
    }
  4. Write GetFocusableWindow function to determine the next focus. You can assign appropriate handles that will receive focuses depending on the data of nPosition.
    Generally, you can write parent dialog's GetFocusableWindow like the following codes. m_dlgSub is the sub dialog that will be get a focus after the last control.
    HWND CFocusDlg::GetFocusableWindow(int nPosition, LPVOID lParam)
    {
    	CFocusDlg* pThis = (CFocusDlg*)lParam;
    	switch( nPosition )
    	{
    	case FOCUSABLEWINDOW_POSITION_FIRST:
    		{
    			return CFocusEx::GetFirstFocusableWindow
    					( pThis->GetSafeHwnd() );
    		}
    		break;
    	case FOCUSABLEWINDOW_POSITION_FOCUSABLE:
    		{
    			return pThis->m_dlgSub.GetSafeHwnd();
    		}
    		break;
    	case FOCUSABLEWINDOW_POSITION_LAST:
    		{
    			return CFocusEx::GetLastFocusableWindow
    					( pThis->GetSafeHwnd() );
    		}
    		break;
    
    	}
    	return NULL;
    }

    That function has three switch states: FOCUSABLEWINDOW_POSITION_FIRST or FOCUSABLEWINDOW_POSITION_LAST returns the first or last control's handle. FOCUSABLEWINDOW_POSITION_FOCUSABLE is the most important part of the function.
    The function will be called with FOCUSABLEWINDOW_POSITION_FOCUSABLE when user presses the tab button on the end of the control. So, there are two cases to consider.

    1. First, set focus from the parent to the child. Return the parent window's handle to set the focus to the parent window.

    2. Second, set focus from the child to the parent. Return the child window's handle to set the focus to the child window.

  5. Override sub dialog's PreTranslateMessage function to intercept the event of a tab key.
  6. Declare CFocusEx object as a class member variable and GetFocusableWindow function in order to manage the tab order:
    class CFocusSubDlg : public CDialog
    {
    // Construction
    public:
    	CFocusSubDlg();
    	.
    	.
    	.
    private:
    	static HWND GetFocusableWindow(int nPosition, LPVOID lParam);
    	CFocusEx m_objFocus;
    };
  7. Initialize CFocusEx object:
    BOOL CFocusSubDlg::PreTranslateMessage(MSG* pMsg)
    {
    	m_objFocus.InitFocusEx( GetFocusableWindow, this );
    
    	if( m_objFocus.ProcessKeyPressMessage( this, pMsg ) ) {
    		return TRUE;
    	}
    
    	return CDialog::PreTranslateMessage(pMsg);
    }
  8. Write GetFocusableWindow function to determine the next focus.
    Generally, you can write child dialog's GetFocusableWindow like the following code:
    HWND CFocusDlg::GetFocusableWindow(int nPosition, LPVOID lParam)
    {
    	CFocusDlg* pThis = (CFocusDlg*)lParam;
    	switch( nPosition )
    	{
    	case FOCUSABLEWINDOW_POSITION_FIRST:
    		{
    			return CFocusEx::GetFirstFocusableWindow
    					( pThis->GetSafeHwnd() );
    		}
    		break;
    	case FOCUSABLEWINDOW_POSITION_FOCUSABLE:
    		{
    			if( pThis->GetParent() ) {
    				if( pThis->GetParent()->GetParent() ) {
    					return pThis->GetParent()->
    						GetParent()->GetSafeHwnd();
    				}
    			}
    		}
    		break;
    	case FOCUSABLEWINDOW_POSITION_LAST:
    		{
    			return CFocusEx::GetLastFocusableWindow
    					( pThis->GetSafeHwnd() );
    		}
    		break;
    
    	}
    	return NULL;
    }

That's all. If you didn't understand my code in this article, the code of the attached project will be easier to understand.

History

  • 13th October, 2009: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here