c - 不破坏复制/粘贴的子类编辑控件
问题描述
我想创建一个编辑控件,用户只能在其中输入浮点数,但我也希望能够在此编辑中复制/粘贴/剪切文本。因此,我使用以下窗口过程对编辑控件进行了子类化:
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
和的消息VK_FINAL
(可能分别是,我不记得了)。这很奇怪,因为这些键听起来都不像代表这些输入,而且互联网上没有任何人说它们代表这些输入。
如果我添加这些关键代码被传递DefSubclassProc()
而不是被拒绝的条件,它可以解决问题。但是我很犹豫是否接受这个修复并继续前进,因为我无法解释它为什么会起作用,而且我不知道它可能会引入什么错误,这是由这些关键代码的实际含义引起的。
那么,为什么覆盖WM_CHAR
使复制/粘贴/剪切不再起作用?为什么这些看似与这些输入无关的关键代码会与它们相关联?以及如何以一种不那么老套的方式允许复制/粘贴/剪切?
解决方案
根据MSDN 上的键盘输入文档:
我们在模块 1 中首次看到的函数将击键转换
TranslateMessage
为字符。该函数检查按键消息并将其转换为字符。对于产生的每个字符,该TranslateMessage
函数将一个WM_CHAR
或WM_SYSCHAR
消息放入窗口的消息队列中。消息的wParam参数包含 UTF-16 字符。...
某些 CTRL 键组合被转换为 ASCII 控制字符。例如,CTRL+A 被转换为 ASCII ctrl-A (SOH) 字符(ASCII 值 0x01)。对于文本输入,您通常应该过滤掉控制字符。另外,避免使用
WM_CHAR
来实现键盘快捷键。相反,使用WM_KEYDOWN
消息;甚至更好的是,使用加速器表。加速器表在下一个主题加速器表中进行了描述。
因此,正在发生的事情是,TranslateMessage()
在您的应用程序的消息循环中,将、 和序列的消息分别转换WM_KEYDOWN
为携带ASCII 控制字符0x03(ASCII ,aka )、0x16(ASCII ,aka )和 0x18(ASCII ,aka )的消息。CTRL-CCTRL-VCTRL-XWM_CHAR
ETX
^C
SYN
^V
CAN
^X
WM_CHAR
带有翻译的字符代码,而不是虚拟键代码,这就是为什么VK_CANCEL
(0x03)、VK_IME_ON
(0x16) 和VK_FINAL
(0x18) 会让您感到困惑。中不使用虚拟键码WM_CHAR
。VK_RETURN
和VK_BACK
(但不是VK_DELETE
)在您的过滤中“起作用”的原因是因为这些键被转换为ASCII 控制字符,根据使用键盘输入文档:
当
TranslateMessage
函数翻译对应于字符键的虚拟键码时,窗口过程接收字符消息。字符消息是WM_CHAR
、WM_DEADCHAR
、WM_SYSCHAR
和WM_SYSDEADCHAR
。典型的窗口过程会忽略所有字符消息,除了WM_CHAR
. 当用户按下以下任何键时,该TranslateMessage
函数会生成一条WM_CHAR
消息:
- 任意字符键
- 退格
- ENTER(回车)
- ESC键
- SHIFT+ENTER(换行)
- 标签
ENTER被翻译成 ASCII 控制字符 0x0D (ASCII CR
, aka ^M
),与VK_RETURN
.
BACKSPACE被翻译成 ASCII 控制字符 0x08 (ASCII BS
, aka ^H
),与VK_BACK
.
请注意,该DELETE键不在翻译键列表中,因此标准DELETE键不会生成WM_CHAR
消息,因为没有用于删除的 ASCII 控制字符(但是,DEL (.)数字键盘上的键可能会生成WM_CHAR
带有 . 的消息VK_DELETE
。在这种情况下,第 24 位lParam
将为 1)。
因此,DefWindowProc()
将这些WM_CHAR
用于剪贴板操作的特殊消息分别转换为WM_COPY
、WM_PASTE
和WM_CUT
消息。但是,您正在过滤掉这些消息,因此它们不会到达DefSubclassProc()
,因此也不会到达DefWindowProc()
。
因此,正如您已经发现的那样,您确实需要允许这些消息通过您的过滤,例如:
LRESULT CALLBACK FloatTextboxWindowProc(HWND windowHandle, UINT msg, WPARAM wparam, LPARAM lparam, UINT_PTR subclassId, DWORD_PTR refData)
{
if (msg == WM_CHAR)
{
// If the character isn't a digit or a dot, rejecting it.
if (!(
(wparam >= '0' && wparam <= '9') ||
wparam == '.' ||
wparam == VK_RETURN ||
wparam == VK_DELETE ||
wparam == VK_BACK ||
wparam == 0x03 || // CTRL-C
wparam == 0x16 || // CTRL-V
wparam == 0x18) // CTRL-X
)
{
return 0;
}
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;
}
}
}
return DefSubclassProc(windowHandle, msg, wparam, lparam);
}
推荐阅读
- wordpress - 将 Wordpress 功能媒体 (wordpress-feature-media) 自定义为 ionic3 的 better_featured_image
- c++ - Cmake 编译添加 .cpp 与仅包含标头
- python - 运行 df.columns 后显示所有列
- .net - 如何证明电子邮件离开了我们的系统并进行了跟踪?
- javascript - 使用两个 JS 对象数组的对象属性值
- assembly - at&t 汇编器中的数组
- javascript - JavaScript 范围练习 - 无法弄清楚为什么结果是 10(而不是 30)
- sql-server - 自定义 SQL 查询到 HTML
- google-apps-script - 当 Google Apps 脚本将文件移动到其他文件夹时,云端硬盘文件的修改日期会发生变化
- java - maven错误:在包中找不到符号类