Delphi XE 和使用 OnKeyDown 捕获箭头键

发布于 2024-12-23 02:22:35 字数 48 浏览 5 评论 0原文

我希望我的表单能够处理箭头键,而且我可以做到——只要表单上没有按钮。这是为什么呢?

I want my form to handle the arrow keys, and I can do it -- as long as there is no button on the form. Why is this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

歌入人心 2024-12-30 02:22:35

关键消息由接收这些消息的控件本身进行处理,这就是为什么当您使用按钮时表单不会接收消息。因此,通常您必须对这些控件进行子类化,但 VCL 足够友善地询问父子表单如果感兴趣,该怎么办:

type
  TForm1 = class(TForm)
    ..
  private
    procedure DialogKey(var Msg: TWMKey); message CM_DIALOGKEY;
    ..


procedure TForm1.DialogKey(var Msg: TWMKey); 
begin
  if not (Msg.CharCode in [VK_DOWN, VK_UP, VK_RIGHT, VK_LEFT]) then
    inherited;
end;

François 编辑: 要回答 OP 原始问题,您需要以某种方式调用 onKeyDown 以便他的事件代码可以工作(随意编辑;评论太长)。

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  private
    { Private declarations }
    procedure DialogKey(var Msg: TWMKey); message CM_DIALOGKEY;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.DialogKey(var Msg: TWMKey);
begin
  case Msg.CharCode of
    VK_DOWN, VK_UP, VK_RIGHT, VK_LEFT:
      if Assigned(onKeyDown) then
        onKeyDown(Self, Msg.CharCode, KeyDataToShiftState(Msg.KeyData));
    else
      inherited
  end;
end;

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  case Key of
    VK_DOWN: Top := Top + 5;
    VK_UP: Top := Top - 5;
    VK_LEFT: Left := Left - 5;
    VK_RIGHT: Left := Left + 5;
  end;
end;

Key messages are processed by the controls themselves who receives these messages, that's why when you're on a button the form is not receiving the message. So normally you would have to subclass these controls, but the VCL is kind enough to ask the parenting form what to do if the form is interested:

type
  TForm1 = class(TForm)
    ..
  private
    procedure DialogKey(var Msg: TWMKey); message CM_DIALOGKEY;
    ..


procedure TForm1.DialogKey(var Msg: TWMKey); 
begin
  if not (Msg.CharCode in [VK_DOWN, VK_UP, VK_RIGHT, VK_LEFT]) then
    inherited;
end;

François editing: to answer the OP original question, you need to call onKeyDown somehow so that his event code would work (feel free to edit; was too long for a comment).

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  private
    { Private declarations }
    procedure DialogKey(var Msg: TWMKey); message CM_DIALOGKEY;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.DialogKey(var Msg: TWMKey);
begin
  case Msg.CharCode of
    VK_DOWN, VK_UP, VK_RIGHT, VK_LEFT:
      if Assigned(onKeyDown) then
        onKeyDown(Self, Msg.CharCode, KeyDataToShiftState(Msg.KeyData));
    else
      inherited
  end;
end;

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  case Key of
    VK_DOWN: Top := Top + 5;
    VK_UP: Top := Top - 5;
    VK_LEFT: Left := Left - 5;
    VK_RIGHT: Left := Left + 5;
  end;
end;
扮仙女 2024-12-30 02:22:35

箭头键用于在表单上的按钮之间导航。这是标准的 Windows 行为。尽管您可以禁用此标准行为,但在违反平台标准之前您应该三思而后行。箭头键用于导航。

如果您想全面了解按键如何通过消息循环,我建议您阅读 一把钥匙的奥德赛。如果您想在按键变成导航键之前拦截该按键,则需要在 IsKeyMsg 或更早版本中执行此操作。例如,Sertac 的答案就给出了这样一种可能性。

Arrow keys are used to navigate between buttons on a form. This is standard Windows behaviour. Although you can disable this standard behaviour you should think twice before going against the platform standard. Arrow keys are meant for navigation.

If you want to get the full low down on how a key press finds its way through the message loop I recommend reading A Key's Odyssey. If you want to intercept the key press before it becomes a navigation key, you need to do so in IsKeyMsg or earlier. For example, Sertac's answer gives one such possibility.

你的笑 2024-12-30 02:22:35

只有具有焦点的对象才能接收键盘事件。

要让表单能够访问箭头键事件,
在表单的公共部分声明一个 MsgHandler
在表单创建构造函数中,将 Application.OnMessage 分配给此 MsgHandler。

下面的代码仅在箭头键来自 TButton 后代时拦截它们。可以根据需要添加更多控件。

procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.OnMessage := Self.MsgHandler;
end;

procedure TForm1.MsgHandler(var Msg: TMsg; var Handled: Boolean);
var
  ActiveControl: TWinControl;
  key : word;
begin
  if (Msg.message = WM_KEYDOWN) then
    begin
      ActiveControl := Screen.ActiveControl;
      // if the active control inherits from TButton, intercept the key.
      // add other controls as fit your needs 
      if not ActiveControl.InheritsFrom(TButton)
        then Exit;

      key := Msg.wParam;
      Handled := true;
      case Key of // intercept the wanted keys
        VK_DOWN : ; // doStuff
        VK_UP : ; // doStuff
        VK_LEFT : ; // doStuff
        VK_RIGHT : ; // doStuff
        else Handled := false;
      end;
   end;
