Click here to Skip to main content
Click here to Skip to main content

The Tab Order Helper Class

, 13 Oct 2009
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Yonghwi Kwon
Software Developer
United States United States
I started to write software since 1999 and have developed various products including security solutions and system utilities.
 
Microsoft Visual C++ MVP (from 2008 to present)
Website: http://rodream.net

Comments and Discussions

 
GeneralMy vote of 5 Pinmemberwebrobot99923-Jul-11 13:09 
GeneralMy vote of 2 Pinmemberpophelix15-Oct-09 2:56 
GeneralRe: My vote of 2 [modified] PinmemberKwon Yong Hwi27-Oct-09 4:37 
GeneralWS_EX_CONTROLPARENT does this Pinmemberjonnybgood213-Oct-09 12:11 
GeneralRe: WS_EX_CONTROLPARENT does this [modified] PinmemberKwon Yong Hwi27-Oct-09 4:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140814.1 | Last Updated 13 Oct 2009
Article Copyright 2009 by Yonghwi Kwon
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid