I'm making my first program in delphi and it's a space invaders rip off. So I have 2 questions:
First off, how would I copy code to every single object? This is what I have now:
procedure TForm2.Timer1Timer(Sender: TObject);
begin
//Label2.Caption := IntToStr(Form2.ClientWidth);
//Label1.Caption := IntToStr(Shape2.Left + Shape2.Width);
if smer = 1 then begin
Shape2.Left:=Shape2.left+56;
Shape3.Left:=Shape3.left+56;
Shape4.Left:=Shape4.left+56;
Shape5.Left:=Shape5.left+56;
Shape6.Left:=Shape6.left+56;
if Shape6.Left+Shape6.Width>Form2.ClientWidth then begin
Shape2.Top:=Shape2.Top+56;
Shape3.Top:=Shape3.Top+56;
Shape4.Top:=Shape4.Top+56;
Shape5.Top:=Shape5.Top+56;
Shape6.Top:=Shape6.Top+56;
smer:=0;
end;
end;
if smer = 0 then begin
Shape2.Left:=Shape2.left-56;
Shape3.Left:=Shape3.left-56;
Shape4.Left:=Shape4.left-56;
Shape5.Left:=Shape5.left-56;
Shape6.Left:=Shape6.left-56;
if Shape2.Left<=0 then begin
Shape2.Top:=Shape2.Top+56;
Shape3.Top:=Shape3.Top+56;
Shape4.Top:=Shape4.Top+56;
Shape5.Top:=Shape5.Top+56;
Shape6.Top:=Shape6.Top+56;
smer:=1;
end;
end;
end;
procedure TForm2.Timer2Timer(Sender: TObject);
begin
if MetakP.Visible=true then begin
MetakP.Top:=MetakP.Top-11;
end;
if MetakN.Visible=true then begin
MetakN.Top:=MetakN.Top+11;
end;
if MetakN.Top>Form2.Height then MetakN.Visible:=false;
if MetakP.Top<=0 then begin
MetakP.Left:=Image1.Left+16;
MetakP.Top:=Image1.Top;
MetakP.visible:=false;
Let:=0;
end;
if (MetakN.left>=Image1.Left) or (MetakN.Left+MetakN.Width >= Image1.left) then begin // da li je metak desno od kocke
if MetakN.left<=Image1.Left+Image1.Width then begin // da li je metak levo od kocke
If MetakN.Top<=Image1.Top+Image1.Height then begin // da li je metak ispod kocke
if MetakN.Top>=Image1.Top-Image1.Height then begin
if MetakN.Visible=true then begin
Image1.Visible:=false;//
MetakN.Left:=Image1.Left+16;
MetakN.Top:=Image1.Top;
MetakN.visible:=false;
Let:=0;
gub:=gub+1;
//Image1.Enabled:=false;
end;
end;
end;
end;
end;
if (MetakP.left>=Shape2.Left) or (MetakP.Left+MetakP.Width >= Shape2.left) then begin // da li je metak desno od kocke
if MetakP.left<=Shape2.Left+Shape2.Width then begin // da li je metak levo od kocke
If MetakP.Top<=Shape2.Top+Shape2.Height then begin // da li je metak ispod kocke
if MetakP.Top>=Shape2.Top-Shape2.Height then begin
if Shape2.Visible=true then begin
Shape2.Visible:=false;//
MetakP.Left:=Image1.Left+16;
MetakP.Top:=Image1.Top;
MetakP.visible:=false;
Let:=0;
pob:=pob+1;
//Shape2.Enabled:=false;
end;
end;
end;
end;
end;
end;
This is continued for all shapes. It's basically a hitbox check. Now, that's a lot of code, is there a way I could make it work for all the shapes separately?
Second off, how would I fire off a bullet out of a random shape? I have:
procedure TForm2.Timer4Timer(Sender: TObject);
var r:integer;
var rr:string;
begin
MetakN.Visible:=true;
if Shape2.Visible=false then MetakN.Visible:=false;
r:=2+random(5);
rr:=IntToStr(r);
MetakN.Top:= Shape2.top+Shape2.Height;
MetakN.Left:= Shape2.Left+Shape2.Width div 2;
end;
The r was supposed to be used as "Shape[r].top" and so on, but it doesn't work.
Programs are made up of 2 parts.
Code
Data structures
You are only using 1.
You need to get a data structure to hold your Aliens.
Because it's just a bunch of aliens a list will work fine.
Add a variable to your form to put your aliens in.
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, System.Generics.Collections;
TForm1 = class(TForm)
....
private
Aliens: TList<TShape>;
You can initialize your shapes on form creation.
Something like this.
procedure TForm1.FormCreate(Sender: TObject);
var
i: integer;
AngryAlien: TShape;
begin
Aliens:= TList<TShape>.Create;
for i := 0 to 100 do begin
AngryAlien:= TShape.Create(Form1);
AngryAlien.Parent:= Form1;
AngryAlien.Shape:= stCircle;
AngryAlien.Brush.Color:= clWhite;
AngryAlien.Width:= 30;
AngryAlien.Height:= 30;
AngryAlien.Visible:= false;
Aliens.Add(AngryAlien);
end;
end;
Now you have a 100 101 aliens.
You can move the aliens around on a timer.
procedure TForm1.Timer1Timer(Sender: TObject);
var
i: integer;
Alien: TShape;
begin
//Move 4 aliens.
for i := 0 to 100 do begin
Alien:= Aliens[i];
Alien.Visible:= true;
Alien.Left:= Alien.Left + Random(30) - Random(20);
Alien.Top:= Alien.Top + Random(15) - Random(10);
end;
end;
Now you just use a loop to control every alien in turn.
If you want some game sample code, here something to get you started: http://delphi.about.com/od/gameprogramming/
More specifically: http://delphi.about.com/library/code/fdac_dodge_src.zip
Of course the above code is a bad example of copy-paste anti pattern and I would rewrite it like so:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls;
type
TForm1 = class(TForm)
shp_player: TShape;
shp_enemy: TShape;
btnStart: TButton;
timercircle: TTimer;
shparea: TShape;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
Edit1: TEdit;
Label5: TLabel;
Shape1: TShape;
Lbl_player: TLabel;
lbl_circle: TLabel;
lbl_enemy: TLabel;
procedure FormCreate(Sender: TObject);
procedure btnStartClick(Sender: TObject);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
procedure timercircleTimer(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
//my own category of variables
TLevelArray = Array [1 .. 30] of Boolean;
var
circle: array [1 .. 30] of TShape;
Speedx: array [1 .. 30] of Integer;
Speedy: array [1 .. 30] of Integer;
Level: array [1..30] of TLevelArray;
SpeedxCalculation: Integer;
SpeedyCalculation: Integer;
LevelStore: Integer = 1;
HighScore: Boolean = False;
procedure ShowCircles(Level: TLevelArray);
var
Count: Integer;
begin
for Count:= 1 to 30 do begin
circle[Count].Visible:= Level[Count];
end;
end;
procedure InitLevels;
var
i,j: integer;
begin
for i := 1 to 30 do begin
FillChar(Level[i], SizeOf(Level[i]),#0);
end;
for i := 1 to 30 do begin
for j := 1 to i do begin
Level[i][j]:= true;
end;
end;
end;
procedure Updatecircles; //if the circle needs to be visible for that level
var
Count: Integer;
begin
for Count:= 1 to 30 do begin
ShowCircles(Level[LevelStore]);
end;
end;
Procedure SpeedCalculation;
begin
circle[LevelStore].Left:= 8; //all the circles come from the same position
circle[LevelStore].Top:= 8;
repeat
Randomize; //their speeds are random for more interesting gameplay
SpeedxCalculation:= Random(10) + 1;
Speedx[LevelStore]:= 5 - SpeedxCalculation;
Randomize;
SpeedyCalculation:= Random(10) + 1;
Speedy[LevelStore]:= 5 - SpeedyCalculation;
until (speedy[LevelStore]) and (Speedx[LevelStore]) <> 0;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
Count: Integer;
i: integer;
MyCircle: TShape;
begin
InitLevels;
for i := 1 to 30 do begin
MyCircle:= TShape.Create(Self);
MyCircle.Parent:= Self;
MyCircle.Width:= 10;
MyCircle.Height:= 10;
MyCircle.Brush.Color:= clmaroon;
MyCircle.Visible:= false;
MyCircle[i]:= MyCircle;
end;
Randomize;
shp_enemy.Left:= Random(clientwidth) - shp_enemy.width;
shp_enemy.Top:= Random(clientheight) - shp_enemy.height;
lbl_enemy.Left:= shp_enemy.Left;
lbl_enemy.Top:= shp_enemy.Top - 20;
SpeedCalculation;
updatecircles;
end;
procedure TForm1.btnStartClick(Sender: TObject);
begin
TimerCircle.enabled:= True;
btnStart.Visible:= False;
Label2.Caption:= '0';
Edit1.enabled:= False;
lbl_player.Visible:= False;
lbl_enemy.Visible:= False;
lbl_circle.Visible:= False;
end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
shp_player.Left:= x - shp_player.Width - 10;
shp_player.Top:= y - shp_player.Height - 10; //the green block follows the mouse
lbl_player.Left:= x - lbl_player.Width - 10;
lbl_player.Top:= y - lbl_player.Height - 30;
end;
procedure TForm1.timercircleTimer(Sender: TObject);
var
overlay: Trect;
Count: Integer;
begin
for Count:= 1 to LevelStore do begin
// Moves the circles
circle[Count].Left:= circle[Count].Left + speedx[Count];
circle[Count].Top:= circle[Count].Top + speedy[Count];
//bounces the circles off of the boundaries of the form
if circle[Count].Left > clientwidth - circle[Count].width then speedx[Count]:= -speedx[Count]
else if circle[Count].Left < 0 then speedx[Count]:= -speedx[Count];
if circle[Count].Top > clientheight - circle[Count].Height then speedy[Count]:= -speedy[Count]
else if circle[Count].Top < 0 then speedy[Count]:= -speedy[Count];
//detects a collision between a circle and the players block
if Intersectrect(overlay, circle[Count].BoundsRect, shp_player.BoundsRect) then begin
c1.Left:= 8;
c1.Top:= 8;
btnstart.caption:= 'Restart';
btnstart.Visible:= True;
LevelStore:= 1;
SpeedCalculation;
UpdateCircles;
timercircle.enabled:= false;
if HighScore = True then //if a new high score has been achieved
begin
Edit1.Enabled:= True;
HighScore:= False;
end;
lbl_player.Visible:= True;
lbl_enemy.Visible:= True;
lbl_circle.Visible:= True;
lbl_enemy.Left:= shp_enemy.Left;
lbl_enemy.Top:= shp_enemy.Top - 20;
end;
//detects a collision between the player block and target block
if Intersectrect(overlay, shp_enemy.BoundsRect, shp_player.BoundsRect) then begin
Label2.Caption:= inttostr(strtoint(Label2.Caption) + 1);
if strtoint(Label2.Caption) > strtoint(Label4.Caption) then begin
highscore:= True;
Label4.Caption:= Label2.Caption;
end;
Randomize;
repeat
//the target block goes to a new position on the form
shp_enemy.Left:= Random(clientwidth) + 2 * (shp_enemy.width);
shp_enemy.Top:= Random(clientheight) - 2 * (shp_enemy.height);
until ((shp_enemy.Left) > (Form1.Left + shp_enemy.Width)) and
((shp_enemy.Left) < (Form1.Left + clientwidth - 2 * (shp_enemy.Width))) and
((shp_enemy.Top) > (Form1.Top + shp_enemy.Height)) and
((shp_enemy.Top) < (Form1.Top + clientwidth - 2 * (shp_player.Width)));
LevelStore:= LevelStore + 1;
if LevelStore = 30 then // there are only 30 circles
begin
MessageDlg('Congratulations! - You have completed the game!', mtinformation, [mbOK], 0);
timercircle.enabled:= false;
btnstart.Visible:= True;
LevelStore:= 1;
SpeedCalculation;
UpdateCircles;
end else begin
SpeedCalculation;
UpdateCircles;
end;
end;
end;
end;
end.//FIN - Code by Si (c)
That way you don't repeat yourself.
Related
I have a memory problem with TBgraBitmap in combination with the barcode detection ZXING in Lazarus. Does anyone see my problem?
After resize the image for a better detection, the memory grows and grows.
It works, but it crashes because of running as a 32-bit assembly. I work with ca. 10 source tifs in color scanned mode and 300 dpi.
unit frmmain;
{$IFDEF FPC}
//{$mode objfpc}{$H+}
{$mode delphi}{$H+}
{$ENDIF}
interface
uses
{$ifdef FPC}
LResources,
{$endif}
{$ifdef MSWindows}Windows, {$endif}
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ComCtrls,
StdCtrls, ExtCtrls, Clipbrd, Buttons, fpimage,
Generics.Collections,
ZXing.ReadResult,
ZXing.BarCodeFormat,
ZXing.DecodeHintType,
ZXing.ResultPoint,
ZXing.Scanmanager,
UConvert, dateutils,
{zum Vergrößern }BgraBitmap, BGRABitmapTypes,
{für enum namen}typinfo;
type
{ TMainForm }
TMainForm = class(TForm)
Button1: TButton;
Button2: TButton;
Image1: TImage;
Label1: TLabel;
Label2: TLabel;
ListBoxFiles: TListBox;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure ListBoxFilesClick(Sender: TObject);
private
function GetAppPath(): string;
function GetFiles(LPfad: string): TStringList;
function Resample(Src: string; percent: integer): TBitmap;
public
end;
var
MainForm: TMainForm;
maxZoom: integer;
implementation
{$R *.lfm}
{ TMainForm }
procedure TMainForm.Button1Click(Sender: TObject);
var
scanner: TScanmanager;
readResult: TReadResult;
barcodeInt, zoom: integer;
// pic: TPicture;
bmp: TBitmap;
begin
Button1.Enabled := False;
ListBoxFiles.Enabled := False;
application.ProcessMessages;
zoom := 100;
while zoom <= 180 do
begin
//erkennen
bmp := Resample(Label2.Caption, zoom);
scanner := TScanmanager.Create(TBarcodeFormat.QR_CODE, nil); //TBarcodeFormat.auto
readResult := scanner.Scan(bmp);
//free mem
FreeAndNil(bmp);
//free mem
FreeAndNil(scanner);
application.ProcessMessages;
if readResult <> nil then
begin
barcodeInt := Ord(readResult.BarcodeFormat);
{barcodeTypeStr := TypInfo.GetEnumName(
System.TypeInfo(ZXing.BarCodeFormat.TBarcodeFormat), barcodeInt);
}
Text := 'Zoom: ' + IntToStr(zoom) + ' : ' + readResult.Text +
' Code: ' + IntToStr(barcodeInt);
if zoom > maxZoom then
maxZoom := zoom;
break;
end
else
Text := 'Zoom: ' + IntToStr(zoom) + '-';
zoom := zoom + 10;
application.ProcessMessages;
//free mem
FreeAndNil(readResult);
end;
//show result
Label1.Caption := 'Max. Zoom: ' + IntToStr(maxzoom);
Button1.Enabled := True;
ListBoxFiles.Enabled := True;
end;
function TMainForm.Resample(Src: string; percent: integer): TBitmap;
var
Width, Height: integer;
reSampleBitmap: TBgraBitmap;
pic: TPicture;
begin
//if percent = 100 then
//begin
// Result := TBitmap.Create;
// Result.Assign(Src);
// exit;
//end;
// reSampleBitmap := TBgraBitmap.Create();
// reSampleBitmap.LoadFromFile(Src);
pic := TPicture.Create;
pic.LoadFromFile(src);
reSampleBitmap := TBgraBitmap.Create(pic.Bitmap);
Width := round(reSampleBitmap.Height * percent / 100);
Height := round(reSampleBitmap.Height * percent / 100);
reSampleBitmap.ResampleFilter := rfBestQuality;
reSampleBitmap := reSampleBitmap.Resample(Width, Height);// as TBGRABitmap;
Result := TBitmap.Create;
Result.Assign(reSampleBitmap);
FreeAndNil(reSampleBitmap);
FreeAndNil(pic);
end;
procedure TMainForm.Button2Click(Sender: TObject);
var
i: integer;
begin
maxZoom := 0;
for i := 0 to ListBoxFiles.Count - 1 do
begin
ListBoxFiles.ClearSelection;
ListBoxFiles.Selected[i] := True;
ListBoxFilesClick(self);
end;
end;
procedure TMainForm.ListBoxFilesClick(Sender: TObject);
var
fullFilename: string;
obj: TObject;
bmp: TPicture;
begin
obj := ListBoxFiles.Items.Objects[ListBoxFiles.ItemIndex];
if obj <> nil then
begin
fullfilename := string(obj);
Label2.Caption := fullfilename;
bmp := TPicture.Create;
bmp.LoadFromFile(fullfilename);
Image1.Picture.Assign(bmp);
FreeAndNil(bmp);
obj := nil;
Button1Click(self);
end;
end;
procedure TMainForm.FormCreate(Sender: TObject);
var
sourceFolder, fileName: string;
files: TStringList;
i: integer;
begin
ListBoxFiles.Clear;
Label2.Caption := '';
maxZoom := 100;
sourceFolder := GetAppPath() + 'Tifs\';
//D:\EigeneDateien\Lazarus\ZXing\Tifs
files := Getfiles(sourcefolder);
for i := 0 to files.Count - 1 do
begin
fileName := ExtractFileName(files[i]);
ListBoxFiles.AddItem(fileName, TObject(files[i]));
end;
end;
function TMainForm.GetFiles(LPfad: string): TStringList;
var
LSearchRec: TSearchRec;
begin
Result := TStringList.Create;
if FindFirst(LPfad + '*.*', faAnyFile, LSearchRec) = 0 then
begin
repeat
if LSearchRec.Attr and faDirectory = 0 then
begin
Result.Add(LPfad + LSearchRec.Name);
end;
until FindNext(LSearchRec) <> 0;
FindClose(LSearchRec);
end;
end;
function TMainForm.GetAppPath(): string;
var
appDir: string;
begin
appDir := ExpandFileName(ExtractFileDir(Application.ExeName));
appDir := IncludeTrailingPathDelimiter(appDir);
Result := appDir;
end;
end.
I have questions about how to create dynamic controls, how destroy and how get value inside newly created control.
Create and count edits create in form worked correctly, but where I create edits in panels with buttons to destroy chosen panel (Panel [Edit, button]), it's create correctly, but count doesnt work.
And I don't know how to destroy chosen by me panel with edit without error (I didn't make it yet in code below).
I have this code:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TfrmMain = class(TForm)
btnCreateNewObject: TButton;
btnCountValues: TButton;
btnCreateNewPanels: TButton;
btnAllEditsInPanels: TButton;
procedure btnCreateNewObjectClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure btnCountValuesClick(Sender: TObject);
procedure btnCreateNewPanelsClick(Sender: TObject);
procedure btnAllEditsInPanelsClick(Sender: TObject);
private
dynEdit: TEdit;
dynPanel: TPanel;
yposition: integer;
ypositionpanel: integer;
{ Private declarations }
public
{ Public declarations }
end;
var
frmMain: TfrmMain;
implementation
{$R *.dfm}
procedure TfrmMain.btnCountValuesClick(Sender: TObject);
var
i: integer;
res: integer;
begin
res := 0;
for i := 0 to Self.ControlCount - 1 do
begin
if Controls[i] is TEdit then
begin
res := res + StrToInt((Controls[i] as TEdit).Text);
end;
end;
ShowMessage(IntToStr(res));
end;
procedure TfrmMain.btnCreateNewObjectClick(Sender: TObject);
begin
dynEdit := TEdit.Create(Self);
with dynEdit do
begin
Parent := frmMain;
Width := 80;
Height := 25;
Top := yposition;
Left := 3;
end;
yposition := yposition + 26
end;
procedure TfrmMain.btnCreateNewPanelsClick(Sender: TObject);
begin
dynPanel := TPanel.Create(Self);
with dynPanel do
begin
Parent := frmMain;
Width := 100;
Height := 40;
Top := ypositionpanel;
Left := 120;
dynEdit := TEdit.Create(Self);
with dynEdit do
begin
Parent := dynPanel;
Width := 80;
Height := 25;
Top := 3;
Left := 3;
end;
end;
ypositionpanel := ypositionpanel + 41;
end;
procedure TfrmMain.btnAllEditsInPanelsClick(Sender: TObject);
var
i, j: integer;
res: integer;
begin
res := 0;
for i := 0 to Self.ControlCount - 1 do
begin
for j := 0 to dynPanel.ControlCount - 1 do
begin
if dynPanel.Controls[j] is TEdit then
begin
res := res + StrToInt( (Controls[j] as TEdit).Text );
end;
end;
end;
ShowMessage(IntToStr(res));
end;
procedure TfrmMain.FormCreate(Sender: TObject);
begin
yposition := 1;
ypositionpanel := 1;
end;
end.
object frmMain: TfrmMain
Left = 0
Top = 0
Caption = 'frmMain'
ClientHeight = 500
ClientWidth = 888
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object btnCreateNewObject: TButton
Left = 775
Top = 475
Width = 113
Height = 25
Caption = 'Create new edit'
TabOrder = 0
OnClick = btnCreateNewObjectClick
end
object btnCountValues: TButton
Left = 775
Top = 444
Width = 113
Height = 25
Caption = 'Count all edits'
TabOrder = 1
OnClick = btnCountValuesClick
end
object btnCreateNewPanels: TButton
Left = 648
Top = 475
Width = 121
Height = 25
Caption = 'Create new panels'
TabOrder = 2
OnClick = btnCreateNewPanelsClick
end
object btnAllEditsInPanels: TButton
Left = 648
Top = 444
Width = 121
Height = 25
Caption = 'Count all edits in panels'
TabOrder = 3
OnClick = btnAllEditsInPanelsClick
end
end
You are iterating only through the Edit controls that are direct children of the Form itself, or of the last Panel created. You are not iterating through all of the Panels.
Use a TList or other suitable container to keep track of the Edits you create dynamically, then you can loop through that list/container when needed. And when you are ready to remove a Panel from the Form, simply Remove() its child TEdit from the list and then Free() the Panel, which will free the TEdit for you.
For example:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
System.Generics.Collections;
type
TfrmMain = class(TForm)
btnCreateNewObject: TButton;
btnCountValues: TButton;
btnCreateNewPanels: TButton;
btnAllEditsInPanels: TButton;
procedure btnCreateNewObjectClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure btnCountValuesClick(Sender: TObject);
procedure btnCreateNewPanelsClick(Sender: TObject);
procedure btnAllEditsInPanelsClick(Sender: TObject);
procedure DestroyPanel(Sender: TObject);
private
{ Private declarations }
AllEdits: TList<TEdit>;
yposition: integer;
ypositionpanel: integer;
public
{ Public declarations }
end;
var
frmMain: TfrmMain;
implementation
{$R *.dfm}
procedure TfrmMain.btnCountValuesClick(Sender: TObject);
var
i, res: integer;
dynEdit: TEdit;
begin
res := 0;
for i := 0 to AllEdits.Count - 1 do
begin
dynEdit := AllEdits[i];
if dynEdit.Parent = Self then
res := res + StrToInt(dynEdit.Text);
end;
ShowMessage(IntToStr(res));
end;
procedure TfrmMain.btnCreateNewObjectClick(Sender: TObject);
var
dynEdit: TEdit;
begin
dynEdit := TEdit.Create(Self);
try
with dynEdit do
begin
Parent := Self;
Width := 80;
Height := 25;
Top := yposition;
Left := 3;
end;
AllEdits.Add(dynEdit);
except
dynEdit.Free;
raise;
end;
yposition := yposition + 26
end;
procedure TfrmMain.btnCreateNewPanelsClick(Sender: TObject);
var
dynPanel: TPanel;
dynEdit: TEdit;
dynButton: TButton;
begin
dynPanel := TPanel.Create(Self);
try
with dynPanel do
begin
Parent := Self;
Width := 200;
Height := 40;
Top := ypositionpanel;
Left := 120;
end;
dynEdit := TEdit.Create(dynPanel);
with dynEdit do
begin
Parent := dynPanel;
Width := 80;
Height := 25;
Top := 3;
Left := 3;
end;
dynButton := TButton.Create(dynPanel);
with dynButton do
begin
Parent := dynPanel;
Width := 100;
Height := 25;
Top := 3;
Left := 100;
Caption := 'Destroy this pnl';
onClick := DestroyPanel;
end;
AllEdits.Add(dynEdit);
except
dynPanel.Free;
raise;
end;
ypositionpanel := ypositionpanel + 41;
end;
procedure TfrmMain.DestroyPanel(Sender: TObject);
var
dynPanel: TPanel;
dynEdit: TEdit;
begin
dynPanel := TPanel(TButton(Sender).Owner);
dynEdit := TEdit(dynPanel.Controls[0]);
AllEdits.Remove(dynEdit);
dynPanel.Free;
end;
procedure TfrmMain.btnAllEditsInPanelsClick(Sender: TObject);
var
i, res: integer;
dynEdit: TEdit;
begin
res := 0;
for i := 0 to AllEdits.Count - 1 do
begin
dynEdit := AllEdits[i];
if dynEdit.Parent <> Self then
res := res + StrToInt(dynEdit.Text);
end;
ShowMessage(IntToStr(res));
end;
procedure TfrmMain.FormCreate(Sender: TObject);
begin
yposition := 1;
ypositionpanel := 1;
AllEdits := TList<TEdit>.Create;
end;
procedure TfrmMain.FormDestroy(Sender: TObject);
begin
AllEdits.Free;
end;
end.
I do not know how to call an interactive panel of tools like TeamViewer has. My question is very objective: How can I create a interactive panel where the panel will hide/show at any moment?
Example:
EDIT:
I found a possible solution (code below). Now I want to insert a "Button" glued on the right side and below Panel. How can I make this?
procedure TForm1.btn1Click(Sender: TObject);
begin
AnimateWindow(Panel1.Handle, 800, AW_SLIDE or AW_VER_NEGATIVE or AW_HIDE);
end;
procedure TForm1.btn2Click(Sender: TObject);
begin
AnimateWindow(Panel1.Handle, 800, AW_SLIDE or AW_VER_POSITIVE or AW_ACTIVATE);
end;
type
TForm1 = class(TForm)
pnl1: TPanel;
btn1: TButton;
procedure btn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.btn1Click(Sender: TObject);
begin
if btn1.Caption = 'H' then
begin
btn1.Top := 0;
btn1.Caption := 'S';
AnimateWindow(Pnl1.Handle, 400, AW_SLIDE or AW_VER_NEGATIVE or AW_HIDE);
end
else
begin
btn1.Top:= pnl1.Height;
btn1.Caption := 'H';
AnimateWindow(Pnl1.Handle, 400, AW_SLIDE or AW_VER_POSITIVE or AW_ACTIVATE);
end;
end;
end.
This was my solution:
I'm still using AnimateWindow api.
On Button properties, set right = 0
When Panel is visible, the Button have top := Panel.Height
By last, when Panel is no-visible (hidden), Button have top := 0
Try this:
unit NP;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls;
type
TMainFrm = class(TForm)
Timer1: TTimer;
Timer2: TTimer;
procedure FormCreate(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure Timer2Timer(Sender: TObject);
private
public
end;
var
MainFrm: TMainFrm;
Range: integer;
implementation
{$R *.dfm}
procedure TMainFrm.FormCreate(Sender: TObject);
begin
Width := 255;
Height := Screen.Height;
Left := 0 - Width;
Top := 0;
Range := 0;
Timer1.Enabled := True;
Timer2.Enabled := True;
MainFrm.Show;
end;
procedure TMainFrm.Timer1Timer(Sender: TObject);
var
pos: TPoint;
begin
GetCursorPos(pos);
if (pos.X < 10) and (MainFrm.Left < 0) then
begin
Range := 20;
MainFrm.Show;
end;
if (Range <> 0) then
MainFrm.Left := MainFrm.Left + Range;
if MainFrm.Left < 0 - MainFrm.Width then
begin
Range := 0;
MainFrm.Left := 0 - MainFrm.Width;
MainFrm.Hide;
end;
if (Range = 20) and (MainFrm.Left >= 0) then
begin
Range := 0;
MainFrm.Left := 0;
end;
end;
procedure TMainFrm.Timer2Timer(Sender: TObject);
var
pos: TPoint;
begin
GetCursorPos(pos);
if pos.X > MainFrm.Width then
Range := -20;
end;
end.
Axel
I use FastReport and I need to preview/print Grids with more than 1000 rows and I have some performances problems.
Typically I use TfrxCrossObject to prepare my grid because the end user may change the grid presentation (used columns, column's name, size) so I need to have a dynamical print.
I tested a simple grid (16 cols x2000 rows) and it needs more than 10 seconds to present the first preview page.
Any idea to improve performances ?
EDIT :
As said in some answers, the problem is : how to create 'dynamicaly' a grid (with same columns names and sizes that I have on screen) in FastReport without using TFrxCrossObject which seems to be not very efficent. I may admit all solutions like using DataSet or enhancing TfrxCrossObject.
The test code :
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
frxClass, StdCtrls, Grids, frxCross;
type
TForm1 = class(TForm)
Button1: TButton;
StringGrid1: TStringGrid;
frxCrossObject1: TfrxCrossObject;
frxReport1: TfrxReport;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure frxReport1BeforePrint(c: TfrxReportComponent);
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
var
i, j: Integer;
begin
for i := 1 to 16 do
for j := 1 to 2000 do
StringGrid1.Cells[i - 1, j - 1] := IntToStr(i * j);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
frxReport1.ShowReport;
end;
procedure TForm1.frxReport1BeforePrint(c: TfrxReportComponent);
var
Cross: TfrxCrossView;
i, j: Integer;
begin
if c is TfrxCrossView then
begin
Cross := TfrxCrossView(c);
for i := 1 to 16 do
for j := 1 to 2000 do
Cross.AddValue([i], [j], [StringGrid1.Cells[i - 1, j - 1]]);
end;
end;
end.
The CrossTab have many overhead. Here is a UserDataSet version :
Just drop 1 stringgrid, 1 button, 1 frxReport, 1 frxUserDataSet in the form.
Set the frxUserDataSet events, Form OnCreate and Buttom OnClick as below code.
No need to Design Report or set any properties, All will be set or generated at run-time.
It seems it is faster than the cross-tab version but you need more coding and lost functionality of CrossObject.
Edit : Add some comments and fix PaperWidth mis-calculation.
Edit2 : Add a print-friendly version which split data into pages.
View Friendly version show in 1 single page as the stringgrid :
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, frxClass, Grids, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
StringGrid1: TStringGrid;
frxReport1: TfrxReport;
frxUserDataSet1: TfrxUserDataSet;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure frxUserDataSet1Next(Sender: TObject);
procedure frxUserDataSet1GetValue(const VarName: string; var Value: Variant);
procedure frxUserDataSet1CheckEOF(Sender: TObject; var Eof: Boolean);
procedure frxUserDataSet1First(Sender: TObject);
private
X, Y, TCol, TRow : Integer;
IsEof : Boolean;
CW, CH, PF : Double;
Page : TfrxReportPage;
MDB : TfrxMasterData;
Memo : TfrxMemoView;
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
BW : Double;
begin
IsEof := False;
Page.PaperWidth := CW * TCol + 20; // EndlessWidth seems not work with band column
MDB.SetBounds(0,0, CW * PF * TCol, CH * PF);
MDB.Columns := TCol;
MDB.ColumnWidth := CW * PF;
frxReport1.ShowReport;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
i, j : Integer;
begin
CW := 12; // Cell Width in mm
CH := 5; // Cell Height in mm
PF := 3.7794; // Pixie Factor i.e. the conversion of mm to FR component measurement
TCol := 2000; // Total Column
TRow := 16; // Total Row
for i := 1 to TRow do
for j := 1 to TCol do
StringGrid1.Cells[i - 1, j - 1] := IntToStr(i * j);
frxUserDataSet1.Fields.Text := 'Data';
frxReport1.Clear;
frxReport1.DataSets.Add(frxUserDataSet1);
Page := TfrxReportPage.Create(frxReport1);
Page.CreateUniqueName;
Page.TopMargin := 10;
Page.BottomMargin := 10;
Page.LeftMargin := 10;
Page.RightMargin := 10;
Page.EndlessHeight := True;
Page.EndlessWidth := True;
MDB := TfrxMasterData.Create(Page);
MDB.DataSet := frxUserDataSet1;
Memo := TfrxMemoView.Create(MDB);
Memo.SetBounds(0,0,CW * PF,CH * PF);
Memo.Memo.Text := '[frxUserDataSet1."Data"]';
Memo.Frame.Typ := [ftLeft, ftRight, ftTop, ftBottom];
end;
procedure TForm1.frxUserDataSet1CheckEOF(Sender: TObject; var Eof: Boolean);
begin
Eof := IsEof;
end;
procedure TForm1.frxUserDataSet1First(Sender: TObject);
begin
X := 0;
Y := 0;
end;
procedure TForm1.frxUserDataSet1GetValue(const VarName: string; var Value: Variant);
begin
Value := StringGrid1.Cells[X,Y];
end;
procedure TForm1.frxUserDataSet1Next(Sender: TObject);
begin
If Y = TCol - 1 then
begin
if X = TRow - 1 then
IsEof := True;
Inc(X);
Y := 0;
end
else
Inc(Y);
end;
end.
Print-friendly version which is a bit more complex and separate data in different pages for printing :
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, frxClass, Grids, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
StringGrid1: TStringGrid;
frxReport1: TfrxReport;
frxUserDataSet1: TfrxUserDataSet;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure frxUserDataSet1Next(Sender: TObject);
procedure frxUserDataSet1GetValue(const VarName: string; var Value: Variant);
procedure frxUserDataSet1CheckEOF(Sender: TObject; var Eof: Boolean);
procedure frxUserDataSet1First(Sender: TObject);
private
X, Y, TCol, TRow, RPP, ColBreak : Integer;
IsEof : Boolean;
CW, CH, PF : Double;
Page : TfrxReportPage;
MDB : TfrxMasterData;
Memo : TfrxMemoView;
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses Math;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
BW : Double;
begin
IsEof := False;
RPP := Ceil((Page.PaperHeight - Page.TopMargin - Page.BottomMargin) / CH) - 1; // Row per page
ColBreak := RPP; // break to next column
frxReport1.ShowReport;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
i, j : Integer;
begin
CW := 12; // Cell Width in mm
CH := 5; // Cell Height in mm
PF := 3.7794; // Pixil Factor i.e. the conversion of mm to FR component measurement
TCol := 2000; // Total Column
TRow := 16; // Total Row
for i := 1 to TRow do
for j := 1 to TCol do
StringGrid1.Cells[i - 1, j - 1] := IntToStr(i * j);
frxUserDataSet1.Fields.Text := 'Data';
frxReport1.Clear;
frxReport1.DataSets.Add(frxUserDataSet1);
Page := TfrxReportPage.Create(frxReport1);
Page.CreateUniqueName;
Page.TopMargin := 10;
Page.BottomMargin := 10;
Page.LeftMargin := 10;
Page.RightMargin := 10;
Page.Columns := Ceil(Page.PaperWidth / CW);
MDB := TfrxMasterData.Create(Page);
MDB.DataSet := frxUserDataSet1;
MDB.SetBounds(0,0, CW * PF, CH * PF);
Memo := TfrxMemoView.Create(MDB);
Memo.Align := baClient;
Memo.Memo.Text := '[frxUserDataSet1."Data"]';
Memo.Frame.Typ := [ftLeft, ftRight, ftTop, ftBottom];
end;
procedure TForm1.frxUserDataSet1CheckEOF(Sender: TObject; var Eof: Boolean);
begin
Eof := IsEof;
end;
procedure TForm1.frxUserDataSet1First(Sender: TObject);
begin
X := 0;
Y := 0;
end;
procedure TForm1.frxUserDataSet1GetValue(const VarName: string; var Value: Variant);
begin
Value := StringGrid1.Cells[X,Y];
end;
procedure TForm1.frxUserDataSet1Next(Sender: TObject);
begin
If X = TRow - 1 then
begin
if Y = TCol - 1 then
IsEof := True
else
begin
frxReport1.Engine.NewColumn;
Inc(Y);
X := ColBreak - RPP;
end;
end
else if (X = ColBreak - 1) then
begin
if Y = TCol - 1 then
begin
frxReport1.Engine.NewPage;
ColBreak := ColBreak + RPP;
Y := 0;
end
else
Inc(Y);
frxReport1.Engine.NewColumn;
X := ColBreak - RPP;
end
else
Inc(X);
end;
end.
Your piece of code correspond to the PrintStringGrid demo of FastReport slightly modified (RowCount=2000 instead of 16).
Using TStringGrid as a container is not a good idea if you have to deal with large data: It should only be used for presention concern.
Use an in-memory dataset (e.g. ClientDataset as teran suggested in the question comment thread), you can still present your large data to a TStringGrid if using it is compulsory but a TDBGrid is more appropriate.
Iterating a large TDataset is fast than doing the same with a mere TStringGrid.
The PrintTable demo of FastReport could serve as a starting point, adapting it is left to you as an exercise knowing that it uses the same components as PrintStringGrid demo:
A TfrxReport and
A TfrxCrossObject where the iteration takes place.
I have been having a play around with the TListBox control, and drawing images and changing the font styles etc. I want to step it up a little, and try manipulating the items some more with indentation and multi level indenting.
Take a look at this image for a better idea:
The idea is that items in the list that are positioned between start and end items should be indented accordingly.
So, to give an idea I edited the screenshot in Paint, so it would look something like this:
What would be the way to approach this? My thought was to iterate through the listbox and return in 2 separate variable the amount of start and end items, then somehow determine where the other items are and if the fit between - but my logic is never so good :(
For ease of use, I have provided below the code to show how I am drawing the images and styles:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ImgList, ComCtrls;
type
TForm1 = class(TForm)
ImageList1: TImageList;
PageControl1: TPageControl;
TabSheet1: TTabSheet;
ListBox1: TListBox;
TabSheet2: TTabSheet;
ListBox2: TListBox;
TabSheet3: TTabSheet;
ListBox3: TListBox;
procedure FormCreate(Sender: TObject);
procedure ListBox1MeasureItem(Control: TWinControl; Index: Integer;
var Height: Integer);
procedure ListBox1DrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
procedure ListBox2MeasureItem(Control: TWinControl; Index: Integer;
var Height: Integer);
procedure ListBox2DrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
procedure ListBox3MeasureItem(Control: TWinControl; Index: Integer;
var Height: Integer);
procedure ListBox3DrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
// assign quick identifiers to image indexes
const
imgLayout = 0;
imgCalculator = 1;
imgComment = 2;
imgTime = 3;
imgStart = 4;
imgEnd = 5;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var
ListStyle: TListBoxStyle;
begin
// set the listbox style here
ListStyle := lbOwnerDrawVariable;
ListBox1.Style := ListStyle;
ListBox2.Style := ListStyle;
ListBox3.Style := ListStyle;
end;
{******************************************************************************}
procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
var
TextPosition: Integer;
Images: TImageList;
begin
TListBox(Control).Canvas.FillRect(Rect);
Images := ImageList1;
// draw the images
if TListBox(Control).Items.Strings[Index] = 'Layout' then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgLayout);
end else
if TListBox(Control).Items.Strings[Index] = 'Calculator' then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top,
imgCalculator);
end else
if TListBox(Control).Items.Strings[Index] = 'Comment' then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgComment);
end else
if TListBox(Control).Items.Strings[Index] = 'Time' then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgTime);
end;
// positions the text
TextPosition := (Rect.Bottom - Rect.Top - TListBox(Control).Canvas.TextHeight
(Text)) div 2;
// displays the text
TListBox(Control).Canvas.TextOut(Rect.Left + Images.Width + 8,
Rect.Top + TextPosition, TListBox(Control).Items.Strings[index]);
end;
procedure TForm1.ListBox1MeasureItem(Control: TWinControl; Index: Integer;
var Height: Integer);
begin
Height := ImageList1.Height;
end;
{******************************************************************************}
procedure TForm1.ListBox2DrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
var
TextPosition: Integer;
Images: TImageList;
begin
TListBox(Control).Canvas.FillRect(Rect);
Images := ImageList1;
// draw the images
if TListBox(Control).Items.Strings[Index] = 'Layout' then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgLayout);
TListBox(Control).Canvas.Font.Style := [fsBold];
end else
if TListBox(Control).Items.Strings[Index] = 'Calculator' then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top,
imgCalculator);
TListBox(Control).Canvas.Font.Color := clBlue;
TListBox(Control).Canvas.Font.Style := [fsItalic];
end else
if TListBox(Control).Items.Strings[Index] = 'Comment' then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgComment);
TListBox(Control).Canvas.Font.Color := clRed;
end else
if TListBox(Control).Items.Strings[Index] = 'Time' then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgTime);
end;
// positions the text
TextPosition := (Rect.Bottom - Rect.Top - TListBox(Control).Canvas.TextHeight
(Text)) div 2;
// displays the text
TListBox(Control).Canvas.TextOut(Rect.Left + Images.Width + 8,
Rect.Top + TextPosition, TListBox(Control).Items.Strings[index]);
end;
procedure TForm1.ListBox2MeasureItem(Control: TWinControl; Index: Integer;
var Height: Integer);
begin
Height := ImageList1.Height;
end;
{******************************************************************************}
procedure TForm1.ListBox3DrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
var
TextPosition: Integer;
Images: TImageList;
begin
TListBox(Control).Canvas.FillRect(Rect);
Images := ImageList1;
// draw the images
if TListBox(Control).Items.Strings[Index] = 'Layout' then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgLayout);
end else
if TListBox(Control).Items.Strings[Index] = 'Calculator' then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top,
imgCalculator);
end else
if TListBox(Control).Items.Strings[Index] = 'Comment' then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgComment);
end else
if TListBox(Control).Items.Strings[Index] = 'Time' then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgTime);
end else
if TListBox(Control).Items.Strings[Index] = 'Start' then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top,
imgStart);
TListBox(Control).Canvas.Font.Style := [fsBold];
end else
if TListBox(Control).Items.Strings[Index] = 'End' then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgEnd);
TListBox(Control).Canvas.Font.Style := [fsBold];
end;
// positions the text
TextPosition := (Rect.Bottom - Rect.Top - TListBox(Control).Canvas.TextHeight
(Text)) div 2;
// displays the text
TListBox(Control).Canvas.TextOut(Rect.Left + Images.Width + 8,
Rect.Top + TextPosition, TListBox(Control).Items.Strings[index]);
end;
procedure TForm1.ListBox3MeasureItem(Control: TWinControl; Index: Integer;
var Height: Integer);
begin
Height := ImageList1.Height;
end;
{******************************************************************************}
end.
I would appreciate some tips on how I could determine manipulating the items. I know I can change where the bitmap and texts are placed, but it is identifying if an item falls between the groups or not, and if it does set the correct indent level.
I hope this makes sense thats why I put some mock pictures up.
Thanks :)
PS, I never write small posts sorry!
UPDATE WITH WORKING DEMO
I have accepted Sertac's answer which I have working perfectly thanks Sertac.
To help others who may be viewing - and because I have been learning OOP I want to show my code to see if it is any good :)
I have made 2 units, Lib.pas contains the classes for the list items, and Unit1.pas is the Form1 unit (I shortened unit 1 to make it clearer to see what is going on):
Lib.pas
unit Lib;
interface
uses
Classes, StdCtrls;
type
TMyListData = class(TObject)
public
fCaption: string;
fImageIndex: integer;
public
property Caption: string read fCaption write fCaption;
property ImageIndex: integer read fImageIndex write fImageIndex;
constructor Create;
destructor Destroy; override;
end;
type
TLayoutItem = class(TMyListData);
TCalculatorItem = class(TMyListData);
TCommentItem = class(TMyListData);
TTimeItem = class(TMyListData);
TStartItem = class(TMyListData);
TEndItem = class(TMyListData);
const
imgLayout = 0;
imgCalculator = 1;
imgComment = 2;
imgTime = 3;
imgStart = 4;
imgEnd = 5;
procedure NewLayoutItem(aListBox: TListBox);
procedure NewCalculatorItem(aListBox: TListBox);
procedure NewCommentItem(aListBox: TListBox);
procedure NewTimeItem(aListBox: TListBox);
procedure NewStartItem(aListBox: TListBox);
procedure NewEndItem(aListBox: TListBox);
procedure DeleteItem(aListBox: TListBox; aIndex: integer);
procedure CalculateIndents(aListBox: TListBox);
implementation
{ TMyListData }
constructor TMyListData.Create;
begin
inherited Create;
end;
destructor TMyListData.Destroy;
begin
inherited;
end;
procedure NewLayoutItem(aListBox: TListBox);
var
Obj: TLayoutItem;
begin
Obj := TLayoutItem.Create;
try
Obj.Caption := 'Layout';
Obj.ImageIndex := imgLayout;
aListBox.AddItem(Obj.Caption, Obj);
finally
Obj.Free;
end;
CalculateIndents(aListBox);
end;
procedure NewCalculatorItem(aListBox: TListBox);
var
Obj: TCalculatorItem;
begin
Obj := TCalculatorItem.Create;
try
Obj.Caption := 'Calculator';
Obj.ImageIndex := imgCalculator;
aListBox.AddItem(Obj.Caption, Obj);
finally
Obj.Free;
end;
CalculateIndents(aListBox);
end;
procedure NewCommentItem(aListBox: TListBox);
var
Obj: TCommentItem;
begin
Obj := TCommentItem.Create;
try
Obj.Caption := 'Comment';
Obj.ImageIndex := imgComment;
aListBox.AddItem(Obj.Caption, Obj);
finally
Obj.Free;
end;
CalculateIndents(aListBox);
end;
procedure NewTimeItem(aListBox: TListBox);
var
Obj: TTimeItem;
begin
Obj := TTimeItem.Create;
try
Obj.Caption := 'Time';
Obj.ImageIndex := imgTime;
aListBox.AddItem(Obj.Caption, Obj);
finally
Obj.Free;
end;
CalculateIndents(aListBox);
end;
procedure NewStartItem(aListBox: TListBox);
var
Obj: TStartItem;
begin
Obj := TStartItem.Create;
try
Obj.Caption := 'Start';
Obj.ImageIndex := imgStart;
aListBox.AddItem(Obj.Caption, Obj);
finally
Obj.Free;
end;
CalculateIndents(aListBox);
end;
procedure NewEndItem(aListBox: TListBox);
var
Obj: TEndItem;
begin
Obj := TEndItem.Create;
try
Obj.Caption := 'End';
Obj.ImageIndex := imgEnd;
aListBox.AddItem(Obj.Caption, Obj);
finally
Obj.Free;
end;
CalculateIndents(aListBox);
end;
procedure DeleteItem(aListBox: TListBox; aIndex: integer);
begin
aListBox.Items.Delete(aIndex);
aListBox.Items.Objects[aIndex] := nil;
CalculateIndents(aListBox);
end;
procedure CalculateIndents(aListBox: TListBox);
var
i: Integer;
Indent: Integer;
begin
Indent := 0;
for i := 0 to aListBox.Items.Count - 1 do
begin
if aListBox.Items[i] = 'End' then
Dec(Indent);
if Indent > -1 then
aListBox.Items.Objects[i] := Pointer(Indent);
if aListBox.Items[i] = 'Start' then
Inc(Indent);
end;
for i := aListBox.Items.Count - 1 downto 0 do
begin
if (aListBox.Items[i] = 'End') and (Indent = -1) then
begin
DeleteItem(aListBox, i);
Break;
end;
end;
end;
end.
Unit1.pas
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ImgList, ComCtrls, Buttons;
type
TForm1 = class(TForm)
ImageList1: TImageList;
lbMain: TListBox;
btnLayout: TBitBtn;
btnCalculator: TBitBtn;
btnComment: TBitBtn;
btnTime: TBitBtn;
btnStartGroup: TBitBtn;
btnEndGroup: TBitBtn;
btnDelete: TBitBtn;
procedure FormCreate(Sender: TObject);
procedure lbMainMeasureItem(Control: TWinControl; Index: Integer;
var Height: Integer);
procedure lbMainDrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
procedure btnLayoutClick(Sender: TObject);
procedure btnCalculatorClick(Sender: TObject);
procedure btnCommentClick(Sender: TObject);
procedure btnTimeClick(Sender: TObject);
procedure btnStartGroupClick(Sender: TObject);
procedure btnEndGroupClick(Sender: TObject);
procedure btnDeleteClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses
Lib;
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
// set the listbox style here
lbMain.Style := lbOwnerDrawVariable;
end;
procedure TForm1.lbMainDrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
var
TextPosition: Integer;
Images: TImageList;
begin
TListBox(Control).Canvas.FillRect(Rect);
Images := ImageList1;
// draw the images
if TListBox(Control).Items.Strings[Index] = 'Layout' then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4 +
8 * Integer(TListBox(Control).Items.Objects[Index]),
Rect.Top, imgLayout);
end
else if TListBox(Control).Items.Strings[Index] = 'Calculator' then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4 +
8 * Integer(TListBox(Control).Items.Objects[Index]),
Rect.Top, imgCalculator);
end
else if TListBox(Control).Items.Strings[Index] = 'Comment' then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4 +
8 * Integer(TListBox(Control).Items.Objects[Index]),
Rect.Top, imgComment);
end
else if TListBox(Control).Items.Strings[Index] = 'Time' then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4 +
8 * Integer(TListBox(Control).Items.Objects[Index]),
Rect.Top, imgTime);
end
else if TListBox(Control).Items.Strings[Index] = 'Start' then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4 +
8 * Integer(TListBox(Control).Items.Objects[Index]),
Rect.Top, imgStart);
end
else if TListBox(Control).Items.Strings[Index] = 'End' then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4 +
8 * Integer(TListBox(Control).Items.Objects[Index]),
Rect.Top, imgEnd);
end;
// positions the text
TextPosition := (Rect.Bottom - Rect.Top - TListBox(Control).Canvas.TextHeight
(Text)) div 2;
// displays the text
TListBox(Control).Canvas.TextOut(
Rect.Left + Images.Width + 8 + 8 * Longint(TListBox(Control).Items.Objects[Index]),
Rect.Top + TextPosition, TListBox(Control).Items.Strings[index]);
end;
procedure TForm1.lbMainMeasureItem(Control: TWinControl; Index: Integer;
var Height: Integer);
begin
Height := ImageList1.Height;
end;
procedure TForm1.btnLayoutClick(Sender: TObject);
begin
NewLayoutItem(lbMain);
end;
procedure TForm1.btnCalculatorClick(Sender: TObject);
begin
NewCalculatorItem(lbMain);
end;
procedure TForm1.btnCommentClick(Sender: TObject);
begin
NewCommentItem(lbMain);
end;
procedure TForm1.btnTimeClick(Sender: TObject);
begin
NewTimeItem(lbMain);
end;
procedure TForm1.btnStartGroupClick(Sender: TObject);
begin
NewStartItem(lbMain);
end;
procedure TForm1.btnEndGroupClick(Sender: TObject);
begin
NewEndItem(lbMain);
end;
procedure TForm1.btnDeleteClick(Sender: TObject);
begin
if lbMain.ItemIndex <> -1 then
begin
DeleteItem(lbMain, lbMain.ItemIndex);
end;
end;
end.
It can be made better, ie assigning the image indexes based on the Items.Objects[] property but this works perfectly :)
One way is to iterate over items and modify the text to indicate indentation:
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
Indent: Integer;
begin
...
Indent := 0;
for i := 0 to ListBox3.Items.Count - 1 do begin
if Pos('End', ListBox3.Items[i]) > 0 then
Dec(Indent);
if Indent > 0 then
ListBox3.Items[i] := StringOfChar(#32, 2 * Indent) + ListBox3.Items[i];
if Pos('Start', ListBox3.Items[i]) > 0 then
Inc(Indent);
end;
end;
Since items' text are changed, this approach requires to test the text accordingly when drawing:
procedure TForm1.ListBox3DrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
var
TextPosition: Integer;
Images: TImageList;
begin
TListBox(Control).Canvas.FillRect(Rect);
Images := ImageList1;
// draw the images
if Pos('Layout', TListBox(Control).Items.Strings[Index]) > 0 then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4, Rect.Top, imgLayout);
end else
if Pos('Calculator', TListBox(Control).Items.Strings[Index]) > 0 then
..
(With this approach, indenting images would be a little work, count the leading spaces in item text, and so on..)
If items' objects are not used already, a slightly better approach can be to store indentation as an integer, and use that information when drawing. E.g. when iterating:
Indent := 0;
for i := 0 to ListBox3.Items.Count - 1 do begin
if ListBox3.Items[i] = 'Start' then
Inc(Indent);
ListBox3.Items.Objects[i] := Pointer(Indent);
if ListBox3.Items[i] = 'End' then
Dec(Indent);
end;
When drawing:
..
if TListBox(Control).Items.Strings[Index] = 'Layout' then
begin
Images.Draw(TListBox(Control).Canvas, Rect.Left + 4 +
8 * Integer(TListBox(Control).Items.Objects[Index]),
Rect.Top, imgLayout);
..
// displays the text
TListBox(Control).Canvas.TextOut(
Rect.Left + Images.Width + 8 + 8 * Longint(TListBox(Control).Items.Objects[Index]),
Rect.Top + TextPosition, TListBox(Control).Items.Strings[index]);
..
I think you should probably use the TTreeView instead, which already supports indenting child items.
To answer your question, I think you could use recursion to draw the items in your TListBox. Using recursion, it is easy to see how many levels deep you are.
This is how most parsers work, such as HTML parsers.
Here's some pseudo code that illustrates the concept:
procedure DrawBranch(branch: TMyList; indent: Integer);
var
i: Integer;
begin
// Draw the current branch, using the indent value
branch.Draw;
// Iterate through all of the child branches
for i := 0 to branch.Children.Count - 1 do
begin
// Each time we recurse further, we add 1 to the indent
DrawBranch(branch.Child[i], indent + 1);
end;
end;
procedure DrawTree;
begin
// Start the whole thing off with the root branch
// We start the indent at 0
DrawBranch(root, 0);
end;
You'll want a "hidden" root node in your case.
You'd use similar same logic to add your items to a TTreeView.