Recent

Author Topic: How detect a keypress in a Windows program  (Read 1790 times)

ad1mt

  • Sr. Member
  • ****
  • Posts: 487
    • Mark Taylor's Home Page
How detect a keypress in a Windows program
« on: November 06, 2025, 10:43:54 am »
I'm having problems detecting a keypress in a Windows program.
The following code shows the problem.
Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormClick(Sender: TObject);
  2. var
  3. K,C     :TKeyEvent;
  4. begin
  5. K:= 0; C:= 0;
  6. sleep(5000);
  7. while (PollKeyEvent <> 0) do
  8.         begin
  9.         K:= GetKeyEvent;
  10.         C:= TranslateKeyEvent(K);
  11.         showmessage('K = '+inttostr(K)+' C = '+inttostr(C));
  12.         end;
  13. halt;
  14. end;
  15.  
This example is a very simple project that uses the Keyboard unit, and has a form with nothing except the onclick handler.
I expected the calls to PollKeyEvent, GetKeyEvent and TranslateKeyEvent to return non-zero values if a key is pressed. But they always return zero, no matter how many times I press keys.
Can anyone suggest why this does not work, or suggest a better method?
Thanks.

paweld

  • Hero Member
  • *****
  • Posts: 1568
Re: How detect a keypress in a Windows program
« Reply #1 on: November 06, 2025, 10:59:46 am »
The form has events:
- OnKeyDown
- OnKeyPress
- OnKeyUp

Sample:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormCreate(Sender: TObject);
  2. begin
  3.   Memo1.Lines.Clear;
  4.   Form1.KeyPreview := True;  
  5.   Form1.OnKeyDown := @FormKeyDown;
  6. end;
  7.  
  8. procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState
  9.   );
  10. var
  11.   s: String;
  12. begin
  13.   s := '';
  14.   if (ssShift in Shift) then
  15.     s := s + 'Shift + ';
  16.   if (ssCtrl in Shift) then
  17.     s := s + 'Ctrl + ';
  18.   if (ssAlt in Shift) then
  19.     s := s + 'Alt + ';
  20.   if (ssAltGr in Shift) then
  21.     s := s + 'AltGr + ';
  22.   s := s + IntToStr(Key) + '[ ' + chr(Key) + ' ]';
  23.   Memo1.Lines.Add(s);
  24.   Key := 0;
  25. end;
Best regards / Pozdrawiam
paweld

ad1mt

  • Sr. Member
  • ****
  • Posts: 487
    • Mark Taylor's Home Page
Re: How detect a keypress in a Windows program
« Reply #2 on: November 06, 2025, 01:18:00 pm »
To be clear...
I need to detect a key press while event handler code is running. Just like in my example.

While event handler code is running, Form.FormKeyDown events are not received until the event handler code has exited.


Josh

  • Hero Member
  • *****
  • Posts: 1454
Re: How detect a keypress in a Windows program
« Reply #3 on: November 06, 2025, 01:42:00 pm »
i cant see how the message pump (event  driven) is going to work in a tight loop (your cpu will go through the roof), you could try putting a application.processmessages in the loop, but not recommended in tight loop either, you could try putting a small sleep and processmessage in, that may help.

the event onkeydown etc are the ones to use,
you could setavariable say called  AllowKeyboardInput set it to true in your click event theninyour onkeyevents if AllowKeyboardInput then .....
bare in mind sleeps your app even events, so use a while currenttime-startime<maxtime do  ... check for keyinput   sleep(10);application.processmessages;


The best way to get accurate information on the forum is to post something wrong and wait for corrections.

tetrastes

  • Hero Member
  • *****
  • Posts: 737
Re: How detect a keypress in a Windows program
« Reply #4 on: November 06, 2025, 02:27:45 pm »
This example is a very simple project that uses the Keyboard unit, and has a form with nothing except the onclick handler.
I expected the calls to PollKeyEvent, GetKeyEvent and TranslateKeyEvent to return non-zero values if a key is pressed. But they always return zero, no matter how many times I press keys.
Can anyone suggest why this does not work, or suggest a better method?
Thanks.

