I am running an application developed with RAD Studio XE or Delphi XE under Windows 7. After rebuilding my application from previous project files, I have been testing its functionality, but the application would only run for about a month or so and starts to fail slowly. This application is supposed to run 24/7 for all time, unless Windows OS fails. So, I ran AQTime on the application for few hours and closely watched the results as the program was running. What I noticed was this. With everything else being constant and still in numbers, under resource profiling Brush, Handle, Pen and another Pen are slowly increasing in numbers especially the second pen and Brush resources. Pen seems to be increasing in numbers by about 522 every second. Also, number of handle is going up but very slowly - maybe every 15 minutes. On a side note: some of our users have had a total
catastrophic failure, where Windows OS will die completely to a point that you have to reinstall Windows again and everything else.
AQTime result:
Class_Name Object_Name
Brush Brush:54,947
Handle Handle:44,559 --Handle is increasing slowly
Pen Pen:53,378
Pen Pen:54,915 --Pen is increasing every second by 522.
The application's main window is always going to be displayed on the screen.
UPDATE2:
pen, oldPen Bursh and oldBursh are declared within a base class. They are assigned everytime within the following procedure and the procedure is used throughout the program for drawing elements right on the TForm like circle, polygon, square, line, etc.
procedure TMakerGraphic.SaveCanvas;
begin
oldPen.Assign(myForm.Canvas.Pen);
oldBrush.Assign(myForm.Canvas.Brush);
myForm.Canvas.Pen.Assign(Pen);
myForm.Canvas.Brush.Assign(Brush);
end;
procedure TMakerGraphic.RestoreCanvas;
begin
myForm.Canvas.Pen.Assign(oldPen);
myForm.Canvas.Brush.Assign(oldBrush);
end;
The Only time these variables are released is when the elements on the TForm is deleted as shown by the following Free procedure.
destructor TMakerGraphic.Free;
begin
Pen.Free;
Brush.Free;
oldPen.Free;
oldBrush.Free;
inherited Free;
end;
So, is that mean my application is leaking memory?
Any input will be greatly appreciated. Thank you.
It seems that instances of Pen and Brush are not freed properly.
If you are using the built-in drawing procedures of TCanvas, use
Canvas.Pen.Assign(anotherPen)
to fill in a new pen.
If you are using gdiplus.dll via IGDIPlus, each drawing procedure will take a argument of IGPPen or IGPBrush. Thus, either declaring the variables to pass through as IGPPen/IGPBrush; or declaring them as TGPPen/TGPBrush, and free them afterwards.
================= temporary space for sample source code ==========
I would think the following code, as OP gives, does not incur memory/resource leak.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
currPen, prevPen: TPen;
currBrush, prevBrush: TBrush;
procedure FormPaint(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure SaveCanvas;
procedure RestoreCanvas;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
currPen := TPen.Create;
prevPen := TPen.Create;
currBrush := TBrush.Create;
prevBrush := TBrush.Create;
Self.OnPaint := Self.FormPaint;
Self.OnDestroy := Self.FormDestroy;
end;
procedure TForm1.FormPaint(Sender: TObject);
begin
SaveCanvas;
RestoreCanvas;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
prevPen.Free;
prevBrush.Free;
currPen.Free;
currBrush.free;
end;
procedure TForm1.SaveCanvas;
begin
prevPen.Assign(Self.Canvas.Pen);
prevBrush.Assign(Self.Canvas.Brush);
Self.Canvas.Pen.Assign(currPen);
Self.Canvas.Brush.Assign(currBrush);
end;
procedure TForm1.RestoreCanvas;
begin
Self.Canvas.Pen.Assign(prevPen);
Self.Canvas.Brush.Assign(prevBrush);
end;
end.
Related
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.
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.
https://en.wikipedia.org/wiki/Event-driven_programming
https://msdn.microsoft.com/en-us/library/windows/desktop/ms644927.aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/ms632593.aspx
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:
How to use Pipeline pattern in Delphi
How to Stop all Pipeline tasks correctly
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.).
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'
Input
Update
Presentation
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.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
I'm using Delphi 7.
I have two units containing Form1 and Form2. The secondary form will be called many times during some process, and I am very worried about memory usage.
When I start the program, memory usage is around 2.1 MB. When Form2 is called, memory grows to 2.9 MB. After this process, I close Form2 and call it again to simulate regular usage, and memory grows to 3.1 MB, call again and memory grows to 3.4 MB, 3.6 MB, 3.8 MB, etc.
Memory usage is the main issue.
Form1 is calling Form2 like this:
uses
Unit2;
...
private
{ Private declarations }
FChild : TForm2;
...
FChild := TForm2.Create(nil);
try
FChild.ShowModal;
finally
FChild.Free;
end;
Inside Unit2:
procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
Am I doing something wrong? Is there a better solution?
Please, this not a simple question, because this program will be running 24 hours, and the second form will be called many times. This means that sometimes this program will freeze the computer.
I included FASTMM4 inside project:
program Project1;
uses
FastMM4,
Forms,
Unit1 in 'Unit1.pas' {Form1},
Unit2 in 'Unit2.pas' {Form2};
{$R *.res}
begin
FullDebugModeScanMemoryPoolBeforeEveryOperation := True;
SuppressMessageBoxes:=False;
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
This program is reading a fingerprint.
Unit1:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, Unit2;
type
TForm1 = class(TForm)
btnForm2: TButton;
btnReselease: TButton;
procedure btnForm2Click(Sender: TObject);
procedure btnReseleaseClick(Sender: TObject);
private
{ Private declarations }
FChild : TForm2;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.btnForm2Click(Sender: TObject);
begin
FChild := TForm2.Create(nil);
try
FChild.ShowModal;
finally
FChild.Free;
end;
end;
procedure TForm1.btnReseleaseClick(Sender: TObject);
begin
if FChild <> nil then
begin
FreeAndNil(FChild);
end;
end;
end.
Unit2:
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons, pngimage, ExtCtrls, Grids, XMLIntf, XMLDoc,
ComCtrls, CPort;
type
TForm2 = class(TForm)
btnSyncFP: TBitBtn;
btnExitFP: TBitBtn;
btnDeleteFP: TBitBtn;
btnCaptureFP: TBitBtn;
ComImage: TImage;
FPGrid: TStringGrid;
prgBar: TProgressBar;
lblbar: TLabel;
ComPortA: TComPort;
ComDataPacket1: TComDataPacket;
procedure LoadUsers2;
procedure FormCreate(Sender: TObject);
procedure ComPortAAfterOpen(Sender: TObject);
procedure ComPortAAfterClose(Sender: TObject);
procedure btnExitFPClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
end;
var
// Form2: TForm2;
L1, L2: TStringList;
implementation
{$R *.dfm}
procedure TForm2.LoadUsers2;
var
XML : IXMLDOCUMENT;
CurNode, CNode : IXMLNODE;
i : Integer;
begin
XML := NewXMLDocument;
XML.LoadFromFile('usuario.xml');
XML.Active := True;
CurNode := XML.DocumentElement.ChildNodes[0]; // users
FPGrid.RowCount := CurNode.ChildNodes.Count+1;
prgBar.Min := 0;
prgBar.Max := CurNode.ChildNodes.Count-1;
lblBar.Caption := 'Carregando usuários...';
for i := 0 to CurNode.ChildNodes.Count-1 do
begin
CNode := CurNode.ChildNodes[i]; // groups
with CNode do
begin
FPGrid.Cells[2,i+1] := Attributes['group'];
FPGrid.Cells[1,i+1] := Attributes['id'];
FPGrid.Cells[0,i+1] := Attributes['name'];
FPGrid.Cells[3,i+1] := Attributes['fingerID'];
FPGrid.Cells[4,i+1] := Attributes['level'];
FPGrid.Cells[5,i+1] := Attributes['status'];
end;
if FPGrid.Cells[3,i+1]<>'' then L1.Add(FPGrid.Cells[3,i+1]);
prgBar.Position := i;
end;
XML := nil;
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
LoadUsers2;
with FPGrid do
begin
Font.Name := 'Tahoma';
Font.Size := 12;
ColCount := 4;
Cells[0,0] := 'Name'; Cells[1,0] := 'ID';
Cells[2,0] := 'Group'; Cells[3,0] := 'Read ID';
Cells[4,0] := 'Level'; Cells[5,0] := 'Status';
ScrollBars := ssVertical;
Options := Options + [goRowSelect];
end;
ComPortA.Open;
end;
procedure TForm2.ComPortAAfterOpen(Sender: TObject);
begin
ComImage.Picture.LoadFromFile('conn_on.png');
end;
procedure TForm2.ComPortAAfterClose(Sender: TObject);
begin
ComImage.Picture.LoadFromFile('conn_off.png');
end;
procedure TForm2.btnExitFPClick(Sender: TObject);
begin
ComPortA.Close;
Close;
end;
procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
end.
I know that Task Manager is the best to verify leak memory, but after few hours, the program is growing faster and not realeasing memory.
I let the program running all night and memory is backing, but there is a long time not enough as user usage.
No, apart from a potential error where the form may be "Freed" twice, you appear to be doing everything correctly, assuming that your Form2 implementation does not itself introduce memory leaks.
I am assuming that you are monitoring "memory use" by using Task Manager. If so, this is highly misleading.
Your application is managing its memory using the Delphi heap manager. This heap manager will allocate memory (requesting it from the OS) as required but when it is freed it is not immediately returned to the system (OS), but simply marked as no longer in use. Then, when memory is required by the application in the future, the heap manager can "recycle" this unused memory instead of having to go back to the OS to ask for more memory (which is a relatively "expensive" operation).
However, the way that the heap manager determines whether unused memory can be recycled to satisfy a new request can mean that memory that could potentially be recycled may not be, for example as the result of fragmentation.
Imagine your application allocated 500 bytes of memory then a further 100 bytes and then another 500 bytes:
[1] [used] 500 bytes
[2] [used] 100 bytes
[3] [used] 500 bytes
Imagine then that the two 500 bytes blocks are freed, making them available for re-use.
[1] [free] 500 bytes
[2] [used] 100 bytes
[3] [free] 500 bytes
You might think that a request for 1000 bytes (or even 600, 700, 800 bytes etc) would be able to use this "recyclable" memory.
But that request for 1000 bytes requires a single, contiguous block, and with that 100 byte block still in use, those two 500 byte blocks can only be used for requests for (a maximum of) 500 bytes each. So, a request for 1000 bytes has to be satisfied by allocating a new 1000 byte block:
[1] [free] 500 bytes
[2] [used] 100 bytes
[3] [free] 500 bytes
[4] [used] 1000 bytes
Your application is still only "using" 1100 bytes, but to do so, 2100 bytes have been allocated from the OS.
The net result of all this is that memory "use" can appear to grow in Task Manager when in fact what is really happening is that your application is simply "holding on" to allocated memory it is no longer actually using, just in case it might need it in the future.
If the OS reached a point where it needed memory back, then all processes would be asked to relinquish such memory, and your application would be no different.
You can simulate this by minimising and restoring your application. Unless your application is genuinely using all of the memory currently allocated, you should see a drop in memory use. This drop might be slight or it could be significant, depending on the memory usage profile of your application.
When a (Delphi) application is minimized it will return some/as much memory to the system, on the basis that if the user has minimized it then it is now a "backgrond" process which is not likely to need to have any demands for memoy in the near future.
You can trigger this behaviour with some code in your application OnIdle event, but doing so is mostly pointless. It may give the impression of reduced memory use in Task Manager, but will potentially reduce performance of your application and doesn't actually reduce your memory usage.
What Can I Do About It ?
The Delphi runtime has always supported the ability to replace the application heap manager with alternative implementations.
A popular one (which has been adopted as the new standard since Delphi 2006) is FastMM. This implements strategies that avoid or reduce memory fragmentation and provides other performance and debugging improvements (it can be configured to report memory leaks and incorrectly used references to destroyed objects, for example).
FastMM is open source and can be used even in a Delphi 7 application. All you need to do is download the source and add FastMM as the first unit used in your project dpr file.
Don't use Action := caFree and Free() together on the same object. Use one or the other, not both. The call to Free() will actually cancel out the Action:=caFree, making it redundant.
But that is not the cause of your problem.
The code you have shown is fine in general, Form2 gets freed from memory correctly. What you are not taking into account is that Delphi's Memory Manager simply does not release freed memory back to the OS, it holds on to it and reuses it for future allocations. If you are using Task Manager to track memory usage, that is not a good tool for diagnosing memory leaks, since it has no concept of how Delphi caches freed memory over time.
However, that being said, the growth you describe sounds more like memory fragmentation rather than a memory leak. But it is hard to say for sure since you did not show what all is actually on Form2, or provide a Minimal, Complete, and Verifiable example to reproduce the issue.
In any case, Delphi 7's default memory manager wasn't very efficient in general. That is why Borland eventually switched to FastMM in Delphi 2006. You could try replacing the default memory manager with a newer one and see if it helps.
Use FastMM. See article and add FastMM.
If you can, create separated build configuration with fully enabled FastMM diagnostics and run it i. e. before merging to master git branch. I use this strategy and greatly increase quality of my code, without any drawbacks (FastMM diagnostics are kinda heavy for compiler, but I run them not often, so it's ok).
I have to write program in Delphi using VCL forms. Three figures which are square, hexagon and octagonal must move to up border, then to bottom border and so on. The problem is that my program freezes, when I'm trying to put values in condition operators to stop moving, if coordinate Y = 0. Though it works(strangely) if I put value = 180, for example.
unit Main;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;
type
TMainForm = class(TForm)
Image: TImage;
BeginButton: TButton;
EndButton: TButton;
Timer1: TTimer;
Edit1: TEdit;
procedure FormActivate(Sender: TObject);
procedure BeginButtonClick(Sender: TObject);
procedure EndButtonClick(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
uses Figure;
{$R *.dfm}
Var
t:single=0.0;
L:TSquare;
S:THexagon;
C:TOctagon;
Moving:Boolean=true;
procedure TMainForm.FormActivate(Sender: TObject);
begin
Image.Canvas.Brush.Color:=clWhite;
end;
procedure TMainForm.Timer1Timer(Sender: TObject);
begin
L.Move(t);
S.Move(-0.2*t);
C.Move(0.5*t);
t:=t+0.5;
end;
procedure TMainForm.BeginButtonClick(Sender: TObject);
begin
L:=TSquare.Create(60,35,Image);
S:=THexagon.Create(180,100,Image);
C:=TOctagon.Create(300,100,Image);
Timer1.Enabled:=true;
end;
procedure TMainForm.EndButtonClick(Sender: TObject);
begin
Close;
end;
initialization
finalization
L.Free;
S.Free;
C.Free;
end.
And second Unit:
Unit Figure;
Interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;
Type
TFigure=Class
private x,y, b,
dx:integer;
Image:TImage;
procedure Draw;virtual;abstract;
procedure Rel(t:real);virtual;
public
constructor Create(ax,ay:integer;aImage:TImage);
procedure Move(t:single);
end;
THexagon=Class(TFigure)
private procedure Draw;override;
end;
TSquare=Class(TFigure)
private procedure Draw;override;
end;
TOctagon=Class(TFigure)
private procedure Draw;override;
end;
Implementation
Constructor TFigure.Create;
Begin
inherited Create;
x:=ax; y:=ay; Image:=aImage;
End;
Procedure TFigure.Rel;
Begin
dx:=5*round(t);
End;
Procedure TFigure.Move;
Begin
Image.Canvas.Pen.Color:=clWhite;
Draw;
Image.Canvas.Pen.Color:=clBlack;
Rel(t);
Draw;
End;
Procedure TSquare.Draw;
Begin
b:=70;
Image.Canvas.MoveTo(x+round(0.5*b),y-round(0.5*b));
Image.Canvas.LineTo(x-round(0.5*b),y-round(0.5*b));
Image.Canvas.LineTo(x-round(0.5*b),y+round(0.5*b));
Image.Canvas.LineTo(x+round(0.5*b),y+round(0.5*b));
Image.Canvas.LineTo(x+round(0.5*b),y-round(0.5*b));
End;
Procedure THexagon.Draw;
Begin
b:=70;
repeat
begin
Image.Canvas.MoveTo(x+round(0.5*b),y+dx);
Image.Canvas.LineTo(x+round(0.25*b),y+round(0.5*b)+dx);
Image.Canvas.LineTo(x-round(0.25*b),y+round(0.5*b)+dx);
Image.Canvas.LineTo(x-round(0.5*b),y+dx);
Image.Canvas.LineTo(x-round(0.25*b),y-round(0.5*b)+dx);
Image.Canvas.LineTo(x+round(0.25*b),y-round(0.5*b)+dx);
Image.Canvas.LineTo(x+round(0.5*b),y+dx);
end;
until ((y+round(0.5*b)+dx)<180);
End;
Procedure TOctagon.Draw;
var
I: Integer;
p: array[1..9] of tpoint;
u:extended;
Begin
x:=300;
y:=100;
u:=0;
for I := 1 to 8 do
begin
p[i].X:=x+round(40*cos(u));
p[i].Y:=y-round(40*sin(u));
u:=u+pi/4;
end;
repeat
begin
Image.Canvas.MoveTo(p[8].x,p[8].y-dx);
for I := 1 to 8 do
Image.Canvas.LineTo(p[i].X,p[i].y-dx);
end;
until (p[3].y>50);
End;
end.
Delphi comes with an integrated debugger. You should use it. Here's how to start investigating a case where a program seems to hang.
Start your program under control of the debugger with the "play" button.
Reproduce the situation you're trying to investigate.
When the program hangs, switch to the debugger and press the "pause" button. The debugger will interrupt the execution of your program so you can investigate the current state.
Look at the call stack. (If the call-stack window isn't already visible, you can show it by using the "debug windows" menu option in the IDE.)
The call stack will show the list of functions your program has called. At the top of the stack will be the function your program was running at the moment you paused. The function below it will be the function that called the current function, and so on until you reach the bottom of the stack, which represents the main function of your program.
The function you stop in probably won't be one you wrote. Instead, it's usually a function provided by the OS or by the Delphi run-time library. You don't want to debug those. Generally, you can assume they already work properly. You're looking for a bug in your code instead.
Use the "run until return" command to let the topmost function continue running. Repeat that until you reach one of your functions on the call stack. That's probably the culprit.
Now that you've identified the problematic function, it's time to investigate it further.
Use the "step over" debugger command to run each line of your function one by one. (There's also a "step into" command, but that will step into functions that aren't yours, and you're not interested in those now.)
Observe the current values of variables in your code. You can hover the mouse over a variable to let the debugger display its value in a tool tip, or you can use the "watches" debug window to display multiple variables at once. They'll be updated after each step in your program.
Pay attention to the variables' values. You should already have some expectation of how their values should progress over the course of your program. You probably thought about that progression while you were writing the code. Think back to that time and compare the results you observe in the debugger with your previous expectations. Do they match? If so, then keep stepping through the code. If they don't match, though, then you've found a bug. Fix it.
Another source of unexpected behavior is to reach a point in your program that you didn't expect to reach. Maybe the program called a function it shouldn't have, or maybe you've executed a loop more times you wanted to. If you can work out the reason, then fix the bug. Otherwise, you might need to back up a little ways.
Identify a point in your program earlier than where you have observed the unexpected behavior. Look for the blue dots in the left margin of the code editor. Those dots represent places where you can set a breakpoint. Click one of the dots, and you should notice the line be highlighted (probably in red).
Terminate your program, and run it again.
This time, you should see the debugger stop before the program appears to hang because execution will have reached the breakpoint first. The debugger interrupts your program there.
Step through the lines of your code as you did before, and watch for the condition that causes your program to veer from the expected path of execution. When you've identified the bug, fix it.
It freezes because your repeat-until loop will never end.
repeat
begin
Image.Canvas.MoveTo(x+round(0.5*b),y+dx);
Image.Canvas.LineTo(x+round(0.25*b),y+round(0.5*b)+dx);
Image.Canvas.LineTo(x-round(0.25*b),y+round(0.5*b)+dx);
Image.Canvas.LineTo(x-round(0.5*b),y+dx);
Image.Canvas.LineTo(x-round(0.25*b),y-round(0.5*b)+dx);
Image.Canvas.LineTo(x+round(0.25*b),y-round(0.5*b)+dx);
Image.Canvas.LineTo(x+round(0.5*b),y+dx);
end;
until ((y+round(0.5*b)+dx)<180);
Its condition is based on y, b and dx values but they never change in your loop.
To confirm where it hangs, use the Pause command in Delphi, then press F7/F8 to run it step by step.
I'm in the middle of a project with a number of child forms. Many of the forms may be open at once. I'd like to know if there's already something I can use to manage and keep track of these forms, much like the windows taskbar and/or task manager. If not, then what would be the best approach? I don't want to have to reinvent the wheel if this is already done.
Description
As mentioned above, this project has many forms which may be opened at once. I will also be implementing some visual list control (much like the taskbar or task manager) for user control of these forms (or in the user's case, the forms are called windows). The most ideal way to manage these would be to first capture each of these forms as they're created and keep record of them somewhere. Some forms need this behavior, and some forms do not. For example, modal forms will never need this handling.
I will be giving the user access to show, minimize, or close these forms, as well as some other future un-thought handling, like maybe a custom popup menu associated with one of these forms (but that's another subject). The point is, I need to build something to capture these forms and keep them in order.
This will also include some other user interaction with all the forms at once, as well as simple access to each one of them, similar to how Screen.Forms already works. For example, a command to minimize all forms (FormManager.MinimizeAll), to maximize the currently active form (FormManager.ActiveForm.Maximize), or with a particular form (FormManager[3].Maximize).
Possible Options
I understand there are a few far different approaches to accomplish similar results, and haven't started coding it yet because each of those approaches has a different starting point. The options are...
Wrap Screen.Forms and other associated functionality from the Screen (which wouldn't allow too much of my desired flexibility)
Every time I create a form, register it with this form manager (which is very flexible, but I have to make sure I always register each created form)
Build a master form to register its self with the form manager and inherit everything from it (which is also very flexible, but in different ways, and much more complex)
The second option is sounding the most promising so far. But again, I don't want to start building it if there is already a solution for this. I'm pretty confident that I'm not the first person to do this. I don't know how to search for such a thing, I get nothing related to what I want on Google.
The global variable Screen (in Forms unit) does some "tracking", ie
Screen.Forms list all currently open forms;
Screen.ActiveForm form which has input focus (see also FocusedForm);
Screen.OnActiveFormChange event;
You could add each form to a TObjectList. I wrote a component called FormStack, which allows you to add forms (even forms with the same name), retrieve, remove, etc. To get a Task Manager like behavior, I think you'd just need to iterate the list to obtain form names . Hopefully you can use something here to shed some light on your idea..
Here's the code for FormStack.
unit uFormstack;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Contnrs;
type
TFormstack = class(TComponent)
private
{ Private declarations }
FormList: TObjectList;
protected
{ Protected declarations }
public
{ Public declarations }
Constructor Create(AOwner: TComponent); Override;
Destructor Destroy; Override;
Procedure Add(InstanceClass: TComponentClass; Var Reference);
Procedure RemoveLast;
Procedure RemoveAll;
Function FindForm(AComponentClass: TComponentClass): Boolean;
Function GetForm(AComponentClass: TComponentClass): TObject;
Function GetByIndex(AIndex: Integer): TObject;
Procedure RemoveByIndex(AIndex: Integer);
published
{ Published declarations }
end;
procedure Register;
implementation
//{$R *.res}
procedure Register;
begin
RegisterComponents('FormStack', [TFormstack]);
end;
{-----------------------------------------------------------------------------
TFormStack
-----------------------------------------------------------------------------}
Constructor TFormStack.Create(AOwner: TComponent);
Begin
Inherited Create(AOwner);
FormList := TObjectList.Create;
FormList.OwnsObjects := True;
End;
Destructor TFormStack.Destroy;
Begin
FormList.Free;
Inherited Destroy;
End;
Procedure TFormStack.Add(InstanceClass: TComponentClass; Var Reference);
Var
Instance: TComponent;
Begin
Instance := TComponent(InstanceClass.NewInstance);
TComponent(Reference) := Instance;
Instance.Create(Self); // Owner is FormList <<-- blows up if datamodule in D2010
FormList.Add(Instance);
Instance.Tag := FormList.Count-1;
End;
Procedure TFormStack.RemoveAll;
Var
I: Integer;
Begin
For I := FormList.Count -1 downto 0 do // last in first out
begin
Self.RemoveLast;
End;
End;
// This removes the last form on the stack
Procedure TFormStack.RemoveLast;
Begin
if FormList.Count > 0 then
FormList.Remove(FormList.Items[FormList.Count-1]);
End;
Function TFormStack.FindForm(AComponentClass: TComponentClass): Boolean;
Var
I: Integer;
Begin
Result := False;
For I := FormList.Count-1 downto 0 do
If Formlist.Items[I].ClassType = AComponentClass then
Result := True;
End;
Function TFormStack.GetForm(AComponentClass: TComponentClass): TObject;
Var
I: Integer;
begin
Result := Nil;
For I := FormList.Count-1 downto 0 do
If Formlist.Items[I].ClassType = AComponentClass then
Result := FormList.Items[I];
end;
Function TFormStack.GetByIndex(AIndex: Integer): TObject;
begin
Result := Nil;
If FormList.Count-1 >= AIndex then
Result := FormList.Items[AIndex];
end;
Procedure TFormStack.RemoveByIndex(AIndex: Integer);
begin
If FormList.Count-1 >= AIndex then
FormList.Remove(FormList.Items[AIndex]);
end;
end.
If I understand you correctly, you want to track this in code while the app is running?
Maybe you can do something with Screen.Forms?
I have the following sequence of commands in Delphi2010:
var netdir:string;
....
OpenDialog1.InitialDir:=netdir;
....
OpenDialog1.Execute...
....
GetDir(0,netdir);
....
After executing OpenDialog I should have in string netdir the directory where I finished
my OpenDialog.Execute. And in the next OpenDialog.Execute it should start from that
directory.
It works fine on XP, but not on Windows 7?
It always starts from directory where the program is installed.
Any idea what might be wrong?
Thanks.
Your question cannot be answered as it stands, because it lacks several crucial details.
Is netdir a global constant, or does it go out of scope every now and then?
Do you set netdir to something prior to OpenDialog1.Execute?
Is the question about what directory GetDir return (as your title suggests), or about how to make the open dialog remember the last visited directory (as the body matter suggests)?
I will assume that 1) netdir is a global constant, that 2) you do not set it initially, and that 3) you want the open dialog to remember the last visited folder. Thus you have something like
unit Unit3;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm3 = class(TForm)
OpenDialog1: TOpenDialog;
procedure FormClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form3: TForm3;
var
netdir: string;
implementation
{$R *.dfm}
procedure TForm3.FormClick(Sender: TObject);
begin
OpenDialog1.InitialDir := netdir;
OpenDialog1.Execute;
GetDir(0, netdir);
end;
end.
Then the solution is to let Windows remember the directory for you, that is, simply do
procedure TForm3.FormClick(Sender: TObject);
begin
OpenDialog1.Execute;
end;
alone! But why doesn't your method work? Well, GetDir doesn't return what you want. If you need explicit control, do
procedure TForm3.FormClick(Sender: TObject);
begin
OpenDialog1.InitialDir := netdir;
OpenDialog1.Execute;
netdir := ExtractFilePath(OpenDialog1.FileName)
end;
If you not wan´t opendialog you can do as below to get dir under your program.
yourdir:=ExtractFilePath(Application.ExeName);
I have done it in Vista and it work.
This is the solution for the problem
openDialog1.Options := [ofFileMustExist];
if openDialog1.Execute then
begin
end;