0

我想创建一个编辑控件,用户只能在其中输入浮点数,但我也希望能够在此编辑中复制/粘贴/剪切文本。因此,我使用以下窗口过程对编辑控件进行了子类化:

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_COPYWM_PASTEWM_CUT消息处理的,我什至没有覆盖它们。但我测试发现,当我在编辑中输入Ctrl+CCtrl+V和时Ctrl+X,它会触发一条WM_CHAR带有键码VK_CANCELVK_IME_ON和的消息VK_FINAL(可能分别是,我不记得了)。这很奇怪,因为这些键听起来都不像代表这些输入,而且互联网上没有任何人说它们代表这些输入。

如果我添加这些关键代码被传递DefSubclassProc()而不是被拒绝的条件,它可以解决问题。但是我很犹豫是否接受这个修复并继续前进,因为我无法解释它为什么会起作用,而且我不知道它可能会引入什么错误,这是由这些关键代码的实际含义引起的。

那么,为什么覆盖WM_CHAR使复制/粘贴/剪切不再起作用?为什么这些看似与这些输入无关的关键代码会与它们相关联?以及如何以一种不那么老套的方式允许复制/粘贴/剪切?

4

1 回答 1

2

根据MSDN 上的键盘输入文档:

我们在模块 1 中首次看到的函数将击键转换TranslateMessage为字符。该函数检查按键消息并将其转换为字符。对于产生的每个字符,该TranslateMessage函数将一个WM_CHARWM_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_CHARETX^CSYN^VCAN^X

WM_CHAR带有翻译的字符代码,而不是虚拟键代码,这就是为什么VK_CANCEL(0x03)、VK_IME_ON(0x16) 和VK_FINAL(0x18) 会让您感到困惑。中不使用虚拟键码WM_CHARVK_RETURNVK_BACK(但不是VK_DELETE)在您的过滤中“起作用”的原因是因为这些键被转换ASCII 控制字符,根据使用键盘输入文档:

TranslateMessage函数翻译对应于字符键的虚拟键码时,窗口过程接收字符消息。字符消息是WM_CHARWM_DEADCHARWM_SYSCHARWM_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_COPYWM_PASTEWM_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);
}
于 2020-08-22T00:58:04.560 回答