How to make a console application wait for the "Enter" key, but automatically continue after time?
Asked Answered
M

3

11

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?

Minos answered 15/4, 2013 at 18:21 Comment(19)
Readln blocks execution flow until user presses <kbd>RETURN</kbd>.Trek
Well yes, that's the crux of the question isn't itPredominate
If you want help with your code, post real code . Posting something that has no meaning to your question doesn't help. (There are no "buttons" in a console application, so I'm guessing you mean the 5 key. ReadLn waits until Enter is pressed, so it won't work with the 5 key unless you press 5 and then Enter. But it's not clear what you're really asking here, and your code doesn't help. "Shows messages on button 5" and "Press ENTER to continue" don't seem to match. Please edit your question to make it more clear about what you're asking us. Thanks.Acetaldehyde
Whatever you do, don't write a program that waits until the SHIFT key is pressed. Those keys don't work on your keyboard. Seriously though, use capital letters and show that you care about this question.Predominate
For heaven's sake. I keep editing the question to fix your inability to use the SHIFT key. And you keep undoing my changes. I give up. I've down voted you now.Predominate
I'd like to apologise to everyone for not asking a clear question. I am just too tired trying to find out how i can do this thing. I didn't know what i was writing. Thanks for your patientMinos
Well, it might cease to wait for <kbd>RETURN</kbd> if console's processed input mode has been unset, for example. But that's a fortune-telling...Trek
Mr David Heffernan, i think i made the question to be clear now, don't you thing? :P would you mind telling me how can i do this thing? ThanksMinos
Did you read my comments? I was annoyed that I spent time improving your question and you just discarded my improvements. It's as though you don't care.Predominate
So you want more of a countdown from 5 seconds, right? If user presses enter at any time, it will continue, but if they don't, it will automatically continue after 5 seconds? I also recall you saying something earlier about pressing the 5 key (what I understood) but now you're talking about just the enter key?Cotsen
That's right...I Really care about the questionMinos
I personally think it's a good question, but it is still a bit unclear. Please allow me to edit it for you...Cotsen
Thank you guys for editing my question...Now could you please tell me how to do it? :PMinos
@David: I have done this a few times before. I'm writing an answer right now.Criminate
Topic has been dealt with here, Using VCL TTimer in Delphi console application.Fuchsin
@LURD That's a different topic altogether.Predominate
@LURD The linked question appears to be just about using timers in a console application (since they don't have a message pump). This one is specifically waiting for input, and automatically resuming after time.Cotsen
@DavidHeffernan, allright, I meant the part with with while not KeyPressed do .... I should have linked this instead, How i can implement a IsKeyPressed function in a delphi console application?. Arnaud got it right.Fuchsin
@JerryDodge, sorry, I was not clear enough about what I meant. See my reply to David.Fuchsin
S
12

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.

Sunburst answered 15/4, 2013 at 19:31 Comment(3)
it doesn't recognise any of the variables, what's the library?Minos
No library, only plain Windows API calls I guess.Sunburst
has it moved on the latest version?Butterball
C
6

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.

Criminate answered 15/4, 2013 at 19:42 Comment(1)
ok thanks this is working, however there are some operators missing in ur code, which i had to put, but it's workingMinos
C
6

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.
Cleavable answered 31/3, 2015 at 20:50 Comment(1)
Thanks for posting this console unit! It will be useful. Was it copied from a larger unit or library?Austinaustina

© 2022 - 2024 — McMap. All rights reserved.