如何在带有 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
):
TColorGrid
、TToolBar
、TCustomGrid
、TMediaPlayer
、TCustomRibbon
、TRibbonSpinButton
、TSpinButton
、TTabbedNotebook
、TCustomCombo
和 TTabSet
在你的 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如何实现可用于拦截关键消息的各种挂钩:
我做的有点不同(不需要 WinAPI)。如果您的 VCL 应用程序没有关注任何子组件(如 TButton,TEdit,TMemo
...),那么主窗体键盘事件(OnKeyDown
和 OnKeyUp
)将触发箭头键使用。我知道有两种方法:
不使用带焦点的组件
这听起来可能很傻,但您可以使用 TSpeedButton
而不是 TButton
。但本质上,您可以将所有可聚焦组件放在某些子 window 或页面中,并在不需要时将其隐藏(如设置 window)。
需要箭头时禁用焦点
只是我做了一个 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.
的功能
PS 希望您将 KeyPreview
属性 设置为 true
.
我有一个 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
):
TColorGrid
、TToolBar
、TCustomGrid
、TMediaPlayer
、TCustomRibbon
、TRibbonSpinButton
、TSpinButton
、TTabbedNotebook
、TCustomCombo
和 TTabSet
在你的 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如何实现可用于拦截关键消息的各种挂钩:
我做的有点不同(不需要 WinAPI)。如果您的 VCL 应用程序没有关注任何子组件(如 TButton,TEdit,TMemo
...),那么主窗体键盘事件(OnKeyDown
和 OnKeyUp
)将触发箭头键使用。我知道有两种方法:
不使用带焦点的组件
这听起来可能很傻,但您可以使用
TSpeedButton
而不是TButton
。但本质上,您可以将所有可聚焦组件放在某些子 window 或页面中,并在不需要时将其隐藏(如设置 window)。需要箭头时禁用焦点
只是我做了一个
unfocus
函数,如下所示:void main_unfocus() { Main->bt_unfocus->Visible=true; Main->bt_unfocus->SetFocus(); Main->bt_unfocus->Visible=false; }
其中
bt_unfocus
是不可见的 2x2TButton
,位于主要 windowMain
的左上角。现在,每当我需要在主窗体事件中捕获鼠标滚轮、箭头键等事件时,我只需调用main_unfocus();
我通常在鼠标点击某个查看区域或鼠标在其上移动等时调用它,而且我在任何此类组件(如
TButton,TMemo,TEdit
上点击转义时调用它,因为它们通常在我的组件中共享相同的事件处理程序应用。一切都取决于您 need/want. 的功能
PS 希望您将 KeyPreview
属性 设置为 true
.