子类编辑控件,而不会破坏复制/粘贴

问题描述

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

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 + C Ctrl + V Ctrl + X 时,它会触发{包含键代码WM_CHARVK_CANCELVK_IME_ON(可能分别是我不记得)的{1}}消息。这很奇怪,因为这些键听起来都不像它们代表这些输入,并且在互联网上没有人说过它们。

如果我添加了将这些键码传递给VK_FINAL而不是被拒绝的条件,则可以解决此问题。但是我不愿仅仅进行此修复并继续前进,因为我无法解释它为何起作用,并且我不知道由于这些关键代码的实际含义而可能会引入哪些错误

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

解决方法

根据MSDN上的Keyboard Input文档:

通过TranslateMessage函数将击键转换为字符,这是我们在模块1中首次看到的。此函数检查击键消息并将其转换为字符。 对于产生的每个字符,TranslateMessage函数都会在窗口的消息队列中放置一条WM_CHARWM_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(

分别为ASCII SYN(又名^V)和0x18(ASCII CAN,又名^X)。

WM_CHAR带有翻译的字符代码,而不是virtual key codes,这就是VK_CANCEL(0x03),VK_IME_ON(0x16)和{ {1}}(0x18)使您感到困惑。 VK_FINAL中未使用虚拟键码。 WM_CHARVK_RETURN(但不是VK_BACK)在您的过滤器中起作用的原因是,根据{,这些键被翻译转换为ASCII控制字符。 {3}}文档:

Using Keyboard Input函数转换与字符键相对应的虚拟键代码时,窗口过程会收到字符消息。字符消息是TranslateMessageWM_CHARWM_DEADCHARWM_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_CHARWM_COPY消息。但是,您正在过滤掉这些邮件,以使它们不会到达WM_PASTE,因此也不会到达WM_CUT

因此,正如您已经发现的,您确实需要允许这些消息通过您的过滤,例如:

DefSubclassProc()