不破坏复制/粘贴的子类编辑控件
Posted
技术标签:
【中文标题】不破坏复制/粘贴的子类编辑控件【英文标题】:Subclass edit control without ruining copy/paste 【发布时间】:2020-08-22 00:30:54 【问题描述】:我想创建一个编辑控件,用户只能在其中输入浮点数,但我还希望能够在此编辑中复制/粘贴/剪切文本。因此,我使用以下窗口过程对编辑控件进行了子类化:
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 时,它会触发一个 @ 987654325@ 带有密钥代码VK_CANCEL
、VK_IME_ON
和VK_FINAL
的消息(可能分别是,我不记得了)。这很奇怪,因为这些键听起来都不像代表这些输入,而且互联网上也没有人说它们代表这些输入。
如果我添加将这些密钥代码传递给DefSubclassProc()
而不是被拒绝的条件,则可以解决问题。但是我很犹豫是否接受这个修复并继续前进,因为我无法解释它为什么会起作用,而且我不知道它可能会引入什么错误,这是由这些关键代码的实际含义引起的。
那么,为什么覆盖 WM_CHAR
会使复制/粘贴/剪切不再起作用?为什么这些看似与这些输入无关的关键代码会与它们相关联?以及如何允许以不那么老套的方式进行复制/粘贴/剪切?
【问题讨论】:
如果你想阻止他们粘贴无效字符,不要忘记处理WM_PASTE
。
【参考方案1】:
根据 MSDN 上的 Keyboard Input 文档:
击键通过
TranslateMessage
函数转换为字符,我们在模块 1 中首次看到该函数。该函数检查按键消息并将其转换为字符。 对于产生的每个字符,TranslateMessage
函数将WM_CHAR
或WM_SYSCHAR
消息放入窗口的消息队列中。 消息的wParam 参数包含 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 序列化为带有 ASCII 控制字符 0x03(ASCII ETX
,又名^C
)、0x16(ASCII SYN
,又名^V
)的WM_CHAR
消息, 和 0x18 (ASCII CAN
, aka ^X
)。
WM_CHAR
带有翻译的字符代码,而不是 virtual key codes,这就是为什么 VK_CANCEL
(0x03)、VK_IME_ON
(0x16) 和 VK_FINAL
(0x18) 会让您感到困惑. WM_CHAR
中不使用虚拟键码。 VK_RETURN
和 VK_BACK
(但不是 VK_DELETE
)在您的过滤中“起作用”的原因是因为根据 Using Keyboard Input 文档,这些键被翻译为 ASCII 控制字符:
当
任意字符键 退格键 ENTER(回车) ESC SHIFT+ENTER(换行) 标签TranslateMessage
函数转换对应于字符键的虚拟键代码时,窗口过程会收到字符消息。字符消息是WM_CHAR
、WM_DEADCHAR
、WM_SYSCHAR
和WM_SYSDEADCHAR
。典型的窗口过程会忽略除WM_CHAR
之外的所有字符消息。 当用户按下以下任意键时,TranslateMessage
函数会生成WM_CHAR
消息:
ENTER 被翻译成 ASCII 控制字符 0x0D(ASCII CR
,又名^M
),与VK_RETURN
是相同的数值。
BACKSPACE 被翻译成 ASCII 控制字符 0x08(ASCII BS
,又名^H
),与VK_BACK
是相同的数值。
注意 DELETE 键不在已翻译键列表中,因此标准 DELETE 键不会生成WM_CHAR
消息,因为没有 ASCII用于删除的控制字符(但是,数字键盘上的 DEL (.) 键可能会生成带有 VK_DELETE
的 WM_CHAR
消息。在这种情况下,lParam
的第 24 位将为 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);
【讨论】:
以上是关于不破坏复制/粘贴的子类编辑控件的主要内容,如果未能解决你的问题,请参考以下文章