What keyboard unit? I suppose packages\rtl-console\src\win\keyboard.pp? It's for console, as one can see, and doesn't work with LCL.

Handoko

  • Hero Member
  • *****
  • Posts: 5515
  • My goal: build my own game engine using Lazarus
Re: How detect a keypress in a Windows program
« Reply #5 on: November 06, 2025, 02:37:25 pm »
@ad1mt
I am not sure what you want to do but you can try this:

https://forum.lazarus.freepascal.org/index.php/topic,57229.msg425440.html#msg425440

Thaddy

  • Hero Member
  • *****
  • Posts: 18729
  • To Europe: simply sell USA bonds: dollar collapses
Re: How detect a keypress in a Windows program
« Reply #6 on: November 06, 2025, 02:50:29 pm »
You should not use the keyboard unit in a GUI app. It is incompatible with the Lazarus event model.
You should simply use the OnKeyPress  event, or OnKeyUp.
« Last Edit: November 06, 2025, 02:52:13 pm by Thaddy »
If Europe sells their USA bonds the USD will collapse. Europe can affort that given average state debts. The USA can't affort that. Just an advice...

Hartmut

  • Hero Member
  • *****
  • Posts: 1058
Re: How detect a keypress in a Windows program
« Reply #7 on: November 06, 2025, 03:10:46 pm »
@ad1mt:
There is a function 'GetKeyState' in the windows-Unit and a platform-independent function 'GetKeyState' in the LCLIntf-Unit. Both can query the state of a key or a mouse button.
And there is a function 'GetKeyShiftState' in the Controls-Unit, but this can query only a couple of special keys.

Handoko

  • Hero Member
  • *****
  • Posts: 5515
  • My goal: build my own game engine using Lazarus
Re: How detect a keypress in a Windows program
« Reply #8 on: November 06, 2025, 03:24:09 pm »
For anyone interested with GetKeyState, try this demo:
https://forum.lazarus.freepascal.org/index.php/topic,41089.msg284614.html#msg284614

But GetKeyState has a bug, which as far as I know still hasn't been solved:
https://forum.lazarus.freepascal.org/index.php/topic,57089.msg424413.html#msg424413
« Last Edit: November 06, 2025, 03:26:20 pm by Handoko »

440bx

  • Hero Member
  • *****
  • Posts: 6065
Re: How detect a keypress in a Windows program
« Reply #9 on: November 06, 2025, 04:40:19 pm »
It looks like you are following the example given in the Wiki of PollKeyEvent.

Your code should call InitKeyboard before calling PollKeyEvent and DoneKeyboard when it is done polling. 

That said, I suspect the method mentioned by @paweld to likely be a better solution.  Key events are translated to messages and processing the resulting messages is likely more convenient and reliable (disclaimer: at least on Windows, I have no idea what the situation is on Linux.)

HTH.




FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

ad1mt

  • Sr. Member
  • ****
  • Posts: 487
    • Mark Taylor's Home Page
Re: How detect a keypress in a Windows program
« Reply #10 on: November 06, 2025, 04:56:43 pm »
Since my first post, I've made some progress.
The following small test program works
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2. {$mode objfpc}{$H+}
  3. interface
  4. uses    SysUtils Classes,Forms,Controls,Graphics,Dialogs,LCLIntf,LCLType;
  5. type
  6.  TForm1 = class(TForm)
  7.                 procedure FormClick(Sender: TObject);
  8.         private
  9.         public
  10.         end;
  11. var     Form1: TForm1;
  12. implementation
  13. {$R *.lfm}
  14.  
  15. procedure TForm1.FormClick(Sender: TObject);
  16. var K:integer;
  17. begin
  18. K:= 0;
  19. while (K >= 0) do
  20.         begin
  21.         sleep(2000);
  22.         K:= GetKeyState(VK_escape);
  23.         showmessage('K = '+inttostr(K)+' = '+binstr(K,32));
  24.         end;
  25. halt;
  26. end;
  27. end.
  28.  
Unfortunately, this code does not seem to work in another program where I need it. Maybe I've made a mistake in the other program.

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1572
    • Lebeau Software
