Jump to content
Fons N

KeyDown and Shift state

Recommended Posts

In a KeyDown event (VCL) I have the following code:

 

procedure CodeExample.KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin

  if Key = 37 then
  begin
    Shift:= [ssShift];
    Key:= 9;
  end;

  if Key = 39 then
  begin
    Shift:= [];
    Key:= 9;
  end;

end;
 

The code in the Key = 39 part works. The pressed key is now a tab. The code in the Key = 37 part however does not work. Yes, the pressed key is still a tab, but the state of the Shift key does not act like it is pressed.

 

I have the KeyPreview set to TRUE in the Form. I guess I am doing something wrong here... Most likely that Shift can only be read, so it is the state when the key was pressed and it cannot be used to actually set the Shift key to a specific state. How can that be done?

 

Any help is appreciated.

 

*** UPDATE ***

 

I have found the following code:

 

  Keybd_Event(VK_SHIFT, 0, 0, 0);
  Keybd_Event(VK_TAB, 0, 0, 0);
  Keybd_Event(VK_TAB, 0, KEYEVENTF_KEYUP, 0);
  Keybd_Event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
 

This does the job. Is this the best way ?  And  is it "future" proof ?

 

 

 

Edited by Fons N

Share this post


Link to post

From what I see, you want to change the behavior of the event, assigning new values within it, to change its trajectory.
So, if he changes behavior within himself, wouldn't that cause internal confusion?

  • Event: I got "A", and now you want it to be "B"... What do I do then?

Therefore, I think it would be better if you make the change outside the event. And using a function like Keyb_Event() (outside the event), would be something more sensible!

 

REMEMBER: the event can be called many times... in case, OnKeyDown is called as many times as the keys pressed!

Share this post


Link to post
2 hours ago, Fons N said:

Most likely that Shift can only be read

You can see from the method signature that Key can be modified (it's passed by reference; as a "var") and that Shift cannot (it's passed by value).

 

2 hours ago, Fons N said:

I have found the following code:

 

  Keybd_Event(VK_SHIFT, 0, 0, 0);
  Keybd_Event(VK_TAB, 0, 0, 0);
  Keybd_Event(VK_TAB, 0, KEYEVENTF_KEYUP, 0);
  Keybd_Event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
 

This does the job. Is this the best way ?  And  is it "future" proof ?

We cannot tell if it's "the best way" since you haven't really explained what problem you're solving (see XY problem) - And we cannot predict your future.

 

One thing to be aware of is that keyboard events are read from the message queue in the order in which they occurred. Keybd_Event will append to this queue so you might end up with a key sequence that doesn't correspond to what actually occurred.

  • Thanks 1

Share this post


Link to post
3 hours ago, Fons N said:

In a KeyDown event (VCL) I have the following code:

 

procedure CodeExample.KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin

  if Key = 37 then
  begin
    Shift:= [ssShift];
    Key:= 9;
  end;

  if Key = 39 then
  begin
    Shift:= [];
    Key:= 9;
  end;

end;
 

The code in the Key = 39 part works. The pressed key is now a tab. The code in the Key = 37 part however does not work. Yes, the pressed key is still a tab, but the state of the Shift key does not act like it is pressed.

Why should it? The Shift parameter is not a Var parameter so you just change the local value of it, not the one the caller passed. Even changing that (if you could) would not do what you think it should since it would not change the Windows-internal state of the modifier keys. And the WM_CHAR message for the tab key is already in the message queue anyway.

 

Using keybd_event (in fact thats deprecated, you should use SendInput instead) puts a completely new Shift-Tab combination into the message queue, so you better set Key := 0 

to abort processing of the 37 key. You should use virtual key codes instead of numeric values however, those make it clear which key you are processing here. VK_LEFT is much more readable than 37. :classic_dry:

 

Quote

 

 

Edited by PeterBelow
  • Thanks 1

Share this post


Link to post

in Form1.OnKeyDown() the "TAB" it's not captured because that is used for "jump" to another control = focus!

but in Form1.OnKeyUP() you can see it... of course, it's too late for process as you want!

{$R *.dfm}

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if (Key = VK_TAB) then // and (ssShift in Shift) then
    Memo1.Lines.Add('Form1.OnKeyDown:' + chr(VK_TAB) + ' TAB was pressed');
end;

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
begin
  if (Key = Char(VK_TAB)) then
    Memo1.Lines.Add('Form1.OnKeyPress:' + chr(VK_TAB) + ' TAB was pressed');
end;

procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if (Key = VK_TAB) then // and (ssShift in Shift) then
    Memo1.Lines.Add('Form1.OnKeyUp:' + chr(VK_TAB) + ' TAB was pressed');
end;

image.thumb.png.830ba461a25b984aa4b0f7d1f2cf9942.png

Share this post


Link to post
17 hours ago, PeterBelow said:

you should use SendInput instead

I am not a professional programmer. I looked SendInput up SendInput function (winuser.h) - Win32 apps | Microsoft Learn

 

