Create, destroy and count values inside dynamic controls in Delphi - delphi

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.

Related

Delphi FMX TTreeView Argument out of range exception

Using Delphi 10.4.
I am hoping someone can explain what I am doing wrong with my FMX TTreeView that is causing an EArgumentOutOfRangeException. I am trying to create a custom TTreeViewItem class that allows me to associate some data with each node, as well as provide an in-place editor to allowing changing the node text.
The code below is a stripped down version of what I am doing. The FMX form has a TTreeview and two buttons on it, with the form's Onshow set to FormShow and the buttons set to the two button events.
The TVLinkTreeViewItem is my custom TTreeViewItem where I add a background and edit component for my in-place editor, which is displayed when a node is double clicked.
When you run the code as is, the program will throw the exception when the logic gets to the TreeView1.EndUpdate call at the end of the FormShow routine. The exception is thrown in FMX.Controls in the TControl.EndUpdate procedure.
If you comment out the ExpandAll call, the exception is not thrown, but if you mess with the expanding and collapsing of the nodes and resizing of the form, sooner or later the exception gets thrown. I left the ExpandAll line in the code below, as I assume the exception is being caused by the same error.
From what I can tell, the problem appears to be how I am setting up the fBackground and fEditor. If I don't call the AddObject routine and not set the Parent properties, I get no exception.
So can anybody tell me what I am doing wrong? Or is there a better way to do an in-place editor for the FMX TTreeViewItems component?
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.TreeView, FMX.Layouts, FMX.Controls.Presentation,
FMX.MultiView, FMX.Edit, FMX.Objects, FMX.StdCtrls;
type
TForm1 = class(TForm)
TreeView1: TTreeView;
Button1: TButton;
Button2: TButton;
procedure FormShow(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
type
TVLinkTreeViewItem = class(TTreeViewItem)
private
fData: string;
fEditor: TEdit;
fBackground: TRectangle;
procedure TreeViewItem1DblClick(Sender: TObject);
procedure EditorExit(Sender: TObject);
procedure EditorKeyUp(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState);
public
property Editor: TEdit read fEditor write fEditor;
property Data: string read fData write fData;
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
TreeView1.ExpandAll;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
TreeView1.CollapseAll;
end;
procedure TForm1.FormShow(Sender: TObject);
var
I, c, r, s: Integer;
vNode1,
vNode2,
vNode3,
vNode4: TVLinkTreeViewItem;
begin
TreeView1.BeginUpdate;
TreeView1.Clear;
for I := 0 to 4 do
begin
vNode1 := TVLinkTreeViewItem.Create(TreeView1);
vNode1.Text := 'Level 1 - '+ IntToStr(I);
TreeView1.AddObject(vNode1);
for c := 0 to 4 do
begin
vNode2 := TVLinkTreeViewItem.Create(vNode1);
vNode2.Text := 'Level 2 - '+ IntToStr(c);
vNode1.AddObject(vNode2);
for r := 0 to 4 do
begin
vNode3 := TVLinkTreeViewItem.Create(vNode2);
vNode3.Text := 'Level 3 - '+ IntToStr(r);
vNode2.AddObject(vNode3);
// for s := 0 to 4 do
// begin
// vNode4 := TVLinkTreeViewItem.Create(vNode3);
// vNode4.Text := 'Level 4 - '+ IntToStr(s);
// vNode3.AddObject(vNode4);
// end;
end;
end;
end;
//ExpandAll works when no parent is set for fBackGround and fEditor is not set in "TVLinkTreeViewItem.Create" below"
//If the Parents are set below, ExpandAll/EndUpdate causes "Augument out of range" exception.
TreeView1.ExpandAll;
treeView1.EndUpdate;
end;
{ TVLinkTreeViewItem }
constructor TVLinkTreeViewItem.Create(AOwner: TComponent);
begin
inherited;
fData := '';
fBackground := TRectangle.Create(AOwner);
//When ExpandAll is not called in FormShow,
// Calling "AddObject" or setting parent, as shown below, make all the code work,
// but will get intermident "Augument out of range" exceptions when resizing form,
// or when expanding or collapsing nodes using the buttons.
self.AddObject(fBackGround);
//fBackGround.Parent := self;
fBackGround.Visible := false;
fEditor := TEdit.Create(AOwner);
fBackGround.AddObject(fEditor);
//fEditor.Parent := fBackGround;
fEditor.Visible := false;
fEditor.Align := TAlignLayout.Client;
fEditor.OnKeyDown := EditorKeyUp;
self.OnDblClick := TreeViewItem1DblClick;
fEditor.OnExit := EditorExit;
end;
destructor TVLinkTreeViewItem.Destroy;
begin
inherited;
end;
procedure TVLinkTreeViewItem.TreeViewItem1DblClick(Sender: TObject);
begin
fBackGround.Visible := true;
fBackGround.Width := self.Width - 20;
fBackGround.Height := self.Height;
fBackGround.Position.X := 20;
fEditor.Enabled := true;
fEditor.Visible := true;
fEditor.Opacity := 1;
fBackGround.BringToFront;
fEditor.BringToFront;
fEditor.Text := Text;
fEditor.SetFocus;
fEditor.SelectAll;
end;
procedure TVLinkTreeViewItem.EditorKeyUp(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState);
begin
inherited;
if Key = vkReturn then
begin
Text := fEditor.Text;
fBackGround.Visible := false;
fEditor.Enabled := false;
end
else if Key in [vkEscape, vkCancel, vkTab, vkHardwareBack] then
begin
fBackGround.Visible := false;
fEditor.Enabled := false;
end;
end;
procedure TVLinkTreeViewItem.EditorExit(Sender: TObject);
begin
fBackGround.Visible := false;
fEditor.Enabled := false;
fEditor.Visible := false;
end;
end.
Here's the fmx content:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 480
ClientWidth = 640
FormFactor.Width = 320
FormFactor.Height = 480
FormFactor.Devices = [Desktop]
OnShow = FormShow
DesignerMasterStyle = 0
object TreeView1: TTreeView
Align = Left
Size.Width = 269.000000000000000000
Size.Height = 480.000000000000000000
Size.PlatformDefault = False
TabOrder = 0
Viewport.Width = 265.000000000000000000
Viewport.Height = 476.000000000000000000
end
object Button1: TButton
Position.X = 356.000000000000000000
Position.Y = 68.000000000000000000
TabOrder = 2
Text = 'Expand'
OnClick = Button1Click
end
object Button2: TButton
Position.X = 354.000000000000000000
Position.Y = 102.000000000000000000
TabOrder = 1
Text = 'Collapse'
OnClick = Button2Click
end
end

Clientdataset 'index not found' error in nested dataset

I am attempting to find a subset of records in a clientdataset by using a clone cursor to seek a records using an indexdef. In the example below I have created a button to implement the clone creation. The code works fine for a clientdataset named cdsData. However if I nest cdsData in another dataset (cdsMaster) then the button code fails to find the indexDef.
The Form: (including both clientdatasets showing both datasources and clientdatasets. Omit dsMaster & cdsMAster in first unit example below)
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 289
ClientWidth = 554
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 Button1: TButton
Left = 32
Top = 24
Width = 75
Height = 25
Caption = 'Clone'
TabOrder = 0
OnClick = Button1Click
end
object dsMaster: TDataSource
DataSet = cdsMaster
Left = 56
Top = 80
end
object cdsMaster: TClientDataSet
Aggregates = <>
Params = <>
Left = 56
Top = 144
end
object dsData: TDataSource
DataSet = cdsData
Left = 152
Top = 88
end
object cdsData: TClientDataSet
Aggregates = <>
Params = <>
Left = 152
Top = 144
end
end
The successful cdsData unit:
unit IndexFindTest;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Data.DB, Datasnap.DBClient;
type
TForm1 = class(TForm)
dsData: TDataSource;
cdsData: TClientDataSet;
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
Disp, Lbl : string;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
Clone: TClientDataset;
begin
Clone := TClientDataset.Create(nil);
try
Clone.CloneCursor(cdsData,false);
cdsData.IndexDefs.Update;
clone.IndexName := cdsData.IndexDefs.find ('Lbl').Name;
//..added code to select a range of records using the IndexDef
finally
clone.free;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
disp := 'Item 1';
lbl := 'a';
with TStringField.Create(Self) do begin
Name := 'MstrDisplays';
FieldKind := fkData;
FieldName := 'Displays';
Size := 10;
DataSet := cdsData;
end;
with TStringField.Create(Self) do begin
Name := 'MstrLabel';
FieldKind := fkData;
FieldName := 'Label';
Size := 10;
DataSet := cdsData;
end;
with cdsData.IndexDefs.AddIndexDef do begin
Name := 'Lbl';
Fields := 'Displays;Label';
Options := [ixCaseInsensitive];
end;
cdsData.CreateDataSet;
end;
end.
The nested version that fails:
unit IndexFindTest2;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Data.DB, Datasnap.DBClient;
type
TForm1 = class(TForm)
dsMaster: TDataSource;
cdsMaster: TClientDataSet;
Button1: TButton;
dsData: TDataSource;
cdsData: TClientDataSet;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
Disp, Lbl : string;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
Clone: TClientDataset;
begin
Clone := TClientDataset.Create(nil);
try
Clone.CloneCursor(cdsData,false);
cdsData.IndexDefs.Update;
//Error generated in next line
clone.IndexName := cdsData.IndexDefs.find ('Lbl').Name;
//..added code to select a range of records using the IndexDef
finally
clone.free;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
disp := 'Item 1';
lbl := 'a';
with TStringField.Create(Self) do begin
Name := 'MstrTitle';
FieldKind := fkData;
FieldName := 'Title';
Size := 10;
DataSet := cdsMaster;
end;
with TDataSetField.Create(Self) do
begin
Name := 'MstrDisplay';
FieldName := 'Displays';
DataSet := cdsMaster;
end;
cdsData.DataSetField := TDataSetField(cdsMaster.FieldByName('Displays'));
with TStringField.Create(Self) do begin
Name := 'ClientNested';
FieldKind := fkData;
FieldName := 'Notes';
Size := 10;
DataSet := cdsData;
end;
with TStringField.Create(Self) do begin
Name := 'kntLabel';
FieldKind := fkData;
FieldName := 'Label';
Size := 10;
DataSet := cdsData;
end;
cdsData.IndexDefs.Update;
with cdsData.IndexDefs.AddIndexDef do begin
Name := 'Lbl';
Fields := 'Notes;Label';
Options := [ixCaseInsensitive];
end;
cdsMaster.CreateDataSet;
end;
end.
When running the second program I receive the error
cdsData: index 'Lbl' not found.
The only difference I can identify between the two programs is the fact that cdsData is nested in the second version. I found a note in CaryJensen's Delphi in Depth:clientDatasets 2nd edition (pg128) stating that the erro may occur and can be fixed using update, but no matter where in the sequence I apply the update, it does not work in this situation.
Can anyone shed light on this problem? Are there additional steps with a nested dataset?

2 delphi questions, copying code and randomizing

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.

Allow multiple child controls to detect when their parent control resizes

I'm writing a TSplitter descendant that proportionally resizes its aligned control when its parent control resizes. In order to detect the parent resize I subclass the parents WinProc procedure
FOldWindowProc := Parent.WindowProc;
Parent.WindowProc := SubclassedParentWndProc;
This works perfectly when there is a single splitter parented by the parent. However, when there are one or more splitters, only one of them works correctly.
How can I receive a notification to all the splitter controls that the parent has resized?
Here's my code
unit ProportionalSplitterU;
interface
uses
Windows, SysUtils, Controls, Messages, Classes, CommCtrl, ExtCtrls;
type
TSPlitterHelper = class helper for TSplitter
public
function FindControlEx: TControl;
end;
TProportionalSplitter = class(TSplitter)
private
FOldWindowProc: TWndMethod;
FControlRatio: Double;
FProportionalResize: Boolean;
procedure SubclassedParentWndProc(var Msg: TMessage);
procedure SetRatio;
procedure SetProportionalResize(const Value: Boolean);
protected
procedure SetParent(AParent: TWinControl); override;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure StopSizing; override;
public
constructor Create(AOwner: TComponent); override;
published
property ProportionalResize: Boolean read FProportionalResize write SetProportionalResize;
end;
implementation
{ TProportionalSplitter }
constructor TProportionalSplitter.Create(AOwner: TComponent);
begin
inherited;
FProportionalResize := True;
end;
procedure TProportionalSplitter.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited;
if (Operation = opRemove) and
(AComponent = Parent) then
begin
Parent.WindowProc := FOldWindowProc;
FOldWindowProc := nil;
end;
end;
procedure TProportionalSplitter.SetParent(AParent: TWinControl);
begin
FControlRatio := -1;
if Assigned(Parent) then
begin
Parent.WindowProc := FOldWindowProc;
end;
inherited SetParent(AParent);
if Assigned(AParent) then
begin
FOldWindowProc := Parent.WindowProc;
Parent.WindowProc := SubclassedParentWndProc;
SetRatio;
end;
end;
procedure TProportionalSplitter.SetProportionalResize(const Value: Boolean);
begin
FProportionalResize := Value;
SetRatio;
end;
procedure TProportionalSplitter.SetRatio;
var
ActiveControl: TControl;
begin
if FProportionalResize then
begin
ActiveControl := FindControlEx;
if (Parent <> nil) and
(ActiveControl <> nil) then
begin
case Align of
alTop,
alBottom: FControlRatio := ActiveControl.Height / Parent.Height;
alLeft,
alRight: FControlRatio := ActiveControl.Width / Parent.Width;
end;
end;
end
else
begin
FControlRatio := -1;
end;
end;
procedure TProportionalSplitter.StopSizing;
begin
inherited;
SetRatio;
end;
procedure TProportionalSplitter.SubclassedParentWndProc(Var Msg: TMessage);
begin
FOldWindowProc(Msg);
if Msg.Msg = WM_SIZE then
begin
if FControlRatio <> -1 then
begin
case Align of
alTop,
alBottom: FindControlEx.Width := Round(Parent.Height * FControlRatio);
alLeft,
alRight: FindControlEx.Width := Round(Parent.Width * FControlRatio);
end;
end
else
begin
SetRatio;
end;
end;
end;
{ TSPlitterHelper }
function TSPlitterHelper.FindControlEx: TControl;
begin
Result := Self.FindControl;
end;
end.
Demo .pas
unit Unit2;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls,
ProportionalSplitterU;
type
TForm2 = class(TForm)
Panel1: TPanel;
Panel2: TPanel;
Panel3: TPanel;
procedure FormCreate(Sender: TObject);
private
FSplitter: TProportionalSplitter;
FSplitter2: TProportionalSplitter;
public
{ Public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
procedure TForm2.FormCreate(Sender: TObject);
begin
FSplitter := TProportionalSplitter.Create(Self);
FSplitter.Parent := Self;
FSplitter.Align := alLeft;
FSplitter.Left := Panel1.Width + 1;
FSplitter.Width := 20;
FSplitter.ResizeStyle := rsUpdate;
FSplitter2 := TProportionalSplitter.Create(Self);
FSplitter2.Parent := Self;
FSplitter2.Align := alTop;
FSplitter2.Top := Panel3.Height + 1;
FSplitter2.Height := 20;
FSplitter2.ResizeStyle := rsUpdate;
end;
end.
Demo .dfm
object Form2: TForm2
Left = 0
Top = 0
Caption = 'Form2'
ClientHeight = 478
ClientWidth = 674
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
PixelsPerInch = 120
TextHeight = 16
object Panel1: TPanel
Left = 0
Top = 193
Width = 249
Height = 285
Align = alLeft
Caption = 'Panel1'
TabOrder = 0
ExplicitTop = 0
ExplicitHeight = 478
end
object Panel2: TPanel
Left = 249
Top = 193
Width = 425
Height = 285
Align = alClient
Caption = 'Panel2'
TabOrder = 1
ExplicitTop = 0
ExplicitHeight = 478
end
object Panel3: TPanel
Left = 0
Top = 0
Width = 674
Height = 193
Align = alTop
Caption = 'Panel3'
TabOrder = 2
end
end
You code is working perfectly correctly as far as intercepting parent window messages is concerned. There is however a problem in your window hook code which may have lead you to incorrectly conclude that this was not working as one of your panels in your test case was not being proportionally resized.
The problem is in this code:
case Align of
alTop, vvvvv
alBottom : FindControlEx.Width := Round(Parent.Height * FControlRatio);
^^^^^
alLeft,
alRight : FindControlEx.Width := Round(Parent.Width * FControlRatio);
end;
Notice that in both cases you are setting the WIDTH of the active control. For Top/Bottom aligned splitter you should instead be setting the HEIGHT.
case Align of
alTop, vvvvvv
alBottom : FindControlEx.Height := Round(Parent.Height * FControlRatio);
^^^^^^
alLeft,
alRight : FindControlEx.Width := Round(Parent.Width * FControlRatio);
end;
This is why your top panel was not resizing its height, even though the WM_SIZE message is being received.

can't get Elements... elements of webbrowser. 'A' and 'INPUT' ando so on... delphi 2007

guys my english is bad, but I need your help...
I can't get frames and elements by one webbrowser, and I need get all. "delphi 2007".
without this application on my pc, I get all, but when I install this application, many inputs not be assigned. see...
public
doc1: IHTMLDocument2;
Elementos: IHTMLElementCollection;
Elemento: IHTMLElement;
end;
procedure TNavegador.wbDocumentComplete(ASender: TObject; const pDisp: IDispatch; var URL: OleVariant);
var
Z : Integer;
begin
doc1 := (pDisp as IWebBrowser2).Document as IHTMLDocument2;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Memo3.Text := Wb.OleObject.Document.documentElement.innerText; //not assigned
memo2.Text := doc1.body.innerHTML; // work. <-----
Elementos := (doc1.all).tags('A') as IHTMLElementCollection; //not assigned
if Assigned(Elementos) then
begin
for Z := 0 to Elementos1.length - 1 do
begin
Elemento := Elementos.Item(Z, 0) as IHTMLElement;
if Assigned(Elemento) then
begin
if pos('/IMG/bt_voltar.gif', Elemento.innerHTML) > 0 then
begin
Elemento.Click; //click in link back
end;
end;
end;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
Q : Integer;
Elementos1: IHTMLElementCollection;
Elemento1: IHTMLElement;
begin
Elementos1 := (doc1.all).tags('INPUT') as IHTMLElementCollection; //not assigned
for Q := 0 to Elementos1.length - 1 do
begin
Elemento1 := Elementos1.Item(Q, 0) as IHTMLElement;
if Assigned(Elemento1) then
begin
if Elemento1.getAttribute('name', 0) = 'Post_me' then
begin
Elemento1.setAttribute('value', '010203', 0);
end;
if Elemento1.getAttribute('name', 0) = 'btn_click' then
begin
Elemento1.Click;
end;
end;
end;
end;
function getAllInputs(doc: IHTMLDocument2): IHTMLElementCollection; //not assigned
var
elementos: IHTMLElementCollection;
begin
elementos := (doc.all).tags('input') as IHTMLElementCollection;
result := elementos;
end;
function getAllLinks(doc: IHTMLDocument2): IHTMLElementCollection; //not assigned
var
elementos: IHTMLElementCollection;
begin
elementos := (doc.all).tags('A') as IHTMLElementCollection;
result := elementos;
end;
Many Idea????? waiting.
thank's.
Your problem lies in the fact that the OnDocumentComplete event will be fired for EACH frameset + the top document. Here is some sample code how to correctly implement this event:
procedure TFrm_browser.BrowserDocumentComplete(ASender: TObject; const pDisp: IDispatch; var URL: OleVariant);
var CurrentBrowser: IWebBrowser2;
TopBrowser: IWebBrowser2;
Doc : IHTMLDocument2;
begin
CurrentBrowser := pDisp as IWebBrowser2;
TopBrowser := (ASender as TWebbrowser).DefaultInterface;
if Assigned(CurrentBrowser) and Assigned(TopBrowser) then
begin
Doc := CurrentBrowser.Document as IHTMLDocument2;
if CurrentBrowser = TopBrowser then
begin
if Assigned(FOnCompleteDocLoaded) then
FOnCompleteDocLoaded(Self, Doc);
end
else
begin
if Assigned(FOnFrameSetLoaded) then
FOnFrameSetLoaded(Self, Doc);
end;
end;
end;
You must process each frameset and the top document.
EDIT
Since the OP does not have a clue, I made a small testproject:
unit Unit4;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, OleCtrls, SHDocVw, StdCtrls, Mshtml;
type
TForm1 = class(TForm)
WebBrowser1: TWebBrowser;
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
procedure WebBrowser1DocumentComplete(ASender: TObject; const pDisp: IDispatch; var URL: OleVariant);
private
{ Private declarations }
procedure GetH3Tags(Doc : IHTMLDocument2);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
WebBrowser1.Navigate('http://www.w3schools.com/tags/tryit.asp?filename=tryhtml_frame_cols');
end;
procedure TForm1.GetH3Tags(Doc: IHTMLDocument2);
var Elements: IHTMLElementCollection;
Element : IHTMLElement;
Index : Integer;
begin
Elements := Doc.all.tags('h3') as IHTMLElementCollection;
Index := Elements.length;
while Index > 0 do
begin
Dec(Index);
Element := Elements.item(Index, '') as IHTMLElement;
Memo1.Lines.Add(Element.innerText);
end;
end;
procedure TForm1.WebBrowser1DocumentComplete(ASender: TObject; const pDisp: IDispatch; var URL: OleVariant);
var CurrentBrowser: IWebBrowser2;
TopBrowser: IWebBrowser2;
Doc : IHTMLDocument2;
begin
CurrentBrowser := pDisp as IWebBrowser2;
TopBrowser := (ASender as TWebbrowser).DefaultInterface;
if Assigned(CurrentBrowser) and Assigned(TopBrowser) then
begin
Doc := CurrentBrowser.Document as IHTMLDocument2;
if CurrentBrowser = TopBrowser then
begin
// get tags for top level document
GetH3Tags(Doc);
end
else
begin
// get tags for each frameset
GetH3Tags(Doc);
end;
end;
end;
end.
DFM file:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 427
ClientWidth = 899
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 WebBrowser1: TWebBrowser
Left = 209
Top = 0
Width = 690
Height = 427
Align = alClient
TabOrder = 0
OnDocumentComplete = WebBrowser1DocumentComplete
ExplicitLeft = 56
ExplicitTop = 24
ExplicitWidth = 300
ExplicitHeight = 150
ControlData = {
4C00000050470000222C00000000000000000000000000000000000000000000
000000004C000000000000000000000001000000E0D057007335CF11AE690800
2B2E126208000000000000004C0000000114020000000000C000000000000046
8000000000000000000000000000000000000000000000000000000000000000
00000000000000000100000000000000000000000000000000000000}
end
object Memo1: TMemo
Left = 0
Top = 0
Width = 209
Height = 427
Align = alLeft
Color = clHighlight
TabOrder = 1
end
end
This sample will get all H3 tags from this page:
http://www.w3schools.com/tags/tryit.asp?filename=tryhtml_frame_cols
#Tlama: this is a good example where OnDocumentcomplete will fire multiple times.

Resources