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
- Override parent dialog's
PreTranslateMessage
function to intercept the event of a tab key.
- Declare
CFocusEx
object as a class member variable and GetFocusableWindow
function in order to manage the tab order:
class CFocusDlg : public CDialog
{
public:
CFocusDlg();
.
.
.
private:
static HWND GetFocusableWindow(int nPosition, LPVOID lParam);
CFocusEx m_objFocus;
};
- 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);
}
- 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.
-
First, set focus from the parent to the child. Return the parent window's handle to set the focus to the parent window.
-
Second, set focus from the child to the parent. Return the child window's handle to set the focus to the child window.
- Override sub dialog's
PreTranslateMessage
function to intercept the event of a tab key.
- Declare
CFocusEx
object as a class member variable and GetFocusableWindow
function in order to manage the tab order:
class CFocusSubDlg : public CDialog
{
public:
CFocusSubDlg();
.
.
.
private:
static HWND GetFocusableWindow(int nPosition, LPVOID lParam);
CFocusEx m_objFocus;
};
- 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);
}
- 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