Fons N 17 Posted January 28, 2023 (edited) 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 January 28, 2023 by Fons N Share this post Link to post
programmerdelphi2k 237 Posted January 28, 2023 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
Anders Melander 1783 Posted January 28, 2023 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. 1 Share this post Link to post
PeterBelow 238 Posted January 28, 2023 (edited) 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. Quote Edited January 28, 2023 by PeterBelow 1 Share this post Link to post
programmerdelphi2k 237 Posted January 28, 2023 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; Share this post Link to post
Fons N 17 Posted January 29, 2023 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
Fons N 17 Posted January 29, 2023 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
Fr0sT.Brutal 900 Posted January 30, 2023 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
Remy Lebeau 1396 Posted January 30, 2023 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
Remy Lebeau 1396 Posted January 30, 2023 (edited) 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 January 30, 2023 by Remy Lebeau 1 Share this post Link to post
PeterBelow 238 Posted January 30, 2023 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 . Share this post Link to post
Remy Lebeau 1396 Posted January 30, 2023 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. 1 Share this post Link to post
Fons N 17 Posted February 4, 2023 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
Fons N 17 Posted February 4, 2023 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