But I cannot make heads or tails of it (if that's the correct expression - I am Dutch)

 

So after some searching I found this type conversion - Delphi keypress and keybd_event - Stack Overflow

 

Keybd_Event(VK_SHIFT, 0, 0, 0);
Keybd_Event(VK_TAB, 0, 0, 0);
Keybd_Event(VK_TAB, 0, KEYEVENTF_KEYUP, 0);
Keybd_Event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
 

With the example code I have tried to translate the Keybd_Event code. But unfortunately it does function (behave) the same. At least my application did not crash.

 

class procedure TRoutinesEx.ShiftTab;
var
  Inputs: array[0..3] of TInput;
begin

  Inputs[0] := Default(TInput);
  Inputs[0].Itype := INPUT_KEYBOARD;
//  Inputs[0].ki.dwFlags := KEYEVENTF_KEYUP;
  Inputs[0].ki.wScan := VK_SHIFT;

  Inputs[1] := Default(TInput);
  Inputs[1].Itype := INPUT_KEYBOARD;
//  Inputs[1].ki.dwFlags := KEYEVENTF_KEYUP;
  Inputs[1].ki.wScan := VK_TAB;

  Inputs[2] := Default(TInput);
  Inputs[2].Itype := INPUT_KEYBOARD;
  Inputs[2].ki.dwFlags := KEYEVENTF_KEYUP;
  Inputs[2].ki.wScan := VK_TAB;

  Inputs[3] := Default(TInput);
  Inputs[3].Itype := INPUT_KEYBOARD;
  Inputs[3].ki.dwFlags := KEYEVENTF_KEYUP;
  Inputs[3].ki.wScan := VK_SHIFT;

  SendInput(Length(Inputs), Inputs[0], SizeOf(Inputs));

end;
 

Guess I did something wrong. But I am clueless...

 

 

Share this post


Link to post
On 1/28/2023 at 2:44 PM, programmerdelphi2k said:

Therefore, I think it would be better if you make the change outside the event. And using a function like Keyb_Event() (outside the event), would be something more sensible!

 

REMEMBER: the event can be called many times... in case, OnKeyDown is called as many times as the keys pressed!

Thanks, I understand, but as the code is inside the following

 

  if Key = VK_LEFT then
  begin
  end;
 

it won't be executed twice.

Share this post


Link to post

If you just want to jump over controls by cursor arrows, it's easier to post crafted messages to a form via PostMessage or Form.Perform

Share this post


Link to post
On 1/28/2023 at 9:04 AM, programmerdelphi2k said:

in Form1.OnKeyDown() the "TAB" it's not captured because that is used for "jump" to another control = focus!

Tab (and arrows) is reserved for focus navigation by default, that is true.  But, if you want to handle it yourself, you can request it from the OS so it arrives as normal messages.  For instance, by intercepting WM_GETDLGCODE to return DLGC_WANTTAB.  Some child controls have properties to handle this for you, but TForm does not, so you have to implement it manually.

Share this post


Link to post
On 1/29/2023 at 1:15 AM, Fons N said:

With the example code I have tried to translate the Keybd_Event code. But unfortunately it does function (behave) the same. At least my application did not crash... Guess I did something wrong. But I am clueless...

The value you are passing to the last (cbSize) parameter of SendInput() is wrong.  It wants you to give it the size of a single TInput, but you are giving it the full size of your entire array instead.

 

You are also using the wrong TInput field inside of the array.  Since you are sending virtual key codes and not hardware scan codes, you should be using the TInput.ki.wVk field rather than the TInput.ki.wScan field.

 

Try this instead:

class procedure TRoutinesEx.ShiftTab;
var
  Inputs: array[0..3] of TInput;
begin
  ZeroMemory(@Inputs, SizeOf(Inputs));

  Inputs[0].Itype := INPUT_KEYBOARD;
  Inputs[0].ki.wVk := VK_SHIFT;

  Inputs[1].Itype := INPUT_KEYBOARD;
  Inputs[1].ki.wVk := VK_TAB;

  Inputs[2].Itype := INPUT_KEYBOARD;
  Inputs[2].ki.dwFlags := KEYEVENTF_KEYUP;
  Inputs[2].ki.wVk := VK_TAB;

  Inputs[3].Itype := INPUT_KEYBOARD;
  Inputs[3].ki.dwFlags := KEYEVENTF_KEYUP;
  Inputs[3].ki.wVk := VK_SHIFT;

  SendInput(Length(Inputs), Inputs[0], SizeOf(TInput));
end;

 

Edited by Remy Lebeau
  • Thanks 1

Share this post


Link to post
On 1/29/2023 at 10:15 AM, Fons N said:

With the example code I have tried to translate the Keybd_Event code. But unfortunately it does function (behave) the same.

It is expected to act the same since SendInput is just the recommended alternative to the old keybd_event API, which is basically legacy from 16-bit Windows; still supported by current Windows versions but it may be removed sometime in the future, though that seems unlikely based on past experience with other "legacy" APIs. Windows carries a loads of old APIs along, since dropping them will cause old applications still in use at many customers to fail, and the resulting uproar would be bad for business :classic_dry:.

 

 

Share this post


Link to post
2 hours ago, PeterBelow said:

It is expected to act the same since SendInput is just the recommended alternative to the old keybd_event API

Well, not exactly the same.  Keep in mind that keybd_event() (and mouse_event(), too) can only inject 1 event at a time into the input queue, thus other events from other sources could interweave in between the synthetic events.  That may have subtle side-effects.  Wherever SendInput() is designed to inject multiple events atomically into the queue, thus no interweaving can happen anymore.

  • Like 1

Share this post


Link to post
On 1/30/2023 at 7:06 PM, Remy Lebeau said:

Try this instead:

It's works perfectly !  Thank you very much, Remy, it is really appreciated. I wouldn't have figured it out for myself. Thanks again !!

Share this post


Link to post
On 1/30/2023 at 9:53 AM, Fr0sT.Brutal said:

If you just want to jump over controls by cursor arrows, it's easier to post crafted messages to a form via PostMessage or Form.Perform

It is to "jump" within different "parts" of the same control. But, if I need to it to be between or over controls, I will keep your suggestions in mind.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×