Reputation: 35
I'm using Delphi 7 and the program I am writing needs to continuously draw on the screen. While it currently doesn't draw anything important, this is a necessity in the program later on. However, when I put the procedure for drawing the screen in a while loop which can only be stopped by pressing any button the program stops responding completely. I don't understand why this is happening. Surely, as the while loop can be exited, the program should continue to run fine. Here is the source code:
unit DD04f1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, TeCanvas, ExtCtrls;
type
TForm1 = class(TForm)
Image1: TImage;
Button1: TButton;
procedure Image1OnCreate();
procedure ScreenRender();
procedure OnCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
IsDone : Boolean;
implementation
{$R *.dfm}
procedure TForm1.OnCreate(Sender: TObject);
begin
IsDone := False;
end;
procedure TForm1.Image1OnCreate ();
var
Count:Integer;
begin
image1.canvas.Create();
image1.canvas.Pen.Color:=clBlack;
image1.canvas.rectangle(0,0,640,480);
image1.canvas.Pen.Color:=$ed630e; //bgr instead of rgb
Count:=0;
While (Count <> 640) do
begin
image1.Canvas.moveto(Count,0);
image1.Canvas.LineTo(Count,480);
Count:=Count+1;
end;
end;
procedure TForm1.ScreenRender();
var
Count : Integer;
begin
Count:=0;
While(Count<>640) do
begin
image1.Canvas.moveto(Count,0);
image1.Canvas.LineTo(Count,480);
Count:=Count+1;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Image1OnCreate();
Button1.Visible := False;
While(IsDone = False) do
begin
ScreenRender();
end;
end;
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
IsDone := True;
end;
end.
Upvotes: 0
Views: 2301
Reputation: 1328
You already got an excellent answer why your current code does not work and in your comments you are mentioning you want to do ray casting and drawing from a players perspective, so I assume some kind of game background.
I'm not sure the VCL is the best basis for a game. Different philosophies and needs. As Arioch 'The explained Delphi's VCL is event driven. Things happen in response to windows messages, even painting. If nothing causes a need to repaint, nothing will be painted anew.
This is very different from how I understand game engines (I'm by no means an expert). Even if nothing happens, they will continuously draw frame after frame to present as fluid as possible. Each frame might include an update to underlying structures based on game rules, physics, player input, animation, but even when they remain the same a new frame will be drawn. Basically three steps happen in a simplified 'game loop'
All this happens for every frame. There might be no input, no update of the game's structures or even no presentation is desired. But all three steps belong together, the input causing an update that is later presented happened in the exact same frame as the resulting drawing.
This is something I find hard to fit into the VCL. As a solution must be based on the existing VCL loop and windows messages. You basically attempted to create such a game loop in VCL.
A way to solve your immediate issue - that you want to present something based on a calculation - could be just using the principle of the VCL. You want to have something drawn. VCL controls normally communicate their desire to be drawn by Invalidate
, causing their BoundsRect
to be invalidated. You could do that after you have done your calculations. In the following example I'll just use a timer to simulate your calculations are done. Just be aware that Invalidate
will cause WM_PAINT
messages to be generated for the control, but will not cause immediate repainting. There might be messages queued before the WM_PAINT
is processed.
I'm using a TPaintBox
's OnPaint
to actually do the painting work, you might want to have your own control for that in the future when your project progresses.
unit Unit2;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls;
type
TFormMain = class(TForm)
procedure FormCreate(Sender: TObject);
private
Timer1: TTimer;
PaintBox1: TPaintBox;
{ Private declarations }
procedure PaintBox1Paint(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
public
{ Public declarations }
end;
implementation
{$R *.dfm}
procedure TFormMain.FormCreate(Sender: TObject);
begin
PaintBox1 := TPaintBox.Create(Self);
PaintBox1.Parent := Self;
PaintBox1.Align := alClient;
PaintBox1.OnPaint := PaintBox1Paint;
Timer1 := TTimer.Create(Self);
Timer1.Interval := 100;
Timer1.OnTimer := Timer1Timer;
Randomize;
end;
procedure TFormMain.PaintBox1Paint(Sender: TObject);
var
AColor: TColor;
I: Integer;
begin
for I := 0 to PaintBox1.ClientWidth - 1 do
begin
AColor := RGB(Random(256), Random(256), Random(256));
PaintBox1.Canvas.Pen.Color := AColor;
PaintBox1.Canvas.MoveTo(I, 0);
PaintBox1.Canvas.LineTo(I, PaintBox1.ClientHeight);
end;
end;
procedure TFormMain.Timer1Timer(Sender: TObject);
begin
PaintBox1.Invalidate;
end;
end.
Upvotes: 2
Reputation: 16045
procedure TForm1.OnCreate(Sender: TObject);
begin
IsDone := False;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Image1OnCreate();
Button1.Visible := False;
While(IsDone = False) do
begin
ScreenRender();
end;
end;
Assuming IsDone
is always False
(because otherwise we would not enter the loop), this loop can not terminate. It is infinite.
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
IsDone := True;
end;
You do not call this procedure from inside TForm1.Button1Click
loop, hence it can never be called after you entered that loop. Since you never exit the TForm1.Button1Click
procedure you do not allow any outside agent (like messages dispatch loop in VCL) to get executed and call that procedure either. To sum it up as soon as you entered the loop there is no any executable code that can change IsDone
value. So, it is not changed.
Event handlers are supposed to be very short procedures, executing almost immediately, and giving up "execution flow control" back to VCL internals. Every long (more so infinite) processing leads to the program becomes irresponsive. No matter how many news Windows might want to tell the program - the program never asks for them.
It was once told that Windows windows (GDI objects) are living in the center of the "messages storm" that they have to work out in timely matter. Hundreds of those messages are incoming every second and a Window Procedure (built inside the VCL classes for Delphi 7 forms) should receive, dispatch, and process every one of them before it's too late.
As soon as you blocked that process by making one of event handlers long or even endless - you broke the basic contract between the OS and the application.
You have to do "inversion of control", to break your continuous work into small short chunks and make Windows call those chunks when it sees appropriate.
Try to use TTimer
for example.
PS. A VERY remote problem you can look at:
Skip all the multithreading stuff there, for your case it only is important that other threads create those "chunks of work" that we have to paint onto our forms when Windows asks us to do so at some reasonable framerate (not too fast and not too slow). Your work chunks are fundamentally different, so all the threading stuff unrelated to you.
And the rendering is made inside TTimer
events. So the "framework" of setting up the timer, turning it on and off might be of some interest to you. However the work you are going to do inside the .OnTimer
event would be significantly different (just painting something, or even merely invalidating
some part of the form and waiting for the Windows to trigger OnPaint
event.).
Upvotes: 2