如何在带有 TButtons 的 VCL 表单应用程序中使用键盘箭头进行输入

How to use keyboard arrows for input in a VCL forms application with TButtons

我有一个 VCL 表单应用程序,我在其中拦截在 OnKeyPress 和 OnKeyDown 处理程序中按下的键。这适用于常规字符和键,例如 VK_ESC。

然而,当试图抓住箭头键(VK_UP 和 VK_DOWN)时,它不起作用,因为它们似乎只是在改变主窗体上控件的焦点,并且没有触发主窗体 OnKeyDown 处理程序。

catch/process 那些按键是怎么操作的?

更新: 根据以下答案尝试以下操作,但仍然没有成功。

void __fastcall TMain::WndProc(TMessage& Message)
{
  switch (Message.Msg)
  {
    case WM_GETDLGCODE:
        Log(lInfo) << "Got WM_GETDLGCODE";
    break;
    case CM_WANTSPECIALKEY:
        Log(lInfo) << "Got WM_WANTSPECIALKEY";
    break;

None 以上情况是通过在我的应用程序中使用 up/down 箭头触发的。

我有以下观察。如果创建一个空的vcl表单,上面的情况触发,CM_WANSPECIALKEY先触发,然后是WM_GETDLGCODE。

但是,只要将 TButton 放在窗体上,就不会再触发案例。

我正在使用 C++ Builder XE3。

箭头键由 Windows 保留用于导航目的。如果 window 想要处理箭头键消息,它必须响应 WM_GETDLGCODE 消息并在其 return 值中包含 DLGC_WANTARROWS(或 DLGC_WANTALLKEYS)标志.

大多数 VCL UI 组件(包括 TForm)return WM_GETDLGCODE 0。只有少数标准 VCL 组件响应 DLGC_WANTARROWS(或 DLGC_WANTALLKEYS):

TColorGridTToolBarTCustomGridTMediaPlayerTCustomRibbonTRibbonSpinButtonTSpinButtonTTabbedNotebookTCustomComboTTabSet

在你的 TForm class 中,覆盖它的虚拟 WndProc() 方法,或者声明一个 message 处理程序来处理 WM_GETDLGCODE,然后你可以 return 你需要的任何标志。

void __fastcall TMyForm::WndProc(TMessage &Message)
{
    TForm::WndProc(Message);
    if (Message.Msg == WM_GETDLGCODE)
        Message.Result |= DLGC_WANTARROWS;
}

更新:如果 WM_GETDLGCODE 不起作用,请尝试回复 CM_WANTSPECIALKEY 消息:

void __fastcall TMyForm::WndProc(TMessage &Message)
{
    TForm::WndProc(Message);
    if (Message.Msg == CM_WANTSPECIALKEY)
    {
        switch (reinterpret_cast<TCMWantSpecialKey&>(Message).CharCode)
        {
            case VK_LEFT:
            case VK_RIGHT:
            case VK_UP:
            case VK_DOWN:
                Message.Result = 1;
                break;
        }
    }
}

更新:您正在尝试在表单本身中处理关键事件,这仅在以下情况下有效:

  • 表单 window 本身有输入焦点。

  • a childVCL window收到关键信息,Form的KeyPreview属性为真

当 windowed child 控件(如按钮)具有输入焦点时,关键消息将转到 window,而不是表单 window。为了使 KeyPreview 属性 为箭头键工作,获得焦点的 child 必须响应 WM_GETDLGCODE 要求 OS 发送箭头键消息的消息。只有这样 child 才能将消息转发给表单进行处理。

默认情况下,按钮不要求箭头键消息,因此您必须子class按钮以手动处理WM_GETDLGCODE消息,例如:

class TMain : public TForm
{
__published:
    TButton *Button1;
    void __fastcall FormKeyDown(TObject *Sender, Word &Key, TShiftState Shift);
    void __fastcall FormKeyPress(TObject *Sender, Char &Key);
private:
    TWndMethod PrevBtnWndProc;
    void __fastcall BtnWndProc(TMessage &Message);
public:
    __fastcall TMain(TComponent *Owner);
};

__fastcall TMain::TMain(TComponent *Owner)
    : TForm(Owner)
{
    PrevBtnWndProc = Button1->WindowProc;
    Button1->WindowProc = &BtnWndProc;
}

void __fastcall TMain::BtnWndProc(TMessage &Message
{
    PrevBtnWndProc(Message);
    if (Message.Msg == WM_GETDLGCODE)
        Message.Result |= DLGC_WANTARROWS;
}

void __fastcall TMain::FormKeyDown(TObject *Sender, Word &Key, TShiftState Shift)
{
    // works now!
}

void __fastcall TForm7::FormKeyPress(TObject *Sender, Char &Key)
{
    // works now!
}

如果您只有几个控件可以子class,那很好,但是如果您有很多控件,另一种解决方案是让您的表单 class 处理 CM_DIALOGKEY 消息改为:

void __fastcall TMyForm::WndProc(TMessage &Message)
{
    TForm::WndProc(Message);
    if (Message.Msg == CM_DIALOGKEY)
    {
        switch (reinterpret_cast<TCMDialogKey&>(Message).CharCode)
        {
            case VK_LEFT:
            case VK_RIGHT:
            case VK_UP:
            case VK_DOWN:
                // process key as needed...
                // set Message.Result to suppress the message further...
                Message.Result = 1;
                break;
        }
    }
}

你应该阅读EDN上的以下文章,它很好地解释了VCL如何处理关键消息,以及VCL如何实现可用于拦截关键消息的各种挂钩:

A Key’s Odyssey

我做的有点不同(不需要 WinAPI)。如果您的 VCL 应用程序没有关注任何子组件(如 TButton,TEdit,TMemo...),那么主窗体键盘事件(OnKeyDownOnKeyUp)将触发箭头键使用。我知道有两种方法:

  1. 不使用带焦点的组件

    这听起来可能很傻,但您可以使用 TSpeedButton 而不是 TButton。但本质上,您可以将所有可聚焦组件放在某些子 window 或页面中,并在不需要时将其隐藏(如设置 window)。

  2. 需要箭头时禁用焦点

    只是我做了一个 unfocus 函数,如下所示:

    void main_unfocus()
        {
        Main->bt_unfocus->Visible=true;
        Main->bt_unfocus->SetFocus();
        Main->bt_unfocus->Visible=false;
        }
    

    其中 bt_unfocus 是不可见的 2x2 TButton,位于主要 window Main 的左上角。现在,每当我需要在主窗体事件中捕获鼠标滚轮、箭头键等事件时,我只需调用 main_unfocus();

    我通常在鼠标点击某个查看区域或鼠标在其上移动等时调用它,而且我在任何此类组件(如 TButton,TMemo,TEdit 上点击转义时调用它,因为它们通常在我的组件中共享相同的事件处理程序应用。一切都取决于您 need/want.

  3. 的功能

PS 希望您将 KeyPreview 属性 设置为 true.