Object movement with multiple arrow keys pressed in Delphi and openGL - delphi

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.

Related

How to avoid repeating execution of TAction.Shortcut?

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.

Delphi monitoring status of CapsLock key

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;

Detect key(s) held down in another process

I have a Delphi 2007 Win32 executable which sends keystrokes to other applications. My app is invoked from within these target applications by a hotkey like F11 or Shift+F11.
I want users to be able to hold down a key to abort the keystroke sending (say, if they realize they invoked my app in the wrong location). I had thought Shift, Ctrl, and Alt were good candidates because, alone, those key presses aren't likely to disrupt anything in the target application. (Escape, for instance, is a bad choice, as it might cause the target application to close one or more windows.)
I wrote the function farther below and call it periodically as follows, while sending keystrokes with the intent of detecting keys held down.
if wsAnyKeysDownInWindow( TgtWindow, [VK_Escape, VK_Menu{Alt}, VK_Control, VK_Shift] ) then
Abort;
Problem is, my app sends keystroke combinations like Shift+Tab and Ctrl+Home, which (I think) makes this approach fail--it always detects a down state for Shift and/or Ctrl. (I also tried a similar function which called SetKeyboardState just prior to beginning to send keystrokes, to set the key states' high-order (down) bit but that didn't help.)
Anyone think of a workable approach, short of hooking the keyboard?
function wsAnyKeysDownInWindow(Handle: HWnd; VKeys: array of byte): boolean;
{ Checks whether each of the VKeys set of virtual keys is down in Handle,
a window created by another process. }
var
OtherThreadID : integer;
State: TKeyboardState;
AKey: byte;
begin
Result := False;
if not IsWindow(Handle) then
exit;
OtherThreadID := GetWindowThreadProcessID( Handle, nil);
if AttachThreadInput( GetCurrentThreadID, OtherThreadID, True ) then try
GetKeyboardState(State);
for AKey in VKeys do
if (State[AKey] and 128) <> 0 then begin //If high-order bit is set, key is down
Result := True;
exit;
end;
finally
AttachThreadInput( GetCurrentThreadID, OtherThreadID, False );
end;
end;
Consider using Scroll Lock for this. It is rarely used (only of Excel comes to my mind), and you will have even visual indicator if keys are being sent or not.
BTW, Alt is not a good choice for another reason - it invokes the main menu in an application (if there is one, of course).

Determine if SHIFT is pressed insde keyboard Hook Proc

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.

How to get rid of windows sounds when capturing CTRL + S?

In my application, when I press CTRL + S, my form (with Key Preview enabled) captures this and saves the document. But when the focus is in, for example, an edit control, I get an annoying "Ding" sound, or in general, windows sounds. How do I avoid this sound?
Here's my form's capture of this key event...
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
var
C: String;
begin
if not fChanging then
Modified;
if ssCtrl in Shift then begin
C:= LowerCase(Char(Key));
if C = 's' then begin
DoSave;
Key:= 0; //Tried this but didn't work
end else
if C = 'c' then begin
//Copy selected item(s)
end;
end;
end;
PS - Is there a more standard way of capturing these events? Because I'm sure I'm doing something wrong, and I'm sure there's another way I should be getting these key events without sounds.
A couple of things:
Try putting your code into FormKeyPress instead of FormKeyDown. This will make the Key := 0; code work... You will need to handle the CTRL checking manually though, by using something like GetKeyState() (I originally had GetAsyncKeyState() here, but as Rob Kennedy points out, GetKeyState() is a much better option).
Use an Action instead. Plop a TActionList on your form, double click on it, add an action and set it's hot key to CTRL-S. Add your save code to it's OnExecute event handler. This is the "proper" way to do it I believe.
Hope this helps.
Why don't you use Actions? That is the best way to handle shortcuts.
Here is some Delphi code to disable the system beep.

Resources