Reputation: 143
I created a console application in delphi 7, which is supposed to show messages after you press the enter button:
begin
writeln ('Press ENTER to continue');
readln;
writeln ('blablabla');
writeln ('blablabla');
end;
The thing is that the user can press any button to continue and that's the problem. I want only the program to continue if the user press the enter button of his keyboard. Except, I need it to automatically continue after a period of time, such as 5 seconds, without user input.
How do I make a console application which waits a period of time for the user to press the Enter key, but automatically continues if the user doesn't?
Upvotes: 11
Views: 20353
Reputation: 89
The unit Console
unit Console;
interface
procedure WaitAnyKeyPressed(const TextMessage: string = ''); overload; inline;
procedure WaitAnyKeyPressed(TimeDelay: Cardinal; const TextMessage: string = ''); overload; inline;
procedure WaitForKeyPressed(KeyCode: Word; const TextMessage: string = ''); overload; inline;
procedure WaitForKeyPressed(KeyCode: Word; TimeDelay: Cardinal; const TextMessage: string = ''); overload;
implementation
uses
System.SysUtils, WinAPI.Windows;
procedure WaitAnyKeyPressed(const TextMessage: string);
begin
WaitForKeyPressed(0, 0, TextMessage)
end;
procedure WaitAnyKeyPressed(TimeDelay: Cardinal; const TextMessage: string);
begin
WaitForKeyPressed(0, TimeDelay, TextMessage)
end;
procedure WaitForKeyPressed(KeyCode: Word; const TextMessage: string);
begin
WaitForKeyPressed(KeyCode, 0, TextMessage)
end;
type
TTimer = record
Started: TLargeInteger;
Frequency: Cardinal;
end;
var
IsElapsed: function(const Timer: TTimer; Interval: Cardinal): Boolean;
StartTimer: procedure(var Timer: TTimer);
procedure WaitForKeyPressed(KeyCode: Word; TimeDelay: Cardinal; const TextMessage: string);
var
Handle: THandle;
Buffer: TInputRecord;
Counter: Cardinal;
Timer: TTimer;
begin
Handle := GetStdHandle(STD_INPUT_HANDLE);
if Handle = 0 then
RaiseLastOSError;
if not (TextMessage = '') then
Write(TextMessage);
if not (TimeDelay = 0) then
StartTimer(Timer);
while True do
begin
Sleep(0);
if not GetNumberOfConsoleInputEvents(Handle, Counter) then
RaiseLastOSError;
if not (Counter = 0) then
begin
if not ReadConsoleInput(Handle, Buffer, 1, Counter) then
RaiseLastOSError;
if (Buffer.EventType = KEY_EVENT) and Buffer.Event.KeyEvent.bKeyDown then
if (KeyCode = 0) or (KeyCode = Buffer.Event.KeyEvent.wVirtualKeyCode) then
Break
end;
if not (TimeDelay = 0) and IsElapsed(Timer, TimeDelay) then
Break
end
end;
function HardwareIsElapsed(const Timer: TTimer; Interval: Cardinal): Boolean;
var
Passed: TLargeInteger;
begin
QueryPerformanceCounter(Passed);
Result := (Passed - Timer.Started) div Timer.Frequency > Interval
end;
procedure HardwareStartTimer(var Timer: TTimer);
var
Frequency: TLargeInteger;
begin
QueryPerformanceCounter(Timer.Started);
QueryPerformanceFrequency(Frequency);
Timer.Frequency := Frequency div 1000
end;
function SoftwareIsElapsed(const Timer: TTimer; Interval: Cardinal): Boolean;
begin
Result := (GetCurrentTime - Cardinal(Timer.Started)) > Interval
end;
procedure SoftwareStartTimer(var Timer: TTimer);
begin
PCardinal(@Timer.Started)^ := GetCurrentTime
end;
initialization
if QueryPerformanceCounter(PLargeInteger(@@IsElapsed)^) and QueryPerformanceFrequency(PLargeInteger(@@IsElapsed)^) then
begin
StartTimer := HardwareStartTimer;
IsElapsed := HardwareIsElapsed
end
else
begin
StartTimer := SoftwareStartTimer;
IsElapsed := SoftwareIsElapsed
end
end.
The Test or Sample program
program Test;
{$APPTYPE CONSOLE}
{$R *.res}
uses
WinAPI.Windows,
Console in 'Console.pas';
begin
Console.WaitAnyKeyPressed('Press any key to continue ...');
WriteLn;
Console.WaitAnyKeyPressed(5000, 'I''ll wait 5 seconds until You press any key to continue ...');
WriteLn;
Console.WaitForKeyPressed(VK_SPACE, 'Press [Space] key to continue ...');
WriteLn;
Console.WaitForKeyPressed(VK_ESCAPE, 5000, 'I''ll wait 5 seconds until You press [Esc] key to continue ...');
WriteLn
end.
Upvotes: 7
Reputation: 108948
I have done things similar to this a few times before:
First declare a few global variables:
var
hIn: THandle;
hTimer: THandle;
threadID: cardinal;
TimeoutAt: TDateTime;
WaitingForReturn: boolean = false;
TimerThreadTerminated: boolean = false;
Second, add functions
function TimerThread(Parameter: pointer): integer;
var
IR: TInputRecord;
amt: cardinal;
begin
result := 0;
IR.EventType := KEY_EVENT;
IR.Event.KeyEvent.bKeyDown := true;
IR.Event.KeyEvent.wVirtualKeyCode := VK_RETURN;
while not TimerThreadTerminated do
begin
if WaitingForReturn and (Now >= TimeoutAt) then
WriteConsoleInput(hIn, IR, 1, amt);
sleep(500);
end;
end;
procedure StartTimerThread;
begin
hTimer := BeginThread(nil, 0, TimerThread, nil, 0, threadID);
end;
procedure EndTimerThread;
begin
TimerThreadTerminated := true;
WaitForSingleObject(hTimer, 1000);
CloseHandle(hTimer);
end;
procedure TimeoutWait(const Time: cardinal);
var
IR: TInputRecord;
nEvents: cardinal;
begin
TimeOutAt := IncSecond(Now, Time);
WaitingForReturn := true;
while ReadConsoleInput(hIn, IR, 1, nEvents) do
if (IR.EventType = KEY_EVENT) and
(TKeyEventRecord(IR.Event).wVirtualKeyCode = VK_RETURN)
and (TKeyEventRecord(IR.Event).bKeyDown) then
begin
WaitingForReturn := false;
break;
end;
end;
Now you can use TimeoutWait
to wait for Return, but no longer than a given number of seconds. But you have to set hIn
and call StartTimerThread
before you make use of this function:
begin
hIn := GetStdHandle(STD_INPUT_HANDLE);
StartTimerThread;
Writeln('A');
TimeoutWait(5);
Writeln('B');
TimeoutWait(5);
Writeln('C');
TimeoutWait(5);
EndTimerThread;
end.
You can get rid of StartTimerThread
, especially if you start one thread per call, but it might be more tricky to call TimeoutWait
several times in a row then.
Upvotes: 6
Reputation: 43023
You may try this code (adapted from our SynCommons.pas
unit, within our mORMot framework):
procedure ConsoleWaitForEnterKey(TimeOut: integer);
function KeyPressed(ExpectedKey: Word):Boolean;
var lpNumberOfEvents: DWORD;
lpBuffer: TInputRecord;
lpNumberOfEventsRead : DWORD;
nStdHandle: THandle;
begin
result := false;
nStdHandle := GetStdHandle(STD_INPUT_HANDLE);
lpNumberOfEvents := 0;
GetNumberOfConsoleInputEvents(nStdHandle,lpNumberOfEvents);
if lpNumberOfEvents<>0 then begin
PeekConsoleInput(nStdHandle,lpBuffer,1,lpNumberOfEventsRead);
if lpNumberOfEventsRead<>0 then
if lpBuffer.EventType=KEY_EVENT then
if lpBuffer.Event.KeyEvent.bKeyDown and
((ExpectedKey=0) or (lpBuffer.Event.KeyEvent.wVirtualKeyCode=ExpectedKey)) then
result := true else
FlushConsoleInputBuffer(nStdHandle) else
FlushConsoleInputBuffer(nStdHandle);
end;
end;
var Stop: cardinal;
begin
Stop := GetTickCount+TimeOut*1000;
while (not KeyPressed(VK_RETURN)) and (GetTickCount<Stop) do
Sleep(50); // check every 50 ms
end;
Note that the version embedded in mORMot does allow to call the TThread.Synchronize()
method and also handle a GDI message loop, if necessary. This procedure just fits your need, I hope.
Upvotes: 12