Link to home
Start Free TrialLog in
Avatar of Levente
Levente

asked on

Capturing arrow up and down in CustomControl

I've got a component which is the descendant of TCustomContol. I need to capture when user presses cursor up and down keys if my component has the focus.

Currently, I override the KeyDown method of TCustomControl but it executes when user presses an alphanumerical character. If I press the up or down arrows the focus jumps to the previous or the next component. (Capturing WM_KEYDOWN message doesn't help, either.)

Any ide how to capture the cursor moving keys?

Thanks,
Levente
Avatar of ZafodBiblbrox
ZafodBiblbrox

hi, try to use this code

type
  TCustomXEdit = class(TCustomEdit)
  private
    procedure CNKeyDown(var Message: TWMKey); message CN_KEYDOWN;
  protected
  end;


procedure TCustomXEdit.CNKeyDown(var Message: TWMKey);
var Mask: Integer;
    x: TKeyboardState;
    oldShift: Byte;
begin
  with Message do
    begin
    GetKeyboardState(x);
    oldShift := x[16];
    if CharCode = VK_UP then
      begin
      CharCode := VK_TAB;
      x[16] := 128;
      SetKeyboardState(x);
      end
    else if (CharCode = 13) or (CharCode = VK_DOWN) then
      begin
      CharCode := VK_TAB;
      x[16] := 1;
      SetKeyboardState(x);
      end;
    Result := 1;
    if not (csDesigning in ComponentState) then
      begin
      if Perform(CM_CHILDKEY, CharCode, Integer(Self)) <> 0 then
        begin
        x[16] := oldShift;
        SetKeyboardState(x);
        Exit;
        end;
      Mask := 0;
      case CharCode of
       VK_TAB: Mask := DLGC_WANTTAB;
       VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN: Mask := DLGC_WANTARROWS;
       VK_RETURN, VK_EXECUTE, VK_ESCAPE, VK_CANCEL: Mask := DLGC_WANTALLKEYS;
       end;
      if (Mask <> 0) and (Perform(CM_WANTSPECIALKEY, CharCode, 0) = 0) and
         (Perform(WM_GETDLGCODE, 0, 0) and Mask = 0) and
         (GetParentForm(Self).Perform(CM_DIALOGKEY, CharCode, KeyData) <> 0) then
        begin
        x[16] := oldShift;
        SetKeyboardState(x);
        Exit;
        end;
      end;
    x[16] := oldShift;
    SetKeyboardState(x);
    end;
  inherited;
end;



end.
ASKER CERTIFIED SOLUTION
Avatar of Cynna
Cynna

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Levente

ASKER

Cynna,

Your solution works. One more thing I noticed: TMyComponent appears in the Tab order list of the parent Form, but if I press tab to skip focus TMyComponent never gets focus. (I need to click on the component and manually call TMyComponent.SetFocus in the onClick handler to get focus.)

Is there anything that can be done to have my component get focus automatically?

Thank you,
Levente

ZafodBiblbrox, thank you for your solution, however I'd prefer Cynna's.
Levente,

I'm not sure if I understand you correctly. Could you
post a simple reproducible example?

(1) Are you saying that your component can't receive focus using Tab key? That shouldn't happen because of the code
segment I posted.

I just tried a simple component that uses this principle,
and it works just as expected.
It's really simple, you can copy/paste it directly
in your project and tell me how it went:

  TmyButton = class(TButton)
  private
    procedure WMGetDlgCode(var message: TWMGetDlgCode); message WM_GETDLGCODE;
  end;

(....)

procedure TmyButton.WMGetDlgCode(var message: TWMGetDlgCode);
begin
  inherited;
  message.result:=DLGC_WANTARROWS;
end;

(....)


procedure TForm1.FormCreate(Sender: TObject);
begin
  myButton1:=TmyButton.Create(Self);
  with myButton1 do begin
      Left:=150;
      Top:=70;
      Width:=40; Height:=20;
      Caption:='bla';
      Parent:=Form1;
  end;
end;



(2) Does this example do what you want?
If no, then I missunderstood you, please try to be more detailed.
If yes, then please try posting simple example-component, like the one above, that doesn't work OK, so I can reproduce your problem and try to solve it.


Since I'm not quite shure what exactly is the problem,
I'll now try a few solutions based on my guess on point (1):

a) try replacing line  
         message.result:=DLGC_WANTARROWS;
   with:
         message.result:=message.result or DLGC_WANTARROWS;


b) if that doesn't work, you might override KeyDown event
   and handle focus change yourself if pressed key is Tab:

  private
    procedure WMGetDlgCode(var message: TWMGetDlgCode); message WM_GETDLGCODE;
    // override KeyDown for special preprocessing:
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
  end;


... and in implemetion:

procedure TmyButton.WMGetDlgCode(var message: TWMGetDlgCode);
begin
  inherited;
  // We now want to catch TAB, too:
  message.result:=message.result or DLGC_WANTARROWS or DLGC_WANTTAB;
end;


procedure TMyComponent.KeyDown(var Key: Word; Shift: TShiftState);
begin
  // If Tab is pressed, explicitly change focus to next component:
  if Key=VK_TAB then PostMessage(Parent.Handle,WM_NEXTDLGCTL,0,0);
  inherited KeyDown(Key, Shift);
end;


Above code will trigger KeyDown event in case arrow or Tab key is pressed.
Avatar of Levente

ASKER

Cynna,

Thank you for your answer. I've been on holiday that's whay I couldn't try your solution. In a few days I'll get back tou you.

Regards,
Levente
Avatar of Levente

ASKER

Cynna,

Sorry for the late answer. Here is the code that demonstrates my problem. Focus is in the EditBox. Press Tab, but focus doesn't move to the TMyButton, unless you click on it and force focus to move to it!

unit Temp;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure onMyButtonClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  TMyButton = class(TCustomControl)
     private
        procedure WMGetDlgCode( var Msg: TWMGetDlgCode); message WM_GETDLGCODE;
        procedure KeyDown(var Key: Word; Shift: TShiftState); override;
     protected
        procedure Paint; override;
  end;

var
  Form1: TForm1;
  MyButton1: TMyButton;
  Edit1: TEdit;

implementation

{$R *.DFM}

procedure TMyButton.WMGetDlgCode( var Msg: TWMGetDlgCode);
begin
   inherited;
   msg.result:= DLGC_WANTARROWS;
end;

procedure TMyButton.Paint;
begin
   Canvas.Rectangle( 0,0, Width, Height);
end;

procedure TMyButton.KeyDown(var Key: Word; Shift: TShiftState);
begin
   Canvas.Brush.Color:= RGB( random(255),random(255),random(255));
   repaint;
end;

procedure TForm1.onMyButtonClick(Sender: TObject);
begin
   MyButton1.SetFocus;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
   MyButton1:= TmyButton.Create( self );

   with MyButton1 do begin
      SetBounds( 100, 50, 60, 60);
      Caption:= 'bla';
      Parent:= Form1;
      onClick:= onMyButtonClick;
   end;

   Edit1:= TEdit.Create( self );

   with Edit1 do begin
      SetBounds( 10, 10, 60, 20);
      Parent:= Form1;
      Text:= 'Sample';
   end;

end;

end.
Levente,

Just add TabStop:=TRUE; after Caption:= 'bla' in MyButton1 properties.

Avatar of Levente

ASKER

I'd prefer Cynna's solution.
Avatar of Levente

ASKER

As simple as that! Thank you!
:)
You are welcome...