duilib391中,当焦点落在Edit中, WM_KEYDOWN就收不到了,wparam == VT_TAB自然也就收不到。
这就导致了Edit中不能用Tab键切换到另外一个Edit.
这看起来很怪. 比如一个登录对话框, 使用者在输入用户名之后,用鼠标点击另外一个口令输入框,才能继续输入口令.
手头有一个duilib1.0做的登录对话框,支持Tab键切换, 回车键执行登录.
将这个框框挪到duilib391的版本,全部失效。
记得看过duilib的svn日志,说是取消了Tab键的支持,原因是WebBrowser控件会有问题.
今天和同事讨论后,用了个折中的方法.
使用SetWindowHookEx, 检测TAB键, 回车键, ESC键, 使登录对话框Tab键可以切换, 回车键执行登录, ESC键退出登录框.
在SetWindowsHookEx中指定的键盘回调中,PostMessage到主程序, 主程序再PostMessage或直接调用登录对话框中的方法, 来执行TAB键,ENTER键,ESC键的处理.
typedef struct _tag_HOOKDATA
{
int nType;
HOOKPROC hkProc;
HHOOK hHook;
} TAG_HOOK_DATA;
/// Hook键盘产生的数据, 动机 : 当焦点在Edit中时, DuiLib不传递 WM_KEYxx + VT_TAB
/// 下全局钩子, 不采用DLL注入方式, 只监控本程序的键盘输入
/// 当有Tab键按下时, PostMessage 到主程序中处理,
/// 当此时, 登录框出现时, 就切换用户名和口令的Edit焦点
TAG_HOOK_DATA g_KbHookData;
HWND g_hHwndMain = NULL;
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
static LONG_PTR lVkCodePrev_Tab = 0;
static LONG_PTR lVkCode_Tab = 0;
static LONG_PTR lVkCode_Enter = 0;
static LONG_PTR lVkCode_Cancel = 0;
LONG_PTR lTemp = 0;
/** when tab key press down and up
KeyboardProc : nCode = 0x0, wParam = 0x9, lParam = 0xF0001
KeyboardProc : nCode = 0x0, wParam = 0x9, lParam = 0xC00F0001
*/
if (0 == nCode)
{
switch (wParam)
{
case VK_TAB:
{
lVkCode_Tab = (LONG_PTR)lParam;
lTemp = lVkCode_Tab & 0xFFF00000;
if (0 == lTemp)
{
// tab key press down
lVkCodePrev_Tab = lVkCode_Tab;
}
else
{
// tab key press up
if (lVkCode_Tab != lVkCodePrev_Tab)
{
lVkCodePrev_Tab = lVkCode_Tab;
if (NULL != g_hHwndMain)
{
::PostMessageW(g_hHwndMain, WM_TAB_KEY_PRESS, 0, 0);
}
}
}
}
break;
case VK_RETURN:
{
lVkCode_Enter = (LONG_PTR)lParam;
lTemp = lVkCode_Enter & 0xFFF00000;
if (lTemp > 0)
{
// key press up
if (NULL != g_hHwndMain)
{
::PostMessageW(g_hHwndMain, WM_ENTER_KEY_PRESS, 0, 0);
}
}
}
break;
case VK_ESCAPE:
{
lVkCode_Cancel = (LONG_PTR)lParam;
lTemp = lVkCode_Cancel & 0xFFF00000;
if (0 == lTemp)
{
// key press down
if (NULL != g_hHwndMain)
{
::PostMessageW(g_hHwndMain, WM_CANCEL_KEY_PRESS, 0, 0);
}
}
}
break;
default:
break;
}
}
return CallNextHookEx(g_KbHookData.hHook, nCode, wParam, lParam);
}
窗口出来后,有消息循环时, 执行SetWindowsHookEx
g_hHwndMain = this->GetHWND();
g_KbHookData.nType = WH_KEYBOARD;
g_KbHookData.hkProc = KeyboardProc;
g_KbHookData.hHook = ::SetWindowsHookEx(
g_KbHookData.nType,
g_KbHookData.hkProc,
(HINSTANCE)NULL,
GetCurrentThreadId());
窗口销毁前, UnHook
case WM_DESTROY:
{
/// 窗口临近销毁时, SendMessageW不到消息处理函数了, 阻塞了
/// so 窗口销毁时, 做个标记, 不允许再执行 SendMessage的操作
m_bWmDestroy = TRUE;
if (NULL != g_KbHookData.hHook)
UnhookWindowsHookEx(g_KbHookData.hHook);
ProcessBeforeQuit();
}
break;
消息的处理:
case WM_TAB_KEY_PRESS:
{
if (NULL != m_pLoginDlg)
m_pLoginDlg->TabPressUpProcess();
}
break;
case WM_ENTER_KEY_PRESS:
{
if (NULL != m_pLoginDlg)
m_pLoginDlg->EnterPressUpProcess();
}
break;
case WM_CANCEL_KEY_PRESS:
{
if (NULL != m_pLoginDlg)
m_pLoginDlg->CancelPressUpProcess();
}
break;
登录对话框消失后,要再设置一下鼠标光标.
void CMainDlg::DoLogin()
{
if (NULL == m_pLoginDlg)
{
m_pLoginDlg = new CLoginDlg(XML_FILE_NAME_LOGIN_DLG, WND_CLASS_NAME_LOGIN_DLG);
if (NULL != m_pLoginDlg)
{
m_pLoginDlg->SetOwner(this);
m_pLoginDlg->Create(this->GetHWND(), WND_DISP_NAME_LOGIN_DLG, UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);
m_pLoginDlg->CenterWindow();
m_pLoginDlg->ShowModal();
m_pLoginDlg = NULL;
/// 防止回车登录成功后, 鼠标光标必须右击一下才出现正常的鼠标光标.
::SetCursor(::LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW)));
}
}
}
登录对话框中的键处理:
BOOL CLoginDlg::TabPressUpProcess()
{
if (m_peditUserName->IsFocused())
m_peditUserPwd->SetFocus();
else
m_peditUserName->SetFocus();
return TRUE;
}
BOOL CLoginDlg::EnterPressUpProcess()
{
m_PaintManager.SendNotify(m_pbtnOk, DUI_MSGTYPE_CLICK);
return TRUE;
}
BOOL CLoginDlg::CancelPressUpProcess()
{
this->PostMessageW(WM_CLOSE, 0, 0);
return TRUE;
}