Delphi programming
step by step program execution


In some cases, as in educational software, it is necessary to execute a program step-by-step.
A convenient way to advance each step is by hitting the SPACE bar.

But also is must be possible to terminate the process and return to an input mode or whatever.
A good key for this is ESCAPE.

Implementation of both demands requires two boolean variables.
1.
var stopflag : boolean;

stopflag is normally true.
However, hitting the space bar sets stopflag false, which causes the program to continue.

2.
var runflag : boolean;

Runflag is normally true.
Program execution may proceed in this case.
Hitting ESCAPE clears runflag and the execution of our process stops.
The program itself does not stop, but only waits for new commands after skipping procedures.

A good way to halt program execution is
    application.processmessages;
This calls Windows to handle events.

A procedure for the program stop may be
procedure programstep;
begin
  stopflag := true;
  while stopflag do application.processmessages;
end;  
Say we have processes 1..4 to step through.

The program now will look like:
procedure DoJob;
begin
  runflag := true;
  process1;
  programstep;
  if runflag then process2;
  programstep;
  if runflag then process3;
  programstep;
  if runflag then process4;
  programstep;
end;
runflag may also depend on process results such as error codes.
In the case of errors, the processes that follow will be skipped if runflag is set false.

A problem is closing the form while stopped.
So, the Close event must set runflag and stopflag to false, otherwise the program will not close until finished.

Normally a button press will start the job.
It is convenient to use this button as well to continue the program when stopped.
The final program (see below) takes care of that.

In a project, events from keyboard or mouse have a meaning that depends on the state of the program.
Editing input data, processing data, waiting for a command.....
Therefore a variable with name such as programstate is needed.
In this simple case, programstate may be psIdle or psExecute,
to differentiate between executing and not executing the processes 1..4.

On form1 is placed a button (name GO).
Paintbox1 shows some arbitrary actions of procedures 1..4.
A label named msglabel allows for the display of messages.
That's all.

Do not forget to set keypreview true in the form1 object inspector.

unit Unit1;
interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    msglabel: TLabel;
    GoBtn: TButton;
    PaintBox1: TPaintBox;
    procedure GoBtnClick(Sender: TObject);
    procedure FormKeyPress(Sender: TObject; var Key: Char);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

type Tprogramstate = (psIdle,psExecute);

var runflag : boolean;
    stopflag :boolean;
    programstate : TProgramstate = psIdle;

procedure programstep;
//stop program execution until step or escape
//do not stop if runflag is dropped
begin
 if runflag = false then exit;
 form1.msglabel.Caption := 'stop: press  to continue,  to stop';
 stopflag := true;
 while stopflag do application.ProcessMessages;
end;

procedure clearpaintbox;
begin
 with form1.PaintBox1 do with Canvas do
  begin
   brush.Color := form1.Color;
   brush.Style := bsSolid;
   fillrect(rect(0,0,width,height));
   font.Height := 20;
   font.Name := 'arial';
   font.Color := $000000;
  end;
end;   

procedure process1;
var r : Trect;
begin
 with form1.PaintBox1 do with canvas do
  begin
   r := rect(0,0,width,height);
   brush.color := $00ffff;
   brush.style := bsSolid;
   fillrect(r);
   framerect(r);
   font.color := $0000ff;
   brush.Style := bsClear;
   textout(10,10, 'process1 completed');
  end;
end;

procedure process2;
begin
 with form1.PaintBox1 do with canvas do
  begin
   brush.Style := bsSolid;
   brush.Color := $ff0000;
   ellipse(200,10,300,60);
   font.Color := $ff0000;
   brush.Style := bsClear;
   textout(10,40,'process2 completed');
  end;
end;

procedure process3;
begin
  with form1.PaintBox1 do with canvas do
  begin
   brush.Style := bsSolid;
   brush.Color := $00ff00;
   ellipse(200,70,300,120);
   font.color := $00ff00;
   brush.Style := bsClear;
   textout(10,70,'process3 completed');
  end;
end;

procedure process4;
begin
 with form1.PaintBox1 do with canvas do
  begin
   brush.Style := bsSolid;
   brush.Color := $ff00ff;
   ellipse(200,130,300,190);
   font.color := $ff00ff;
   brush.Style := bsClear;
   textout(10,100,'process4 completed');
  end;
end;

procedure TForm1.GoBtnClick(Sender: TObject);
//GO button pressed to start or to advance one step
begin
 if programstate = psExecute then
  begin
   stopflag := false; //step thru if waiting
   exit;
  end;

 activecontrol := nil;//prevent space key to activate GO button
 runflag := true;
 clearpaintbox;
 programstate := psExecute;

 process1;
 programstep;

 if runflag then process2;
 programstep;

 if runflag then process3;
 programstep;

 if runflag then process4;
 programstep;

 programstate := psIdle;
 if runflag then msglabel.Caption := 'process completed'
  else msglabel.Caption := 'process interrupted';
end;

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
begin
  if programstate = psExecute then
   begin
    if key = #32 then stopflag := false;       //space
    if key = #27 then begin                    //escape
                       runflag := false;
                       stopflag := false;
                      end;
    key := #0;
   end;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  runflag := false;
  stopflag := false;
end;

end.