Dynamically creating a new component without predeclared variable limitation? - delphi

I am working on a school project. I need the user to click a button and each time the button is pressed a new component with a new name is created.
The idea I had looked something like this
procedure TForm1.btnClick (Sender: TObject);
Var
pnlTest1, pnlTest2 : TPanel;
Begin
If iCount = 1
then
Begin
pnlTest1 := TPanel.Create(Self);
pnlTest1.Parent := Self;
pnlTest.Top := 0;
Etc...
End
Else if iCount = 2
Then
Begin
PnlTest2 := TPanel.Create(Self);
PnlTest2.Parent := Self;
PnlTest2.Top := 0;
Etc...
End;
The problem is i can only create as many components as I have created varaibles and I need the user to craete basically an infinite amount of new components. I tried other methods as well but the same problem occurs, I also tried creating dynamic variables with pointers and so on but the data types of those varaibles worked only (in my efforts) with the basic data types (Integer, String etc) and so i could not create a variable in run time to create a component (in the example pnlTest : TPanel was declared, this did not work with the dynamic variables)
Please advise on how i can create a new component everytime the button is clicked without bieng limited by the preexisting variables or please advise on how i can have a ''ínfinite'' amount of variables to use for creating a new component each time.

You only need to declare 1 pointer variable to receive the new component instance, eg:
private
iCount: Integer;
...
procedure TForm1.btnClick (Sender: TObject);
Var
pnlTest : TPanel;
Begin
pnlTest := TPanel.Create(Self);
pnlTest.Parent := Self;
pnlTest.Name := 'pnlTest' + IntToStr(iCount);
pnlTest.Top := 0;
//...
Inc(iCount);
End;
The component instance is stored in the Form's Components and Controls properties, since you are assigning the Form as the component's Owner and Parent, respectively.

Related

How to find what form is calling a public procedure

