问题描述
我想创建一个编辑控件,用户只能在其中输入浮点数,但是我也希望能够在此编辑中复制/粘贴/剪切文本。因此,我使用以下窗口过程将编辑控件子类化:
LRESULT CALLBACK FloatTextBoxWindowProc(HWND windowHandle,UINT msg,WParaM wparam,LParaM lparam,UINT_PTR subclassId,DWORD_PTR refData)
{
switch (msg)
{
case WM_CHAR:
// If the character isn't a digit or a dot,rejecting it.
if (!(('0' <= wparam && wparam <= '9') ||
wparam == '.' || wparam == VK_RETURN || wparam == VK_DELETE || wparam == VK_BACK))
{
return 0;
}
else if (wparam == '.') // If the digit is a dot,we want to check if there already is one.
{
TCHAR buffer[16];
SendMessage(windowHandle,WM_GETTEXT,16,(LParaM)buffer);
// _tcschr finds the first occurence of a character and returns NULL if it wasn't found. Rejecting this input if it found a dot.
if (_tcschr(buffer,TEXT('.')) != NULL)
{
return 0;
}
}
default:
return DefSubclassproc(windowHandle,msg,wparam,lparam);
}
}
除了复制/粘贴/剪切操作被阻止的事实外,此方法都有效。当我尝试这样做时,什么也没发生。
这使我感到困惑,因为微软表示这些操作是由WM_copY
,WM_PASTE
和WM_CUT
消息处理的,这些消息我都不会覆盖。但是我进行了测试,发现当我在编辑中键入 Ctrl + C , Ctrl + V 和 Ctrl + X 时,它会触发{包含键代码为WM_CHAR
,VK_CANCEL
和VK_IME_ON
(可能分别是我不记得)的{1}}消息。这很奇怪,因为这些键听起来都不像它们代表这些输入,并且在互联网上没有人说过它们。
如果我添加了将这些键码传递给VK_FINAL
而不是被拒绝的条件,则可以解决此问题。但是我不愿仅仅进行此修复并继续前进,因为我无法解释它为何起作用,并且我不知道由于这些关键代码的实际含义而可能会引入哪些错误。
那么,为什么覆盖DefSubclassproc()
会使复制/粘贴/剪切不再起作用?为什么这些似乎与这些输入无关的关键代码与它们相关联?以及如何允许我以一种不太怪异的方式复制/粘贴/剪切?
解决方法
根据MSDN上的Keyboard Input文档:
通过
TranslateMessage
函数将击键转换为字符,这是我们在模块1中首次看到的。此函数检查击键消息并将其转换为字符。 对于产生的每个字符,TranslateMessage
函数都会在窗口的消息队列中放置一条WM_CHAR
或WM_SYSCHAR
消息。 wParam 消息的em>参数包含UTF-16字符。...
某些CTRL键组合被转换为ASCII控制字符。例如,CTRL + A被转换为ASCII ctrl-A(SOH)字符(ASCII值0x01)。对于文本输入,通常应过滤掉控制字符。另外,请避免使用
WM_CHAR
来实现键盘快捷键。而是使用WM_KEYDOWN
条消息;甚至更好,请使用加速器表。加速器表将在下一个主题Accelerator Tables中进行描述。
因此,正在发生的情况是,应用程序的消息循环中的TranslateMessage()
将WM_KEYDOWN
条消息转换为 CTRL-C , CTRL-V ,和 CTRL-X 序列进入WM_CHAR
消息中,这些消息带有 ASCII控制字符 0x03(ASCII ETX
,又名^C
),0x16(
SYN
(又名^V
)和0x18(ASCII CAN
,又名^X
)。
WM_CHAR
带有翻译的字符代码,而不是virtual key codes,这就是VK_CANCEL
(0x03),VK_IME_ON
(0x16)和{ {1}}(0x18)使您感到困惑。 VK_FINAL
中未使用虚拟键码。 WM_CHAR
和VK_RETURN
(但不是VK_BACK
)在您的过滤器中起作用的原因是,根据{,这些键被翻译转换为ASCII控制字符。 {3}}文档:
当Using Keyboard Input函数转换与字符键相对应的虚拟键代码时,窗口过程会收到字符消息。字符消息是
TranslateMessage
,WM_CHAR
,WM_DEADCHAR
和WM_SYSCHAR
。典型的窗口过程会忽略除VK_DELETE
之外的所有字符消息。 当用户按下以下任意键时,WM_CHAR
函数会生成一条TranslateMessage
消息:
- 任何字符键
- 后退
- ENTER(回车)
- ESC
- SHIFT + ENTER(换行)
- TAB
ENTER 转换为ASCII控制字符0x0D(ASCII WM_CHAR
,又名CR
),该数字与^M
相同。
BACKSPACE 转换为ASCII控制字符0x08(ASCII VK_RETURN
,又名BS
),该数字与^H
相同。
请注意, DELETE 密钥不在转换后的密钥列表中,因此标准的 DELETE 密钥不会生成VK_BACK
消息,因为存在没有要删除的ASCII控制字符(但是,数字键盘上的 DEL(。)键可能会生成带有WM_CHAR
的{{1}}消息。在这种情况下, WM_CHAR
将为1)。
因此,VK_DELETE
会将用于剪贴板操作的特殊lParam
消息分别转换为DefWindowProc()
,WM_CHAR
和WM_COPY
消息。但是,您正在过滤掉这些邮件,以使它们不会到达WM_PASTE
,因此也不会到达WM_CUT
。
因此,正如您已经发现的,您确实需要允许这些消息通过您的过滤,例如:
DefSubclassProc()