My program runs in the background, and uses a timer to regularly check if Capslock is ON or OFF.
My question is if there exists a better solution than using a timer?
procedure TForm1.Timer2Timer(Sender: TObject);
var KeyState: TKeyboardState;
begin
GetKeyboardState(KeyState) ;
if (KeyState[VK_CAPITAL] = 0) then
CheckBox1.Checked:=False //Capslock is OFF
else
CheckBox1.Checked:=True; //Capslock is ON
end;
Do this with a low level keyboard hook, WH_KEYBOARD_LL. Install the hook with SetWindowHookEx. You'll get notified of every keyboard event in the hook proc. Detect the original state by calling GetKeyboardState.
Note that you must read the documentation more carefully. For GetKeyboardState it says:
If the key is a toggle key, for example CAPS LOCK, then the low-order bit is 1 when the key is toggled and is 0 if the key is untoggled.
Therefore it is erroneous to test the entire byte against zero. Test just the low-order bit. Use and $1 to pick out that bit.
KeyPreview:= True
FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if GetKeyState(VK_CAPITAL) > 0 then
hdrSts.Sections[0]:= 'CAPS LOCK'
else
hdrSts.Sections[0]:= '';
end;
Related
In Delphi 10.1.2, inside a TActionList I have created a TAction with these properties and assigned a shortcut Ctrl+F12 to it:
At runtime, when I keep the shortcut keys Ctrl+F12 pressed, the action is executed repeatedly (with speed depending on the system keyboard repeating speed).
So how can I make the action execute only ONCE (until these keys are lifted up), even if the user keeps the keys pressed down or if the user's system has a high keyboard repeat speed setting?
You can retrieve system keyboard settings by using SystemParametersInfo. SPI_GETKEYBOARDDELAY gives the repeat delay; the time between the first and second generated events. SPI_GETKEYBOARDSPEED gives keyboard repeat rate; the duration between events after the initial delay. The documentation has approximate values of the slowest and fastest settings which may help you decide on an acting point.
Suppose you decided to act on. Since shortcuts expose no direct relation to the event that generated them, they have no property or anything that could reveal information about if it is an initial, delayed, or repeated execution.
Then, one option is to disable a shortcut's execution as soon as it is entered and re-enable after the appropriate key has been released. You have to set KeyPreview of the form to true to implement this solution as any of the controls on the form might be the one with the focus when a shortcut is generated.
A less cumbersome solution I would find is to prevent generation of the shortcut when it's not generated by an initial key-down. You have to watch for key down events this time.
One possible implementation can be to install a local keyboard hook.
var
KeybHook: HHOOK;
procedure TForm1.FormCreate(Sender: TObject);
begin
KeybHook := SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, 0, GetCurrentThreadId);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
UnhookWindowsHookEx(KeybHook);
end;
It's probably tempting to test for the repeat count of the interested key in the callback, however, as mentioned in the documentation for WM_KEYDOWN, the repeat count is not cumulative. What that practically means is that the OS does not supply this information. The previous key state information is provided though. That would be bit 30 of the "lParam" of the callback. You can prevent any keyboard message when your shortcut keys are down, and the primary shortcut has been previously down, from reaching the window procedure of the focused control.
function KeyboardProc(code: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT;
stdcall;
begin
if code > 0 then begin
if (wParam = vk_f12) and (GetKeyState(VK_CONTROL) < 0) and
((lParam and $40000000) > 0) then begin
Result := 1;
Exit;
end;
end;
Result := CallNextHookEx(KeybHook, code, wParam, lParam);
end;
Lastly, don't disregard the probability that if a user's system has been set up with a fast keyboard repeat, it's the user's choice rather than not.
hi i am doing a command shell using a memo in Delphi , the problem is to detect the last line written and read the command I need to know how to detect the enter key on a memo.
as I can detect the enter key on a memo ?
In the OnKeypress event you can check for certain keys and handle them as you wish yourself. The enter key is one of these keys.
procedure TForm1.Memo1KeyPress(Sender: TObject; var Key: Char);
const
ENTER = #13;
begin
case Key of
ENTER : begin
// Do something
end;
end;
end;
By default a TMemo has the WantReturns property set to TRUE. This means that as well as any response to the key press that you might implement in your code, the TMemo will still also receive the key event and add a new line to the content of the memo.
If you do not want this then you can either:
Set WantReturns = FALSE. you will still get the KeyPress
event for the enter key, but the memo will not add new lines in
response (they can still be added if the user presses Ctrl +
Enter)
OR
Keep WantReturns = TRUE but set Key to the value #0 for any key
events which you want to suppress (i.e. prevent from reaching the
memo control).
An example of this latter approach might look something like this:
const
NO_KEY = #0;
ENTER = #13;
begin
case Key of
ENTER : begin
// Do something
if NOT AddNewLine then
Key := NO_KEY;
end;
end;
end;
NOTE: The OnKeyPress event only allows you to respond to a subset of key events, specifically those that correspond to CHAR type values (although this does include some non-printing characters such as Tab and Backspace, for example).
If you want or need to detect the state of a wider range of non-character keys or to reliably handle key combinations such as Ctrl+Key or Shift+Key then you will need to query the state of those modifier keys. However, by the time you are responding to the key event, the state of the modifier keys may have changed and a better approach in that case is to use an alternate event which provides a greater range of key events, including the state of the Shift keys (and Control keys) at the time of the key event itself, such as OnKeyDown.
You can use OnKeyDown event, for example:
procedure TForm.Memo1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key = VK_Return then
begin
// Your code here ...
// set Key to 0 if you do not want the key
// to be default-processed by the control...
Key := 0 ;
end;
end;
Detecting the enter key in a TMemo control is easy. Just add an OnKeyPress event:
procedure TForm1.Memo1KeyPress(Sender: TObject; var Key: Char);
begin
if Key = #13 then
begin
// Do something
end;
end;
I have a 3d object in a delphi program and I am trying to program to control his movement in 3d space with arrow keys.
First problem: For single arrow keys the movement works fine - the object turns around or goes forward according to the key pressed, but it doesn't seem to work with multiple key press. In case of the UP key and Left or Right key press, it rotates but does not go forward.
Second problem: The UP key movement is somehow delayed. Only 0.5 seconds after the UP key is pressed, the object starts moving forward. Why is that?
Here's the code:
function KeyPressed(nKey: Integer): Boolean;
begin
Result := (GetAsyncKeyState(nKey) and $8000) <> 0;
end;
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if keypressed(VK_LEFT) then
if heroMove.angle < 360 then inc(heroMove.angle,5) else heroMove.angle:=0
else if keypressed(VK_RIGHT) then
if heroMove.angle < 360 then inc(heroMove.angle,-5) else heroMove.angle:=0 else
if keypressed(VK_UP) then
begin
heroMove.x:=heroMove.x+0.2*Sin(heroMove.angle*3.1415/180);
heroMove.y:=heroMove.y+0.2*Cos(heroMove.angle*3.1415/180);
pose:=1;
end;
if keypressed(VK_LEFT) and keypressed(VK_UP) then
begin
if heroMove.angle < 360 then inc(heroMove.angle,5) else heroMove.angle:=0;
heroMove.x:=heroMove.x+0.2*Sin(heroMove.angle*3.1415/180);
heroMove.y:=heroMove.y+0.2*Cos(heroMove.angle*3.1415/180);
pose:=1;
end else
if keypressed(VK_RIGHT) and keypressed(VK_UP) then
begin
if heroMove.angle < 360 then inc(heroMove.angle,-5) else heroMove.angle:=0;
heroMove.x:=heroMove.x+0.2*Sin(heroMove.angle*3.1415/180);
heroMove.y:=heroMove.y+0.2*Cos(heroMove.angle*3.1415/180);
pose:=1;
end;
end;
procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if Key=VK_UP then
pose:=0;
end;
P.S. The parameter "pose" just controls the animation type of the object.
What you need here is to stop using event driven programming methods. You need to switch to a more tradition game programming style.
You want to run a game loop that provides a steady pulse to your program. Each iteration of this loop you check the keyboard state. Use GetAsyncKeyState for that. Check the state of each key that you are interested in. In your game loop you will also be driving any updates to the OpenGL canvas.
I can see from your update that you are already using GetAsyncKeyState. But that really does not make a lot of sense when used in response to an OnKeyDown event. Either you use event driven synchronous I/O, or polling asynchronous I/O. I cannot understand why you are trying to mix the two paradigms.
The way you call GetAsyncKeyState looks wrong. You should write it like this:
function KeyPressed(nKey: Integer): Boolean;
begin
Result := GetAsyncKeyState(nKey) < 0;
end;
I also suggest that in each iteration of your main loop you call GetAsyncKeyState once and once only for each key. Save the returned value to a local variable. That avoids the scenario where a key is deemed to be down at the start of the iteration, but then the same key is up at some later point in the iteration. That's an issue you need to be alive to with asynchronous I/O.
inside my hook proc, how can i determine whether a used is pressing SHIFT (without releasing it) and then a char key (like A) ?
for example, if i press Shift+A, i want to know that it will be an uppercase a instead of getting it like Shift, a
so it will be A, if a user presses and releases Shift, it will capture Shift only.
the instaleld hook is WH_KEYBOARD (Global)
function KeyHookProc(Code: Integer; wVirtKey: WPARAM; lKeyStroke: LPARAM)
: LRESULT; stdcall;
type
TTransitionState = (tsPressed, tsReleased);
PKeystrokeData = ^TKeystrokeData;
TKeystrokeData = record
VirtualKey: WPARAM;
KeyStroke: LPARAM;
KeyState: TKeyboardState;
end;
var
Transition: TTransitionState;
KeystrokeDataPtr: PKeystrokeData;
begin
Result := CallNextHookEx(hKeyHook, Code, wVirtKey, lKeyStroke);
Transition := TTransitionState((lKeyStroke shr 31) and 1);
if (Code = HC_ACTION) and (Transition = tsPressed) then
begin
New(KeystrokeDataPtr);
try
KeystrokeDataPtr^.VirtualKey := wVirtKey;
KeystrokeDataPtr^.KeyStroke := lKeyStroke;
GetKeyboardState(KeystrokeDataPtr^.KeyState);
SendMessage(hConsole, WM_NULL, 0, LPARAM(KeystrokeDataPtr));
finally
Dispose(KeystrokeDataPtr);
end;
end;
end;
Here's the code we use in normal day-to-day use to detect the shift key. I've never used it in a hooked context, so I don't know if it would work there, or if something is different in that context that would prevent it.
function ShiftIsDown : Boolean;
var
State: TKeyboardState;
begin
WINDOWS.GetKeyboardState(State);
Result := ((State[vk_Shift] and 128) <> 0);
end;
You detect the pressing of the SHIFT key when your hook proc is called with wParam equal to VK_SHIFT.
When your hook proc is called corresponding to the A key being pressed, the wParam and lParam values are identical whether or not the SHIFT key is down. Since you are not calling TranslateMessage and DispatchMessage as would happen in a normal message loop, you are going to have to translate the raw key down/up events into actual key presses.
It's probably going to be easiest for you to use GetKeyState(VK_SHIFT)<0 to detect whether or not the SHIFT key is down. That depends on exactly what you are trying to do. It looks a little like you are making a fully functioned keylogger. In which case ad-hoc calls to GetKeyState(VK_SHIFT)<0 may not meet your needs, and proper processing of the individual key down/up messages would be better.
Some other comments:
Why are you heap allocating your TKeystrokeData record? You can perfectly well use a stack allocated record.
I hope that hConsole is a window in the same process as your hook. If not it won't receive any useful information because the pointer you send it is only meaningful in the process that defines it. If you want to send information cross-process them WM_COPYDATA is your guy.
I just discovered that the caps lock and shift key (and probably some more of the keys) affect all of the keyboards connected to the computer. (one of the hazards of testing a program that I coded, I only have two hands so it took me ages to realize a problem like this)
So separating the keystrokes is fine, but the shift/caps locks drives everything crazy (When one user shifts to capitalize, all users' input at that moment are capitalized as well)
Can I capture Capslock and Shift keypress in FormKeyPress?
Anywhere else for that matter?
Can I save a Shift keypress? (so I can properly apply the 'shift' to the respective user input)
Any other suggestion to solve this problem is welcome as well.
to check if shift ley is down try this:
if GetKeyState(VK_SHIFT)<0 //tests if shiftkey is down
then ShowMessage('shift key is down'');
and to detect if caps lock is on try this:
if Odd(GetKeyState(VK_CAPITAL)) //tests if caps lock is on
then showmessage('caps lock is on');
and if you want to check status of both shift and capslock :
if Odd(GetKeyState(VK_CAPITAL)) then
if GetKeyState(VK_SHIFT)<0 then
showmessage('capslock is on and shift key is down too')
else
showmessage('capslock is on but shift key is NOT down');
getkeystate is a windows api function you can read more about it here
You can capture them in the OnKeyDown event
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if Key = VK_SHIFT then
ShowMessage('Shift Pressed');
if Key = VK_CAPITAL then
ShowMessage('Caps Lock Pressed');
end;
if Key = VK_SHIFT then
ShowMessage('Shift Pressed');
there should have been:
if shift = [ssShift] then ...
The program tests Shift: TShiftState for SHIFT key hold down,
not key :word; it is checked for other ordinal characters
It doesn't work for CAPSLOCK - I couldn't find the code.
There are only: ssShift, ssAlt, ssCtrl, ssLeft, ssRight, ssMiddle, ssDouble, so it seems have to use in this case:
if Odd(GetKeyState(VK_CAPITAL)) then ...