How to capture KeyDown when focused controls interfere?

1k Views Asked by At

I have a form with KeyPreview=true and want to capture the arrow keys, unless we are in a control that should handle those. The issue is: focus is always on one of those controls.

How can I adapt/design this to work?

.PAS file

unit uKeyDownTests;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;

type
  TFrmKeyDownTests = class(TForm)
    PnlBottom: TPanel;
    PnlClient: TPanel;
    Edit1: TEdit;
    Edit2: TEdit;
    Edit3: TEdit;
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  private
  public
  end;

var
  FrmKeyDownTests: TFrmKeyDownTests;

implementation

{$R *.dfm}

type
  THackWinControl = class(TWinControl);

procedure TFrmKeyDownTests.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
var lActiveControl: TControl;
begin
   // Earlier code, but that did not work either:
   // if Edit1.Focused or Edit2.Focused or Edit3.Focused then Exit;

   lActiveControl := ActiveControl;
   if Assigned(lActiveControl) then
   begin
      if lActiveControl = Edit1 then
      begin
          THackWinControl(Edit1).KeyDown(Key,Shift);
          Exit;
      end;
      if lActiveControl = Edit2 then
      begin
          THackWinControl(Edit2).KeyDown(Key,Shift);
          Exit;
      end;
      if lActiveControl = Edit3 then
      begin
          THackWinControl(Edit3).KeyDown(Key,Shift);
          Exit;
      end;
   end;

   if (Key = VK_RIGHT) then
   begin
      PnlBottom.Caption := PnlBottom.Caption + 'R';
      Key := 0;
      Exit;
   end;
   if (Key = VK_LEFT) then
   begin
      PnlBottom.Caption := PnlBottom.Caption + 'L';
      Key := 0;
      Exit;
   end;
   if (Key = VK_UP) then
   begin
      PnlBottom.Caption := PnlBottom.Caption + 'U';
      Key := 0;
      Exit;
   end;
   if (Key = VK_DOWN) then
   begin
      PnlBottom.Caption := PnlBottom.Caption + 'D';
      Key := 0;
      Exit;
   end;
end;

end.

.DFM file

object FrmKeyDownTests: TFrmKeyDownTests
  Left = 0
  Top = 0
  Caption = 'Keydown tests'
  ClientHeight = 336
  ClientWidth = 635
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  KeyPreview = True
  OldCreateOrder = False
  OnKeyDown = FormKeyDown
  PixelsPerInch = 96
  TextHeight = 13
  object PnlBottom: TPanel
    Left = 0
    Top = 295
    Width = 635
    Height = 41
    Align = alBottom
    TabOrder = 0
  end
  object PnlClient: TPanel
    Left = 0
    Top = 0
    Width = 635
    Height = 295
    Align = alClient
    TabOrder = 1
    object Edit1: TEdit
      Left = 40
      Top = 32
      Width = 121
      Height = 21
      TabOrder = 0
      Text = 'Edit1'
    end
    object Edit2: TEdit
      Left = 40
      Top = 72
      Width = 121
      Height = 21
      TabOrder = 1
      Text = 'Edit1'
    end
    object Edit3: TEdit
      Left = 40
      Top = 112
      Width = 121
      Height = 21
      TabOrder = 2
      Text = 'Edit1'
    end
  end
end
1

There are 1 best solutions below

0
On

(Answering my own question for my specific situation, slightly different from the one in the 'Possible dupe', but based on the answers there)
In my case, the easiest solution was:

  • procedure DialogKey(var Msg: TWMKey); message CM_DIALOGKEY; which only calls inherited
  • KeyPreview=true for the form
  • A FormKeydown that handles what I want to do with arrow keys

Result:

  • The controls that have focus as well as the form handle the arrow keys
  • It does not matter if the controls have an OnKeyDown handler (the Edit2 control) or not (the others)

Modified code:

unit uKeyDownTests;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;

type
  TFrmKeyDownTests = class(TForm)
    PnlBottom: TPanel;
    PnlClient: TPanel;
    Edit1: TEdit;
    Edit2: TEdit;
    Edit3: TEdit;
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure Edit2KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  private
    procedure DialogKey(var Msg: TWMKey); message CM_DIALOGKEY;  public
  end;

var
  FrmKeyDownTests: TFrmKeyDownTests;

implementation

{$R *.dfm}

procedure TFrmKeyDownTests.DialogKey(var Msg: TWMKey);
begin
   inherited;
end;

procedure TFrmKeyDownTests.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
   case Key of
      VK_RIGHT: PnlBottom.Caption := PnlBottom.Caption + 'R';
      VK_LEFT : PnlBottom.Caption := PnlBottom.Caption + 'L';
      VK_UP   : PnlBottom.Caption := PnlBottom.Caption + 'U';
      VK_DOWN : PnlBottom.Caption := PnlBottom.Caption + 'D';
   end;
end;


{ TFrmKeyDownTests }

procedure TFrmKeyDownTests.Edit2KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
   PnlBottom.Caption := PnlBottom.Caption + '-kd-';
end;

end.