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?

Jerry Dodge
  • 26,858
  • 31
  • 155
  • 327
user2276109
  • 143
  • 1
  • 3
  • 7
  • `Readln` blocks execution flow until user presses RETURN. – OnTheFly Apr 15 '13 at 18:26
  • Well yes, that's the crux of the question isn't it – David Heffernan Apr 15 '13 at 18:31
  • 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. – Ken White Apr 15 '13 at 18:31
  • 1
    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. – David Heffernan Apr 15 '13 at 18:36
  • 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. – David Heffernan Apr 15 '13 at 18:39
  • 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 patient – user2276109 Apr 15 '13 at 18:39
  • Well, it might cease to wait for RETURN if console's processed input mode has been unset, for example. But that's a fortune-telling... – OnTheFly Apr 15 '13 at 18:39
  • 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? Thanks – user2276109 Apr 15 '13 at 18:42
  • 1
    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. – David Heffernan Apr 15 '13 at 18:44
  • 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? – Jerry Dodge Apr 15 '13 at 18:44
  • That's right...I Really care about the question – user2276109 Apr 15 '13 at 18:50
  • I personally think it's a good question, but it is still a bit unclear. Please allow me to edit it for you... – Jerry Dodge Apr 15 '13 at 18:56
  • Thank you guys for editing my question...Now could you please tell me how to do it? :P – user2276109 Apr 15 '13 at 19:14
  • @David: I have done this a few times before. I'm writing an answer right now. – Andreas Rejbrand Apr 15 '13 at 19:27
  • Topic has been dealt with here, [Using VCL TTimer in Delphi console application](http://stackoverflow.com/a/12027967/576719). – LU RD Apr 15 '13 at 20:13
  • 1
    @LURD That's a different topic altogether. – David Heffernan Apr 15 '13 at 21:03
  • @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. – Jerry Dodge Apr 15 '13 at 21:04
  • @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?`](http://stackoverflow.com/q/5845080/576719). Arnaud got it right. – LU RD Apr 15 '13 at 21:16
  • @JerryDodge, sorry, I was not clear enough about what I meant. See my reply to David. – LU RD Apr 15 '13 at 21:21

3 Answers3

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.

Arnaud Bouchez
  • 42,305
  • 3
  • 71
  • 159
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.

Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
  • ok thanks this is working, however there are some operators missing in ur code, which i had to put, but it's working – user2276109 Apr 15 '13 at 21:23
5

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.
Mega WEB
  • 69
  • 1
  • 4