0

I have a VCL form with many tframes (referred to as tcellFrame) containing components arranged in a grid. I am using mouse clicks and arrow keys for the user to navigate between them. The mouse clicks work fine, but I had trouble with the arrow keys until I discovered this question thread: Delphi XE and Trapping Arrow Key with OnKeyDown. The solution in Sertac Akyuz's answer does handle getting the arrow key messages to the form using

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;

but acts upon the form twice for each key stroke. Instead of moving to the left one cellframe, it moves two. Tracing the thread using the debugger demonstrates that the onkeydown event is called twice.

My onKeyDown event is structured as follows:

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var
  i : integer;
  g, r, c, str : string;
  tmpFrame : tcellFrame;  //frame component containing tlabels
begin
  ...

  case key of
    VK_UP:
      begin
        //calc new cell location values for g,r,c
        str := ('Cell'+g+r+c);
        picklist.Clear;  // picklist is a form-wide tstringlist variable containing currently selected cellframes.
        picklist.add (str);
        TmpFrame := FindComponent(picklist [0]) as TCellFrame;
        tmpframe.Color := pickClr;
      end;
    //VK_DOWN, VK_LEFT, VK_RIGHT: defined similarly to VK_UP

  end;
end;

There is more code in Formkeydown, but it is all internal calculations to determine the proper tcellframe name to place in the picklist.

My questions are:

  • What is causing this repeat to occur?
  • How do I terminate the message implementation after its first instance?
Ashlar
  • 636
  • 1
  • 10
  • 25
  • That solution is for when a button is the active control (please read the question), otherwise the form processes the keys alright. If you fabricate a KeyDown when the form can already handle the key, then it of course doubles. – Sertac Akyuz Oct 10 '19 at 17:34
  • From docs for `KeyDown`: *Either KeyDown or the OnKeyDown event handler it calls can suppress further processing of a key by setting the Key parameter to zero.* – Tom Brunberg Oct 10 '19 at 17:38
  • @Tom - I guess the issue then would be to decide when to halt further processing and when to not. From here it looks like poster could test for active control class but we'd need a [mcve] in any case. – Sertac Akyuz Oct 10 '19 at 17:41
  • Yes, we would. In a simple test I could not replicate OPs problem. – Tom Brunberg Oct 10 '19 at 17:44
  • The simple answer to your question as it stands is "remove the DialogKey method, the form handles the keys fine by itself". – Sertac Akyuz Oct 10 '19 at 17:46
  • @Sertac I'm not so sure OP should remove the `DialogKey` as that is the solution to the original question (in previous post). But again, just speculation as we don't have the full picture. – Tom Brunberg Oct 10 '19 at 17:52
  • @Tom - I'm not sure either. I just claimed that because of the doubling. – Sertac Akyuz Oct 10 '19 at 17:54

1 Answers1

4

In your CM_DIALOGKEY message handler, return a non-zero value if you handle the key, then it won't be dispatched further.

procedure TForm1.DialogKey(var Msg: TWMKey);
begin
  case Msg.CharCode of
    VK_DOWN, VK_UP, VK_RIGHT, VK_LEFT:
      begin
        if Assigned(onKeyDown) then
          onKeyDown(Self, Msg.CharCode, KeyDataToShiftState(Msg.KeyData));
        Msg.Result := 1; // <-- add this
      end;
    else
      inherited;
  end;
end;

However, if you have KeyPreview=True on the Form and the arrow keys are already being dispatched normally, then there is no need to handle CM_DIALOGKEY at all, just let the Form's OnKey... events get dispatched normally. You should not be firing the Form's OnKey... events from a CM_DIALOGKEY handler.

See A Key's Odyssey for more details.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770