Solved

Capturing arrow up and down in CustomControl

Posted on 2002-06-20
10
1,114 Views
Last Modified: 2010-08-05
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
0
Comment
Question by:Levente
  • 5
  • 4
10 Comments
 

Expert Comment

by:ZafodBiblbrox
ID: 7095346
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.
0
 
LVL 7

Accepted Solution

by:
Cynna earned 300 total points
ID: 7095617
Levente,

Delphi , with respect to your problem, has two classes
of components. Those which are expected to use arrow
keys (such as TMemo, TEdit, etc.) and those, like TButton,
who can't do anything useful with arrow key press. For
those, Delphi doesn't propagate message ON_KEYDOWN to
event handler of those components, but instead implements
default behaviour, ie focus change.
The trick is that Delphi 'asks' component 'Do you want to
handle Arrow keys?'. Only if component explicitly answers
'Yes I do!' will Delphi proceed Arrow press to the component.
Message which is sent to component as this question is
WM_GETDLGCODE, and message result (ie, component affirmative answer) is DLGC_WANTARROWS.

So, it boils down to this: implement message handler
for WM_GETDLGCODE message, and return result DLGC_WANTARROWS in your component.



Example (you can Copy/Paste, it will work):
----------------------------------------------------------------

Add this in private section of you component:

  private
    procedure WMGetDlgCode(var message: TWMGetDlgCode); message WM_GETDLGCODE;
  end;


And, implementation is:

procedure TMyComponent.WMGetDlgCode(var message: TWMGetDlgCode);
begin
  inherited;
  // Answer Delphi that this component wants to handle its own arrow key press:
  message.result:=DLGC_WANTARROWS;
end;



0
 

Author Comment

by:Levente
ID: 7098143
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.
0
 
LVL 7

Expert Comment

by:Cynna
ID: 7098460
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.
0
 

Author Comment

by:Levente
ID: 7116843
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
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 

Author Comment

by:Levente
ID: 7126449
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.
0
 
LVL 7

Expert Comment

by:Cynna
ID: 7127097
Levente,

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

0
 

Author Comment

by:Levente
ID: 7131456
I'd prefer Cynna's solution.
0
 

Author Comment

by:Levente
ID: 7131460
As simple as that! Thank you!
0
 
LVL 7

Expert Comment

by:Cynna
ID: 7131647
:)
You are welcome...
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

The uses clause is one of those things that just tends to grow and grow. Most of the time this is in the main form, as it's from this form that all others are called. If you have a big application (including many forms), the uses clause in the in…
Creating an auto free TStringList The TStringList is a basic and frequently used object in Delphi. On many occasions, you may want to create a temporary list, process some items in the list and be done with the list. In such cases, you have to…
Sending a Secure fax is easy with eFax Corporate (http://www.enterprise.efax.com). First, Just open a new email message.  In the To field, type your recipient's fax number @efaxsend.com. You can even send a secure international fax — just include t…
Access reports are powerful and flexible. Learn how to create a query and then a grouped report using the wizard. Modify the report design after the wizard is done to make it look better. There will be another video to explain how to put the final p…

760 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

21 Experts available now in Live!

Get 1:1 Help Now