Delphi XE and Trapping Arrow Key with OnKeyDown

后端 未结 5 1545
闹比i
闹比i 2020-12-06 17:21

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?

相关标签:
5条回答
  • 2020-12-06 17:52
    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.

    0 讨论(0)
  • 2020-12-06 17:55

    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.

    0 讨论(0)
  • 2020-12-06 18:00

    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;
    
    0 讨论(0)
  • 2020-12-06 18:02

    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.

    0 讨论(0)
  • 2020-12-06 18:03

    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;
    
    0 讨论(0)
提交回复
热议问题