end;

Only the object that has the focus can receive a keyboard event.

To let the form have access to the arrow keys event,
declare a MsgHandler in the public part of the form.
In the form create constructor, assign the Application.OnMessage to this MsgHandler.

The code below intercepts the arrow keys only if they are coming from a TButton descendant. More controls can be added as needed.

procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.OnMessage := Self.MsgHandler;
end;

procedure TForm1.MsgHandler(var Msg: TMsg; var Handled: Boolean);
var
  ActiveControl: TWinControl;
  key : word;
begin
  if (Msg.message = WM_KEYDOWN) then
    begin
      ActiveControl := Screen.ActiveControl;
      // if the active control inherits from TButton, intercept the key.
      // add other controls as fit your needs 
      if not ActiveControl.InheritsFrom(TButton)
        then Exit;

      key := Msg.wParam;
      Handled := true;
      case Key of // intercept the wanted keys
        VK_DOWN : ; // doStuff
        VK_UP : ; // doStuff
        VK_LEFT : ; // doStuff
        VK_RIGHT : ; // doStuff
        else Handled := false;
      end;
   end;
end;
自此以后,行同陌路 2024-12-30 02:22:35

因为它们会抢先处理将焦点设置到下一个可用的 WinControl 上的问题。
(我很确定,如果您放置“编辑”而不是“按钮”,您会看到相同的结果)。

如果您想自己处理它们,您可以为应用程序提供一个 OnMessage 事件,该事件将在处理它们之前对其进行过滤并在那里自行处理。

Because they are preempted to deal with setting the focus on the next available WinControl.
(I'm pretty sure that if you put an Edit instead of a Button you see the same thing).

If you want to handle them yourself, you can provide the Application with an OnMessage event that will filter those before they are processed and handle them yourself there.

辞别 2024-12-30 02:22:35
var
KBHook: HHook; {this intercepts keyboard input}

implementation

{$R *.dfm}

function KeyboardHookProc(Code: Integer; WordParam: Word; LongParam: LongInt): LongInt; stdcall;
 begin
 case WordParam of
   vk_Space: ShowMessage ('space')  ;
   vk_Right:ShowMessage ('rgt') ;
   vk_Left:ShowMessage ('lft') ;
   vk_Up: ShowMessage ('up') ;
   vk_Down: ShowMessage ('down') ;
  end; {case}
 end;

procedure TForm4.FormCreate(Sender: TObject);
begin
KBHook:=SetWindowsHookEx(WH_KEYBOARD,@KeyboardHookProc,HInstance,GetCurrentThreadId());
end;

即使控件获得焦点(按钮、列表框),此代码也将起作用,因此请小心某些控件可能会丢失其键盘事件(请阅读 David haffernans 的回答)。

带有聚焦控件的键盘事件

例如:如果您的应用程序中有文本框并且还想接收文本(如果聚焦),则

添加一个 applicationevent1

procedure TForm4.ApplicationEvents1Message(var Msg: tagMSG;var Handled: Boolean);
begin
if Msg.message = WM_KEYFIRST then
  KBHook:=SetWindowsHookEx(WH_KEYBOARD,@KeyboardHookProc,HInstance,GetCurrentThreadId());
end;

函数的底部添加以下代码KeyboardHookProc

UnhookWindowsHookEx(KBHook);

删除。

KBHook:=SetWindowsHookEx(WH_KEYBOARD,@KeyboardHookProc, HInstance, 
GetCurrentThreadId());

并从 oncreate 事件中

var
KBHook: HHook; {this intercepts keyboard input}

implementation

{$R *.dfm}

function KeyboardHookProc(Code: Integer; WordParam: Word; LongParam: LongInt): LongInt; stdcall;
 begin
 case WordParam of
   vk_Space: ShowMessage ('space')  ;
   vk_Right:ShowMessage ('rgt') ;
   vk_Left:ShowMessage ('lft') ;
   vk_Up: ShowMessage ('up') ;
   vk_Down: ShowMessage ('down') ;
  end; {case}
 end;

procedure TForm4.FormCreate(Sender: TObject);
begin
KBHook:=SetWindowsHookEx(WH_KEYBOARD,@KeyboardHookProc,HInstance,GetCurrentThreadId());
end;

This code will work even when a control is focused (buttons , listboxes), so be careful some controls may loose their keyboard events (Read David haffernans answer) .

keyboard events with Focused controls

eg: If you are having textbox in your app and want to recive text(if focused) also , then

add an applicationevent1

procedure TForm4.ApplicationEvents1Message(var Msg: tagMSG;var Handled: Boolean);
begin
if Msg.message = WM_KEYFIRST then
  KBHook:=SetWindowsHookEx(WH_KEYBOARD,@KeyboardHookProc,HInstance,GetCurrentThreadId());
end;

add the following code at the bottom of the function KeyboardHookProc

UnhookWindowsHookEx(KBHook);

and remove

KBHook:=SetWindowsHookEx(WH_KEYBOARD,@KeyboardHookProc, HInstance, 
GetCurrentThreadId());

from oncreate event.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文