First I thought this would be an easy fix. But it wasn’t. It took a while to figure things out but then I had a solution which works.
While digging into the problem, I first found the information that Windows 9x and Windows NT require a different handling to toggle the NumLock key. That problem was solved by a simple piece of code in the constructor of the new CNumLock class.
The handling of the NumLock key is buried deep inside the BIOS: if the NumLock key is on, all keystrokes on the numeric keypad will create digits; if the NumLock key is off, those keys will create the same code as the respective cursor keys. There is simply no way, a program could detect whether a key on the numeric keypad was used or a different, cursor key. Therefore I had to find a way to detect when the NumLock key was used.
One idea was to create an accelerator for the NumLock key: this did not work because holding the NumLock key down for some time would create numerous accelerator events but the NumLock state would not change until the NumLock went up again. Apparently, the NumLock state is changed in the BIOS when the NumLock key is depressed but inside Windows 9x/NT, the state is changed only when the NumLock key goes up again.
The current state of the NumLock key can be detected using GetKeyState. However, the state of the NumLock key is not always up-to-date: when the NumLock key is toggled outside my application, the correct state of the NumLock key is not available when my application is WM_ACTIVATEd! Only when my application goes into the idle loop (CWinApp::OnIdle), the state of the NumLock key can be retrieved correctly again.
The Solution
The solution of the above problems is implemented in the class CNumLock. That class contains the following private data members:
OSVERSIONINFO m_OSVersionInfo;
That structure holds the information whether Windows 9x or Windows NT is running. It is filled during construction of CNumLock by GetVersionEx() and used in CNumLock::ToggleNumLock().
int m_iReqdNumLockState;
is also defined in the CNumLock constructor. It contains either a 0 or a 1, depending on the desired NumLock status for the application. The value is chosen such that a comparison with the current state (using GetKeyState()) is simple.
bool m_bIgnoreKeyInput;
is needed to filter out any undesired keyboard input while the NumLock key is down or while the NumLock key is toggled (by insertion of a NumLock key down and up sequence).
The CNumLock class uses the following methods internally:
int GetCurrNumLockState()const;
returns the current status of the NumLock key as 0 (off) or 1 (on). The result can be compared directly with the required NumLock status m_iReqdNumLockState.
void ToggleNumLock();
will simply toggle the state of the NumLock key. The method used depends on the operating system: under Windows 9x, the state is set by writing the keyboard state array back (SetKeyboardState()); under Windows NT, a NumLock key down and up sequence is needed.
For outside access, the following methods are provided:
CNumLock( bool bAlwaysOn = true );
The constructor specifies the desired state of the NumLock key for the application using this class: the default setting is that the NumLock key should always be on.
CheckNumLockState()
will compare the current NumLock key state with the requested NumLock state. If is not the same, it will cause a ToggleNumLock() call.
bool FilterMessage( const MSG* pMsg )
is used to check all incoming WM_KEYDOWN and WM_KEYUP messages for NumLock activities: if the NumLock key goes down, all further keyboard input will be ignored until the corresponding WM_KEYUP message is detected. Then the actual state of the NumLock key is compared with the required state and – if different – the NumLock key is toggled again. That method is designed to be used in PreTranslateMessage(): the return value specifies whether that WM_KEYxxx message should be ignored or processed.
How to use CNumLock
In general, an object of type CNumLock must be declared for the application, PreTranslateMessage() and OnIdle() must be overridden. This would look like this:
class CNumLockApp : public CWinApp
{
private:
CNumLock m_NumLock;
…
}BOOL CNumLockApp::OnIdle(LONG lCount)
{
if( lCount <= 0 )
m_NumLock.CheckNumLockState();
return CWinApp::OnIdle(lCount);
}BOOL CNumLockApp::PreTranslateMessage(MSG* pMsg)
{
if( m_NumLock.FilterMessage( pMsg ))
return TRUE;
else
return CWinApp::PreTranslateMessage(pMsg);
}
The same idea can be used for a dialog-based application:
class CNumLockDlgDlg : public CDialog
{
private:
CNumLock m_NumLock;
…
}LRESULT CNumLockDlgDlg::OnKickIdle(WPARAM, LPARAM lCount)
{
m_NumLock.CheckNumLockState();
return 0;
}BOOL CNumLockDlgDlg::PreTranslateMessage(MSG* pMsg)
{
if( m_NumLock.FilterMessage( pMsg ))
return TRUE;
else
return CDialog::PreTranslateMessage(pMsg);
}
Restrictions
The document-based application will set the requested NumLock state any time, that application enters the idle loop: OnIdle is called even when the application is not active and the mouse pointer is moved over the application.
The dialog-based application will not work quite the same: the WM_KICKIDLE message is not sent when the application is activated. I could not find out how to intercept the WM_ACTIVATE message for a CDialog …
Download demo project (SDI) – 21 KB
Download demo project (dialog-based app) – 16 KB
Date Last Updated: March 3, 1999