Introduction
A dialog can send key input to any foreground window without itself being activated to function as an on-screen keyboard. Regular windows get activated and focused when they are clicked or something. To avoid this, a window needs to be a non-activating window. You can do this by creating a window with the "WS_EX_NOACTIVATE
" extended style, or by modifying its style using "ModifyStyleEx
". But, with this style, a window behaves somewhat differently than expected when you move the window. To solve this, you need to provide your own implementations for the "WM_NCLBUTTONDOWN
" and "WM_MOUSEMOVE
" messages to behave the same way as the system-provided On-Screen Keyboard does. This code shows how to do this and send your own keyboard inputs to any foreground window using the newly-introduced "SendInput
" system function.
Background
One day, I needed to implement a virtual On-Screen Keyboard that received signals - through a COM (serial) port - from a custom input device and then sent keyboard inputs to the system according to the signals. While doing this, I came to know about the On-Screen Keyboard accessory application provided by Windows, and wanted my application to behave similar to that. But, it was not as trivial a task as I first expected, because of the "focus" and "activation" problems. In this example, you will find that avoiding those problems could be done in a simple way.
Using the code
This is a VC++ 9.0 project.
Firstly, you need to create an MFC dialog-based application. And then, to make the dialog a non-activating topmost window, you need to change its "No Activate" and "Topmost" resource properties to "true".
Then, handle "WM_NCLBUTTONDOWN
" message. This is for making the dialog temporarily behave as a regular window without the "WS_EX_NOACTIVATE
" style so that it can move smoothly.
void COnScreenKeyboardDlg::OnNcLButtonDown(UINT nHitTest, CPoint point)
{
if (!m_hForegroundWnd)
{
m_hForegroundWnd = ::GetForegroundWindow();
ModifyStyleEx(WS_EX_NOACTIVATE,0);
SetForegroundWindow();
}
CDialog::OnNcLButtonDown(nHitTest, point);
}
Now, handle the "WM_MOVE
" message. This is for making the dialog behave again as a non-activating window when the mouse pointer moves around in the client area, and for making any previous foreground window active. This is the exact behavior the system-provided On-Screen Keyboard shows.
void COnScreenKeyboardDlg::OnMouseMove(UINT nFlags, CPoint point)
{
if (m_hForegroundWnd)
{
::SetForegroundWindow(m_hForegroundWnd);
ModifyStyleEx(0,WS_EX_NOACTIVATE);
m_hForegroundWnd = NULL;
}
CDialog::OnMouseMove(nFlags, point);
}
Now, to send an arbitrary key input to the system, you can use the "SendInput
" function. Here, I send the "x" (virtual code 88) key input as an example when the "Send X" button in the dialog is clicked.
void COnScreenKeyboardDlg::OnBnClickedSendX()
{
INPUT keyInput;
keyInput.type = INPUT_KEYBOARD;
KEYBDINPUT key;
key.wVk = 88;
key.wScan = ::VkKeyScan(88);
key.dwFlags = 0;
keyInput.ki = key;
::SendInput(1,&keyInput,sizeof(INPUT));
key.dwFlags = KEYEVENTF_KEYUP;
keyInput.ki = key;
::SendInput(1,&keyInput,sizeof(INPUT));
}
Points of interest
Code samples I have found on the Internet do this in an overly complicated manner using the "AttachThreadInput
" function. As you can see, making a window behave the same way as the Windows On-Screen Keyboard can be done in a simple way using the "WS_EX_NOACTIVATE
" style and some other small tricks.