Arrow keys are reserved by Windows for navigation purposes. If a window wants to handle arrow key messages, it must respond to the WM_GETDLGCODE
message and include the DLGC_WANTARROWS
(or DLGC_WANTALLKEYS
) flag in its return value.
Most VCL UI components (including TForm
) return 0 for WM_GETDLGCODE
. Only a handful of standard VCL components respond with DLGC_WANTARROWS
(or DLGC_WANTALLKEYS
):
TColorGrid
, TToolBar
, TCustomGrid
, TMediaPlayer
, TCustomRibbon
, TRibbonSpinButton
, TSpinButton
, TTabbedNotebook
, TCustomCombo
, and TTabSet
In your TForm
class, override its virtual WndProc()
method, or declare a message
handler, to handle WM_GETDLGCODE
, then you can return whatever flag(s) you need.
void __fastcall TMyForm::WndProc(TMessage &Message)
{
TForm::WndProc(Message);
if (Message.Msg == WM_GETDLGCODE)
Message.Result |= DLGC_WANTARROWS;
}
Update: If WM_GETDLGCODE
doesn't work, try responding to the CM_WANTSPECIALKEY
message instead:
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;
}
}
}
Update: you are trying to process key events in the Form itself, which will only work when:
the Form window itself has input focus.
a child VCL window receives key messages, and the Form's KeyPreview
property is true.
When a windowed child control, like a button, has the input focus, key messages go to that window, not to the Form window. In order for the KeyPreview
property to work for arrow keys, the focused child must respond to the WM_GETDLGCODE
message asking the OS to send arrow key messages. Only then can the child forward the messages to the Form for processing.
A button doesn't ask for arrow key messages by default, so you would have to subclass the button to handle the WM_GETDLGCODE
message manually, eg:
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!
}
That is fine if you have only a few controls to subclass, but if you have a lot of controls, an alternative solution would be to have your Form class handle the CM_DIALOGKEY
message instead:
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;
}
}
}
You should read the following article on EDN, it has a good explanation of how the VCL processes key messages, and how the VCL implements various hooks you can use to intercept key messages:
A Key’s Odyssey