Re: How detect a keypress in a Windows program
« Reply #11 on: November 06, 2025, 06:13:43 pm »
Since my first post, I've made some progress.
The following small test program works

On Windows, GetKeyState() relies on the calling thread's local keyboard state, which is updated only during window message processing.  Your event handler doesn't have a message pump of its own, but ShowMessage() does internally.  So, if you get rid of that call, your code will fail to work again.  Use GetAsyncKeyState() instead, which uses global keyboard state rather then the calling thread's local keyboard state.

Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormClick(Sender: TObject);
  2. begin
  3.   repeat
  4.     ..
  5.     if (GetAsyncKeyState(VK_ESCAPE) <> 0) then
  6.       Break;
  7.     ...
  8.   until False;
  9.   Close;
  10. end;

But really, this is not good code design. You need to rethink what you are doing.

For example, you could use a timer instead with OnKey... events, eg:

Code: Pascal  [Select][+][-]
  1. type
  2.   TForm1 = class(TForm)
  3.     Timer: TTimer;
  4.     procedure FormClick(Sender: TObject);
  5.     procedure TimerElapsed(Sender: TObject);
  6.     procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  7.   end;
  8.  
  9. procedure TForm1.FormClick(Sender: TObject);
  10. begin
  11.   Timer.Interval := 2000;
  12.   Timer.Enabled := True;
  13. end;
  14.  
  15. procedure TForm1.TimerElapsed(Sender: TObject);
  16. begin
  17.   // do something...
  18. end;
  19.      
  20. procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  21. begin
  22.   if Key = VK_ESCAPE then
  23.   begin
  24.     Key := 0;
  25.     Timer.Enabled := False;
  26.     Close;
  27.   end;
  28. end;
« Last Edit: November 06, 2025, 06:21:45 pm by Remy Lebeau »
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

ad1mt

  • Sr. Member
  • ****
  • Posts: 487
    • Mark Taylor's Home Page
Re: How detect a keypress in a Windows program
« Reply #12 on: November 06, 2025, 07:19:50 pm »
Your event handler doesn't have a message pump of its own, but ShowMessage() does internally.  So, if you get rid of that call, your code will fail to work again.
Since my last post, I discovered this for myself by a process of elimination. But I found it incomprehensible and I could not understand why removing the call to showmessage() should stop it working.
This is one of the reasons why I dislike O.O.P... you call a procedure/function (aka method) thinking it will do "what it says on the label" or what it says in the documentation. But what you actually get is a whole truckload of other inherited functionality that is often not documented, and can cause insolvable problems.

Your suggestions look exactly what I need, so I will try them
Many thanks for your reply.

« Last Edit: November 07, 2025, 06:04:02 pm by ad1mt »

440bx

  • Hero Member
  • *****
  • Posts: 6065
Re: How detect a keypress in a Windows program
« Reply #13 on: November 06, 2025, 07:21:11 pm »
@OP

One thing that is unclear to me is, are you trying to detect key presses when your application has the focus ? or are you trying to detect key presses regardless of which is the active/foreground application ?

There is a big difference between those two cases.  if it is case 1 then as @paweld implied and @Remy Lebeau mentioned, just process the messages resulting from the key events.

if it is case 2 then you need to use GetAsyncKeyState and you have to deal with the complication of possibly getting the key state multiple times when there hasn't actually been a key state change.

The question: which case is it you are trying to deal with ?
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

ad1mt

  • Sr. Member
  • ****
  • Posts: 487
    • Mark Taylor's Home Page
Re: How detect a keypress in a Windows program
« Reply #14 on: November 07, 2025, 11:46:38 am »
One thing that is unclear to me is, are you trying to detect key presses when your application has the focus ?
The actual program I want to do this is a Draughts/Checkers playing program, discussed in a nearby thread.

The application has the focus at all times. There is code that is called from a mouse click event handler, which can run for several minutes (and this time can be unpredictable). So I want the user to be able to press the [ESC] key to interrupt this code and make it return early.

The problem I've hit is that while the mouse click handler code is running, keyboard key presses get queued up and are not received by the program until the mouse click event handler code has returned to the main application loop code.

When this was a command-line program, it was easy do write code to quit early.

 

TinyPortal © 2005-2018