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.
Related
so i tried to make simple app to check if entered number is odd or even. Also i wanted to handle EConvertError by entering Try and Except.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
var x:integer;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
try
x:=StrToInt(InputBox('Zadávanie','Napíš číslo',''));
if x mod 2 = 0 then ShowMessage('Zadané číslo je párne')
else ShowMessage('Zadané číslo je nepárne');
except
on E: EConvertError do
ShowMessage('Zadávaj len čísla!');
end;
end;
end.
But this don't work and still showing the same exact Project1.exe raised exception class EConvertError with message ''' is not a vaild integer value instead of 'Zadávaj len čísla!'. Why?
Your try..except code is fine.
You are simply experiencing what happens when you run your app inside of the IDE's debugger. The debugger sees the exception before your app does. You are seeing a popup message from the debugger. Simply dismiss the popup and either press the "Run" button in the IDE, or press F9 on the keyboard, to continue execution and the exception will be passed to your app for normal handling, calling your except block. The popup will not happen when you run your app outside of the debugger, the except will just be called immediately.
If you don't want the debugger to popup a message on the exception, you can add EConvertError to the debugger's list of exceptions that it ignores. Or you can place breakpoints around the code that instruct the debugger to ignore exceptions for just this block of code.
Or, you can simply use TryStrToInt() instead of StrToInt(). TryStrToInt() does not raise an exception on a conversion error.
Just use TryStrToInt() which returns false if the input was no valid integer, something like that:
procedure TForm1.FormCreate(Sender: TObject);
var x: integer;
begin
try
if not TryStrToInt(InputBox('Zadávanie','Napíš číslo',''),x) then begin
ShowMessage('Zadávaj len čísla!');
exit;
end;
if x mod 2 = 0 then ShowMessage('Zadané číslo je párne')
else ShowMessage('Zadané číslo je nepárne');
end;
So no exception will be raised on input error.
IMHO exceptions should be exceptional - I don't like using EConvertError at all in my code.
BTW it is not a very good idea to put some UI code in the OnCreate event - better use OnShow for that.
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.
The following code works just fine, but it shouldn't ! When I click the Button1, the object is destroyed first, and then its Value is used and I don't receive any Access Violation or something... Even more, the multiply operation gives the correct result, that proves that Obj1 is not destroyed ! But then again, this is not true either, because when I close the program it does'n report any memory leakage. I'm very confused.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
MyObj = class(TObject)
Value: Cardinal;
end;
TForm1 = class(TForm)
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
public
Obj1:MyObj;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
Obj1.Free;
Obj1.Value:=Obj1.Value * 5;
Caption:=IntToStr(Obj1.Value);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ReportMemoryLeaksOnShutdown:=true;
Obj1:=MyObj.Create;
Obj1.Value:=10;
end;
end.
The object is destroyed. The memory is returned to the memory manager. What happens next is out of your control. The memory could be returned to the system. In which case you'd see a runtime error. Or, the memory could be kept alive by the memory manager ready to reuse the next time the program asks for a block of that size. This is what happens here.
Your program exhibits undefined behaviour. Anything could happen, including the program appearing to work. Obviously the program is wrong and you must not access objects after they have been destroyed.
If you use the full debug version of FastMM then you should see an error because in that scenario steps are taken to detect access after free. That's a useful debugging tool.
One of my coworkers show me a code written in Delphi-XE XE Version 15.0.3953.35171, which I believe it should raise an access violation. The code is bellow:
unit Unit3;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm3 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
function test:TstringList;
{ Public declarations }
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
procedure TForm3.FormCreate(Sender: TObject);
var aStrList : TStringList;
begin
aStrList := TStringList.Create;
test;
FreeAndNil(aStrList);
end;
function TForm3.test: TstringList;
var i:Integer;
begin
for i:=0 to 1000 do
Result.Add('aaa');//AV?
end;
end.
Inspecting aStrList and Result has the following results:
aStrList: TStringList $12FEDC : $42138A
Result: TStringList $12FEC4 : $B01B90
I do not understand why it is working. Result.Add should raise an access violation
LE: It seems that is working only on Debug Build Configuration.
The Result variable in that function has not been initialized and could hold any value. Now, the implementation detail means that, in some combinations of compiler options, your code happens to run with Result referring to a valid object. But that's really just a coincidence of those implementation details.
If this were C++ then that function would exhibit undefined behaviour. Although that term does not have a formal meaning in Delphi, it can be helpful to use that term in a Delphi setting to mean the same thing as in the context of C++.
I would also make the point that even if Result did not refer to a valid string list object, your code would not be guaranteed to raise an access violation. It could be that Result points to a block of memory that just happens to look enough like a string list for that code to execute successfully.
If you do things properly, you can predict the behaviour of your program. If your code is flawed and induces undefined behaviour, then your program's behaviour becomes unpredictable. It may work. It may fail. Or that code may execute fine, but then lead to a failure later in the program's execution. And so on.
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.