0
votes

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?
1
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
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
@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 minimal reproducible example in any case.Sertac Akyuz
Yes, we would. In a simple test I could not replicate OPs problem.Tom Brunberg
The simple answer to your question as it stands is "remove the DialogKey method, the form handles the keys fine by itself".Sertac Akyuz

1 Answers

4
votes

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.