I have too many Forms and I have a procedure that should be running on all form when created
procedure TDM.SetupForm(Max, DisableResize,
DisableMove: Boolean; FormWidth: Integer = 0; FormHeight: Integer = 0);
var
Form: TForm;
begin
Form := ??? // How to find the what form is running this procedure?
Form.AutoScroll := True;
if Max then
begin
Form.Width := Screen.WorkAreaWidth;
Form.Height := Screen.WorkAreaHeight;
Form.Top := 0;
Form.Left := 0;
end
else
begin
if FormWidth > 0 then
Form.Width := FormWidth;
if FormHeight > 0 then
Form.Height := FormHeight;
Form.Position := poScreenCenter;
Form.Align := alCustom;
end;
if DisableResize then
DeleteMenu(GetSystemMenu(Form.Handle, False), SC_SIZE, MF_BYCOMMAND);
if DisableMove then
DeleteMenu(GetSystemMenu(Form.Handle, False), SC_MOVE, MF_BYCOMMAND);
Form.BorderIcons := [biSystemMenu];
if Form.Height > Screen.WorkAreaHeight then
Form.Height := Screen.WorkAreaHeight;
if Form.Width > Screen.WorkAreaWidth then
Form.Width := Screen.WorkAreaWidth;
Form.ShowHint := True;
Form.OnClose := CloseFormAction;
end;
I call this Procedure on FormCreate event
How can I find what form is calling this procedure and use it inside same procedure without passing it as parameter?
I call this procedure on FormCreate event
It seems to me you don't actually need to know the "Last Created Form", but rather which form is currently being created, which you want to call this code for. If that is the case, simply add a TForm parameter to this procedure instead of declaring a variable and trying to obtain it from elsewhere...
procedure TDM.SetupForm(Form: TForm; Max, DisableResize,
DisableMove: Boolean; FormWidth: Integer = 0; FormHeight: Integer = 0);
begin
...Use the `Form` parameter...
Then you would pass Self into this whenever you call it from FormCreate...
DM.SetupForm(Self, ....
Ultimately, this sort of thing is best accomplished by creating a base form first, and then inheriting all the rest of your forms from this base. Such code would be implemented in the base form's constructor, and then you wouldn't have to explicitly call it from each and every form you wish to apply it to. However, it seems you already have many forms written and this would require modifying all of your existing code to consider the base form. Such design should be done from the beginning of development.
I must also note that putting UI code of such nature into a data module is not the right practice. A data module's purpose is to be disconnected from the UI. That's why it's not actually a visible form, but a non-visual-component-only solution. It's best to put such code in independent units for that purpose, such as MyApp.UICommon.pas.

Change Memo1.Font of all Dynamically Created Forms on Delphi

I create many forms on runtime using
Application.CreateForm(TForm2, Form2);
Form2.Show;
Now I need to change the Memo1.Font of them at once.
Form2.Memo1.Font:=newfont;
But only the latest created form's Memo1.Font changes. How can I change all?
I guess you're calling Application.CreateForm(TForm2, Form2); multiple times which reassigns the newly created form to your global Form2 variable, so later when you refer to Form2 you're referring to the last-created instance.
To access all instances of TForm2 in your application, you can use Screen.Forms property:
for I := 0 to Screen.FormCount - 1 do
if Screen.Forms[I] is TForm2 then
TForm2(Screen.Forms[I]).Memo1.Font := ...
The reason for this behaviour is, that component names should be unique,
You can not address multiple components by just one name!
In this case You will have to iterate through all components to find all TMemos.
This could look something like
var i,j: integer;
begin
// first find all Forms in Application
for i:=0 to Application.ComponentCount - 1 do
begin
if Application.Components[i] is TForm then
begin
with (Application.Components[i] as TForm) do
begin
// now find all TMemos and change the font
for j:=0 to ComponentCount-1 do
begin
if (Components[j] is TMemo) and (Components[j].Name = 'Memo1') then (Components[j] as TMemo).Font.Name := 'Arial';
end;
end;
end;
end;
This is a very general approach and You can easily adapt it to other components within Your application.
Another approach would be to memorize all created TMemos in an Objectlist when they are created the first time (then You could easily iterate the object list and change TMemos properties) but without knowing more about Your implementation it is hard to give a good advice.

Accessing StringGrid from another Form

Edit the body for more details.
I have a Form called ENP, defined in the EnpView unit. The ENP form, is created and shown from the handler event of a click a toolbar item in the Main Form (TPrincipal).
procedure TPrincipal.ENP1Click(Sender: TObject);
begin
TENP.Create(self).Show();
end;
Enp form knows (in public declaration) clearGrid() message.
ENP Form have a TStringGrid called StringGrid. And a button called "Add". When click "Add" button other Form is created and shown: AddEnp form, defined in EnpViewAdd unit.
procedure TENP.opAgregarClick(Sender: TObject);
begin
TAddEnp.Create(self).Show();
end;
The AddEnp form, have any TEdits. The values of the inputs must be adding in the EnpView.StringGrid.
I try this:
implementation
uses
EnpView, Main;
procedure TAddEnp.AgregarClick(Sender: TObject);
begin
{ Agrego el nodo xml }
Globals.Xml.agregarMuestra(muestra.Text);
Globals.Xml.insertEnp(muestra.Text,golpes.Text,metros.Text);
{ Send messages to EnpView Form }
ENP.clearGrid();
ENP.populateGrid();
end;
ClearGrid messages fails in line 1, with access violation:
procedure TENP.clearGrid();
begin
Self.StringGrid.RowCount := 2;
Self.StringGrid.Rows[1].Clear();
end;
The clearGrid methods works if is send within the class. Any ideas ?.
Create a property named, for instance, ENPForm in TAddENP and assign the ENP form just after creating it. Declare it as follows:
TAddENP = class(TForm)
private
FENPForm: TENP;
// all of your already existing declarations
public
property ENPForm: TENP read FENPForm write FENPForm;
end;
Now that you have a possible reference to ENP form, you can use it as you like.
During the creation of TAddENP form, do as follows:
procedure TENP.opAgregarClick(Sender: TObject);
var
addForm: TAddENP;
begin
addForm := TAddEnp.Create(Self);
addForm.EnpForm := Self;
addForm.Show;
end;
Now you created the second form and gave it a secure reference to the first one. They can now talk to each other safely.
I advise you to avoid having one form operating other one´s components, because this increses the dependency between them. Instead, declare public methods to do that, so the forms will depend on their interfaces, not their implementations.
I hope this helps.
From your question (I added code-style to make it more clear):
I have a Form called ENP, defined in the EnpView unit. The ENP form,
is created and shown from the handler event of a click a toolbar item
in the Main Form (TPrincipal).
procedure TPrincipal.ENP1Click(Sender: TObject);
begin
TENP.Create(self).Show();
end;
This does nothing with your ENP form variable.
You create an instance of the TENP form class and show it using Show, but the ENP variable is not assigned.
You cannot assign the instance to the ENP variable, as each button click creates a new instance (so you have multiple instances of TENP) around.
Then you create a convoluted depedency of a TAddEnp instance and the (never assinged ENP variable).
You do this by creating a TAddEnp instance (why TAddEnp here, and not TAddENP?) show it using Show (giving the users the opportunity to go back to the TENP instance and again click on the opAgregar button to create more instances of TAddEnp):
procedure TENP.opAgregarClick(Sender: TObject);
begin
TAddEnp.Create(self).Show();
end;
Followed by having the TAddEnp depend on the ENP variable:
procedure TAddEnp.AgregarClick(Sender: TObject);
begin
//...
ENP.clearGrid();
ENP.populateGrid();
end;
This will indeed fail:
ClearGrid messages fails in line 1, with access violation:
procedure TENP.clearGrid();
begin
Self.StringGrid.RowCount := 2;
Self.StringGrid.Rows[1].Clear();
end;
The reason is that ENP is not assigned (by default it will be nil), so inside clearGrid, the Self will also be nil.
Solutions you could implement
Keep a single instance of TENP and TAddEnp around, using ShowModal to force modality to prevent the user from clicking on the same buttons multiple times.
Keep your existing Show behaviour, but binding each TAddEnp instance to the TENP instance it was created from.
For the first solution, your code will become this:
procedure TPrincipal.ENP1Click(Sender: TObject);
begin
ENP := TENP.Create(Application);
ENP.ShowModal();
ENP.Release();
ENP := nil;
end;
and
procedure TENP.opAgregarClick(Sender: TObject);
begin
AddEnp := TAddEnp.Create(Application);
AddEnp.ShowModal();
AddEnp.Release();
AddEnp := nil;
end;
The second will take more effort, as you need to prevent the use of the existing variables.
Delete the ENP variable.
Delete the AddENP variable.
Fix the self -> Self in the methods
procedure TPrincipal.ENP1Click(Sender: TObject);
begin
TENP.Create(Self).Show();
end;
and
procedure TENP.opAgregarClick(Sender: TObject);
begin
TAddEnp.Create(Self).Show();
end;
Here add the dependency:
procedure TAddEnp.AgregarClick(Sender: TObject);
var
ENP: TENP;
begin
//...
ENP := Owner as TENP; // Owner will be of type TENP because of the Create(Self) in the ENP1Click method
ENP.clearGrid();
ENP.populateGrid();
end;

How do I refer to components created at runtime rather than in the form designer?

i have a little problem. I'm trying to create in Delphi7 a list of components at run-time and to resize them with form's .OnResize event but no use... i can't figure out how to do it.
Here's my code:
procedure TForm1.Button1Click(Sender: TObject);
var
//ExtCtrls
panel: TPanel;
memo: TMemo;
splitter: TSplitter;
list: TListBox;
begin
panel := TPanel.Create(Self);
list := TListBox.Create(Self);
splitter := TSplitter.Create(Self);
memo := TMemo.Create(Self);
with panel do
begin
Parent := Form1;
BevelOuter := bvNone;
Top := 12;
Left := 12;
Height := Form1.Clientheight - 24;
Width := Form1.Clientwidth - 24;
end;
with list do
begin
Parent := panel;
Align := alClient;
Top := 0;
Height := panel.Height;
end;
with splitter do
begin
Parent := panel;
Top := 0;
Width := 12;
Align := alLeft;
end;
with memo do
begin
Parent := panel;
Top := 0;
Left := 0;
Width := round(panel.Width / 4) * 3;
Height := panel.Height;
Align := alLeft;
end;
end;
Do i have to somehow register their names in order to use them in form's event? Or maybe, to create a class and include them?
Any kind of help is really appreciated! Thank you in advance.
Your variables are local to the procedure where they are created so you can't refer to them using those variables when outside that procedure. The solution is to make them fields of the form class.
type
TForm1 = class(TForm)
procedure FormResize(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
FPanel: TPanel;
FMemo: TMemo;
FSplitter: TSplitter;
FList: TListBox;
end;
Then your FormResize event handler can refer to them.
procedure TForm1.FormResize(Sender: TObject);
begin
if Assigned(FPanel) then
begin
...
end;
end;
Don't forget to remove the local variables from Button1Click and use the fields instead.
procedure TForm1.Button1Click(Sender: TObject);
begin
FPanel := TPanel.Create(Self);
...
end;
Although David's answer is also very correct, I thought I would take a moment and go into some more detail. By the looks of it, you seem to be very new with Delphi. There is a very common issue with beginners, which David doesn't address in his answer, pertaining to creating and freeing these objects. Any and every time you ever call 'Create' on a class, at some point, when you're done with it, you have to also 'Free' that class. Failure to free anything will result in a memory leak, and no one wants that. Freeing is just as simple as creating - until you get into the subject of keeping a list of objects (which you don't need right now).
Let's say you wanted to create a text box (TEdit) control and place it in the center of your form. Now first of all, the Delphi IDE allows you to simply drop these controls in your form, just making sure you know. You don't necessarily need to create/free them yourself, unless there's some special scenario. But doing this is dangerous. For the sake of this example, we're assuming that this TEdit control will be there for the entire duration of your application.
First, you need to declare a variable somewhere for this control. The most reasonable place for this is inside the class where it will be used (in this case, your form which we'll call Form1). When working with variables (aka Fields) in your form, make sure you do not put anything above the private section. Everything above private is intended for auto-generated code by Delphi for anything which has been dropped (and is visual) in your form. Otherwise, any manually created things must go under either private or under public. The public area would be a good place for your control...
type
TForm1 = class(TForm)
private
public
MyEdit: TEdit;
end;
Now that it's declared, we have to create (and free) it. It's a good practice that any and every time you ever create something, that you immediately put the code to also free it before you continue working. Make an event handler for your form's OnCreate and OnDestroy events...
procedure TForm1.FormCreate(Sender: TObject);
begin
MyEdit:= TMyEdit.Create(nil);
MyEdit.Parent:= Self;
MyEdit.Left:= (ClientWidth div 2) - (Width div 2);
MyEdit.Top:= (ClientHeight div 2) - (Height div 2);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if assigned(MyEdit) then MyEdit.Free;
end;
If this object is not created (before creation or after destruction), then you will get an "Access Violation" when trying to use it. This is because your application tries to access an area of the computer's memory which is not allocated or not matching with the type you meant to get.
Well, that's the basics to fix your scenario. However one more thing to show you. Suppose you need to just create an object for a short time, for the duration of a procedure. There's a different approach for this. In your code above, you declared your variable directly within the procedure. This example will show you when it is necessary to do this...
procedure TForm1.Button1Click(Sender: TObject);
var
MyObject: TMyObject;
begin
MyObject:= TMyObject.Create;
try
MyObject.DoSomething;
Caption:= MyObject.GetSomething;
finally
MyObject.Free;
end;
end;
You see, as long as MyObject will only be used in this one call to this procedure, then you can declare it here. But if the object is expected to stay in memory after this procedure is over and done with, then things get more complicated. Again, in your case, stick with putting this in the form's class until you're more familiar with dynamically creating objects.
A final note, as mentioned above, you do have the ability to place the TEdit control directly on your form in design-time without writing your own code. If you do this, you need to remember NOT to try to create or free these ones. This is also the case when Delphi will automatically put the code above the private section - is when there's something which you're not supposed to play with.
I don't think I am eligible to "comment", so I'm phrasing this as an "answer". If you want to resize your runtime components when their parent changes size, take a good look at the Anchors property. It can save you a lot of work.

Delphi & shared datasources

In my app I have different forms that use the same datasource (so the queries are the same too), defined in a common datamodule. Question is, is there a way to know how many times did I open a specific query? By being able to do this, I could avoid close that query without closing it "every where else".
Edit: It's important to mention that I'm using Delphi3 and it is not a single query but several.
The idea is to use the DataLinks property of the TDataSource.
But, as it is protected, you have to gain access to it. One common trick is to create a fake descendant just for the purpose of casting:
type
TDataSourceHack = class(TDataSource);
Then you use it like:
IsUsed := TDataSourceHack(DataSource1).DataLinks.Count > 0;
You can get creative using a addref/release like approach. Just create a few functions and an integer variable in your shared datamodule to do the magic, and be sure to call them..partial code follows:
TDMShared = class(tDataModule)
private
fQueryCount : integer; // set to 0 in constructor
public
function GetQuery : tDataset;
procedure CloseQuery;
end;
function TDMShared.GetQuery : tDataset;
begin
inc(fQueryCount);
if fQueryCount = 1 then
SharedDatsetQry.open;
Result := shareddatasetqry; // your shared dataset here
end;
procedure TDMShared.CloseQuery;
begin
dec(fQueryCount);
if fQueryCount <= 0 then
shareddatasetqry.close; // close only when no refs left.
end;
EDIT: To do this with multiple queries, you need a container to hold the query references, and a way to manipulate them. a tList works well for this. You will need to make appropriate changes for your TDataset descendant, as well as create a FreeAndNil function if you are using an older version of Delphi. The concept I used for this was to maintain a list of all queries you request and manipulate them by the handle which is in effect the index of the query in the list. The method FreeUnusedQueries is there to free any objects which no longer have a reference...this can also be done as part of the close query method, but I separated it to handle the cases where a specific query would need to be reopened by another module.
Procedure TDMShared.DataModuleCreate(Sender:tObject);
begin
dsList := tList.create;
end;
Function TDMShared.CreateQuery(aSql:String):integer;
var
ds : tAdoDataset;
begin
// create your dataset here, for this example using TADODataset
ds := tAdoDataset.create(nil); // self managed
ds.connection := database;
ds.commandtext := aSql;
ds.tag := 0;
Result := dsList.add(ds);
end;
function TDMShared.GetQuery( handle : integer ) : tDataset;
begin
result := nil;
if handle > dsList.count-1 then exit;
if dsList.Items[ handle ] = nil then exit; // handle already closed
result := tAdoDataset( dsList.items[ handle ]);
Inc(Result.tag);
if Result.Tag = 1 then
Result.Open;
end;
procedure TDMShared.CloseQuery( handle : integer );
var
ds : tAdoDataset;
begin
if handle > dsLIst.count-1 then exit;
ds := tAdoDataset( dsList.items[ handle ]);
dec(ds.Tag);
if ds.Tag <= 0 then
ds.close;
end;
procedure TDMShared.FreeUnusedQueries;
var
ds : tAdoDataset;
ix : integer;
begin
for ix := 0 to dsList.Count - 1 do
begin
ds := tAdoDataset(dsLIst.Items[ ix ]);
if ds.tag <= 0 then
FreeAndNil(dsList.Items[ix]);
end;
end;
procedure TDMShared.DataModuleDestroy(Sender: TObject);
var
ix : integer;
begin
for ix := 0 to dsList.count-1 do
begin
if dsLIst.Items[ix] <> nil then
FreeAndNil(dsLIst.Items[ix]);
end;
dsList.free;
end;
Ok, a completely different solution...one that should work for Delphi 3.
Create a new "Descendant Object" from your existing dataset into a new unit, and add some behavior in the new object. Unfortunately I do not have Delphi 3 available for testing, but it should work if you can find the proper access points. For example:
TMySharedDataset = class(tOriginalDataset)
private
fOpenCount : integer;
protected
procedure Internal_Open; override;
procedure Internal_Close; override;
end;
TMySharedDataset.Internal_Open;
begin
inherited Internal_Open;
inc(fOpenCount);
end;
TMySharedDataset.Internal_Close;
begin
dec(fOpenCount);
if fOpenCount <= 0 then
Inherited Internal_Close;
end;
Then just include the unit in your data module, and change the reference to your shared dataset (you will also have to register this one and add it to the palette if your using components). Once this is done, you won't have to make changes to the other units as the dataset is still a descendant of your original one. What makes this all work is the creation of YOUR overridden object.
You could have a generic TDataSet on the shared datamodule and set it on the OnDataChange, using the DataSet property of the Field parameter
dstDataSet := Field.DataSet;
This way, when you want to close the dataset, close the dataset on the datamodule, which is a pointer to the correct DataSet on some form you don't even have to know

Resources