Delphi dynamic form array and data array management - forms

I have to solve next problem:
Form23:
public
{ Public declarations }
FormsArray : array of TForm24;
end;
Procedure Create_form;
begin
SetLength(FormsArray, Length(FormsArray)+1);
FormsArray[Length(FormsArray)-1] := TForm24.Create(Self);
end;
Form24:
public
end;
var
UniqueValue : Array of ShortString;
Procedure Fill_Unique;
var
tmp1 : Longint;
begin
SetLength(UniqueValue, 256);
for tmp1 := 0 to Length(UniqueValue)-1 do
begin
UniqueValue[tmp1] := IntToStr(tmp1);
end;
end;
Procedure OnButtonClick(Sender);
begin
Fill_Unique;
end;
When i have one form Form24 and i fill with some values, then it is ok.
When i have two forms Form24 (FormsArray[0] and FormsArray[1]) and i change UniqueValue in one form, then i have that values in two forms.
i.e.
I create FormsArray[0] and FormsArray[1]
When I click button on FormsArray[0]:
FormsArray[0] - UniqueValue[...] = '1,2,3,4,5,6,7,8,9...';
FormsArray[1] - UniqueValue[...] = '1,2,3,4,5,6,7,8,9...';
When I click button on FormsArray[1]:
FormsArray[0] - UniqueValue[...] = '1,2,3,4,5,6,7,8,9...';
FormsArray[1] - UniqueValue[...] = '1,2,3,4,5,6,7,8,9...';
When i change code to:
Form24:
public
UniqueValue : Array of ShortString;
end;
and I click button on FormsArray[1] then i have:
FormsArray[0] - UniqueValue[...] = '';
FormsArray[1] - UniqueValue[...] = '';
UniqueValue is empty.
I need to have independent arrays in every forms Form24 i have created (different UniqueValue in every forms i create).
How to do this? What i do wrong?
Thanks for any help.
SOLVED !
I got -1 for solution what i write here. Then will be no solution. Search for yourself.

It sounds like you've already solved it. Make UniqueValue be a member of the form class. Put it in the public section of the class declaration, for example.
type
TForm24 = class(TForm)
public
UniqueValue: array of string;
end;
The first code you showed has the array as a global variable, which is of course shared by all instances of your form class, as well as everything else in your program. You're probably confused thinking that anything declared in the same file as the form class somehow "belongs to" that class, but if you think that, you're mistaken. To make something belong to a class, it should be declared inside that class, not just somewhere in the same unit file.
It looks like you'll probably want to make Fill_Unique and OnButtonClick be members of the form class, too. In the code you showed, they're standalone procedures, so they have no reference to whatever form they're supposed to work on. That means they can't refer to UniqueValue because they won't know which TForm24 instance's field to operate on.

Your UniqueValue array is declared globally, so multiple form instances are going to access the same array in memory. Moving the array into the public section of your Form class allows each instance of that Form to have its own array that is independant of other Form instances. Just make sure you remove the global array.

Related

Change a state of a control on another independent form, in the same application

Situation:
An application with 2 forms and 1 DataModule unit
Both units containing the forms, have the DataModule in "uses". Unit containing the Form1 has the unit containing the Form2 in "uses"
Form1 has two buttons on it
Both forms are shown at the same time, Form1 is NOT a parent of the Form2, nor vice versa
What I need:
I need to be able to control the state of the two buttons on Form1, by actions on the Form2.
What I did:
I used a timer on the Form1, that continuously (250ms) checks a variable in the DataModule and based on it's value, it changes the state of the buttons on the Form1. Then I modify the variable in DataModule from the Form2.
The variable in DataModule:
public
BtnToDisable: string;
The Timer on Form1:
procedure TForm1.Timer1Timer(Sender: TObject);
var
i: integer;
begin
if Datamodule4.BtnToDisable = 'All' then
for i := 0 to Form1.ControlCount - 1 do
begin
if Form1.Controls[i].ClassType = TButton then
Form1.Controls[i].Enabled := False;
end
else if Datamodule4.BtnToDisable = 'None' then
for i := 0 to Form1.ControlCount - 1 do
begin
if Form1.Controls[i].ClassType = TButton then
Form1.Controls[i].Enabled := True;
end
else if Datamodule4.BtnToDisable = 'Button1' then
for i := 0 to Form1.ControlCount - 1 do
begin
if Form1.Controls[i].Name = 'Button1' then
Form1.Controls[i].Enabled := False;
end
else if Datamodule4.BtnToDisable = 'Button2' then
for i := 0 to Form1.ControlCount - 1 do
begin
if Form1.Controls[i].Name = 'Button2' then
Form1.Controls[i].Enabled := False;
end;
end;
The action on the Form2:
DataModule4.BtnToDisable := 'All' // All, None, "name"
DataModule4.BtnToDisable := 'Button1' // All, None, "name"
...
The problem:
Well, it works, but in a more complicated scenario, with a lot of buttons to be enabled/disabled, more forms, and more possible combinations (enable three specific buttons, disable all the other, etc.), it gets complicated, and hard to maintain. Is there a way to access those buttons on the Form1 directly, considering, that I don't have the Form1 object, accessible from the Form2?
You always have access to every Form object, even if you don't use their units directly. All created TForm objects are stored in the Forms[] property of the global Screen object in the Forms unit.
That being said, instead of using a TTimer, I would suggest using a TAction(List).OnUpdate or TApplication(Events).OnIdle event handler to perform the Button updates. Those events are triggered whenever the main UI message loop finishes processing messages from the main message queue and goes idle waiting for new messages to arrive. When you start dealing with multiple Forms that need updating, you can give each Form its own TAction(List) or TApplicationEvents.
I would also suggest defining an Enum to represent each button, and then change the BtnToDisable variable to be a Set of those Enum values. This way, you can disable any combination of Buttons you want. Assign those Enum values to each Button's Tag property, or use a Dictionary, or a class helper, or any other means you want to associate an Enum value with a Button object. Then, when an Update is needed, you can simply loop through the Buttons, and if a given Button's associated Enum value is in the Set then disable that Button, otherwise enable it. No need to check Name properties at all.
And lastly, I would suggest having a Form store its Buttons in an array or T(Object)List that you can loop through, instead of looping through the Form's Controls[] property every time.

ShowModal for an associated form

I have a component with bellow properties:
property Form: TForm read FForm write SetForm
property BtnOK: TButton read FBtnOK write SetBtnOK
and a procedure behind like this:
procedure Execute_FormShowModal;
I would like to open the associated form (eg. FormUser) when the Execute_FormShowModal is executed.
I would like to mention that, the associated form is already defined and exist, but is not created.
Is there any possibility to do this?
procedure TMyComp.Execute_FormShowModal;
var
frm: TForm;
begin
frm:= TForm(FForm.ClassName).Create(FParentForm); //Access Violation...
//... here I would like to play also this the elements from this form
//like: BtnOK.Enabled:= False;
frm.ShowModal;
frm.Free;
end;
You added in the comments that you are setting FForm to be equal to a valid existing form. If so, you may not need to create anything:
procedure TMyComp.Execute_FormShowModal;
var
frm: TFormUser;
begin
frm:= TFormUser(FForm);
frm.BtnOK.Enabled:=False;
frm.ShowModal;
//frm.Free;
end;
This assumes that this valid instance you are referring to is declared
type
TFormUser = class(TForm)
BtnOK : TButton;
// etc...
end;
If you trying to make a copy of the form you might use this:
procedure TMyComp.Execute_FormShowModal;
var
frm: TFormUser;
begin
frm:= TFormUser(TFormClass(FForm.ClassType).Create(FParentForm));
// which is no different than:
frm:= TFormUser.Create(FParentForm));
frm.BtnOK.Enabled:=False;
frm.ShowModal;
frm.Free;
end;
If you want to manipulate the controls on the form (i.e. BtnOK), then you need to know the class type of the Form (TFormUser in this case). So it is contradictory to be required to know the exact class type of the form and yet want to instanciate a form from a design-time established type.
Since you may be trying to instanciate the form without "knowing" its absolute type, your FForm property should be the class for the form.
Assuming you weren't publishing the "Form" property in your component, I would make these changes to your component:
TMyComp = class(TComponent)
FFormClass : TFormClass;
procedure SetFormClass(Value : TFormClass);
property FormClass: TFormClass read FFormClass write SetFormClass;
procedure Execute_FormShowModal;
end;
The initialization code you referred to might look like this:
begin
// .....
//MyComp.Form := FormUser1;
MyComp.FormClass := TFormUser;
// .....
end;
And then "Execute_FormShowModal" becomes:
procedure TMyComp.Execute_FormShowModal;
var
frm: TForm;
begin
// Check that FFormClass is not nil and perform some alternate
// action.
// if FFormClass = nil then ......
//
frm:= FFormClass.Create(FParentForm);
frm.ShowModal;
frm.Free;
end;
Of course, you may also want to add some code to check if FFormClass is nil and preform some alternate behavior if so, like raise an exception or showing some message or even instanciating a default form.
If you were publishing the Form property then it won't be able to handle the case where your FForm field value is nil because you don't know or have a specific class type to instanciate the Form. That is:
frm:= TFormClass(FForm.ClassType).Create(FParentForm);
will simply display a blank, empty form.
If you want to publish this property, you could try making it a string type that carries the name of the form class you want to instanciate and then use RTTI to find the class:
uses RTTI;
TMyComp = class(TComponent)
FFormClassName : string;
procedure SetFormClassName(const Value : string);
property FormClassName: string read FFormClassName write SetFormClassName;
procedure Execute_FormShowModal;
end;
procedure TMyComp.Execute_FormShowModal;
var
frmCls : TFormClass;
frm: TForm;
RTTI : TRTTIContext;
RTTIType : TRTTIType;
begin
frmCls := nil;
for RTTIType in RTTI.GetTypes do
begin
if (RTTIType.Name = FFormClassName) and (RTTIType.TypeKind = tkClass) then
begin
if RTTIType.Handle.TypeData.ClassType.InheritsFrom(TForm) then
begin
frmClass := TFormClass(RTTIType.Handle.TypeData.ClassType);
break;
end;
end;
end;
// Check that frmCls is not nil and perform some alternate
// action.
// if frmCls = nil then ......
//
frm:= frmCls.Create(FParentForm);
frm.ShowModal;
frm.Free;
end;
Is there any possibility to do this?
Yes
Try something like this
uses uFForm; // Add the unit name that defined the associated form to your (TMyComp) unit uses clause
procedure TMyComp.Execute_FormShowModal;
begin
with TFForm.Create(Self) do //TFForm is the child form
begin
//... here I would like to play also this the elements from this form
BtnOK.Enabled:= False;
Show;
end;
end;
First problem is that you are trying to typecast the ClassName to a class type that you're trying to instantiate. Instead you want to work with the metaclass that can be obtained by the ClassType method from an object instance. The next issue is that with such metaclass you need to typecast to a metaclass, not to a class, so instead of typecasting to TForm class cast to its metaclass TFormClass.
To the next part of your question, if you can generally access a specific class members of an object that is declared as a common class ancestor, no, that is not possible. As a workaround you must determine the object class type and access it by typecasting to that class, or use RTTI which is more difficult.
Try something like this:
procedure TMyComp.Execute_FormShowModal;
var
frm: TForm;
begin
frm := TFormClass(FForm.ClassType).Create(FParentForm);
{ to acess the class specific members you will have to typecast to a
specific class (or use RTTI, which is even more difficult) }
if frm is TMyForm then
TMyForm(frm).BtnOK.Enabled := False;
frm.ShowModal;
frm.Free;
end;

Delphi, find form by name

how can I find form by name? On this form I have Edit (TEdit) and i would like to write something in this TEdit (its name e.g.: adress) but I have only form name.
Can you help me?
There is a simpler way of finding a form by name. Since all of auto-created form objects become owned by Application object and TApplication inherits from TComponent, you can either iterate thru Application.Components array property or use Application.FindComponent method.
var
Form: TForm;
begin
Form := Application.FindComponent('LostForm1') as TForm;
if Assigned(Form) then
Form.Show
else
{ error, can't find it }
Note that FindComponent is case-insensitive.
This answer assumes you are making a VCL application. I don't know if FireMonkey has a similar solution.
All forms are added to the global Screen (declared in Vcl.Forms) object when they are created. Thus you can make a little helper function like this
function FindFormByName(const AName: string): TForm;
var
i: Integer;
begin
for i := 0 to Screen.FormCount - 1 do
begin
Result := Screen.Forms[i];
if (Result.Name = AName) then
Exit;
end;
Result := nil;
end;
You can use the FindWindow function if you know the form title or the class name of the form.

Error When Editing Member Variable in Class

I'm trying to use a class in my program.
TStack = Class
Public
constructor Create; Overload;
Procedure Add(Frm:TForm);
Procedure Remove();
Procedure Do_Close;
Private
List : Array[1..Max_Forms] of Rec;
Count : Byte;
End;
Constructor:
constructor TStack.Create;
begin
Self.Count := 0;
end;
Procedure TStack.Add(Frm:TForm);
begin
Inc(Self.Count);
List[Count].Form := #Frm;
List[Count].Width := Frm.Width;
List[Count].Height := Frm.Height;
List[Count].left := Frm.Left;
List[Count].Top := Frm.Top;
end;
I can't change value of Count variable! It cause Run-Time error : Access violation....Write of address 000001E4
What's the problem?!
FOR MORE INFORMATION:
I'm trying to store a pointer to each form in a structure like this :
Rec = Record
Form : ^TForm;
Maximized : Boolean;
Width,
Height,
left,
Top : Integer;
End;
And then
Procedure TStack.Do_Close;
var
i : integer;
MyForm : TForm;
begin
i := .....some code here.......;
MyForm := #List[i].Form;
ShowMessage('I will close '+MyForm.Caption);
MyForm.Close;
end;
AND call constructor like this to initialize 'Count':
Stack.Create;
As described in comments you are attempting to create the object like this:
var
Stack: TStack;
....
Stack.Create;
This is a classic mistake, and one that we've all made. You are calling a method on an uninitialized instance variable.
In order to instantiate a class you need to write this:
Stack := TStack.Create;
On top of that I have the following comments:
Use zero-based indexing for arrays. That's the convention everywhere in Delphi apart from anachronistic strings. And even that is changing in newer versions.
Don't use static arrays for a stack unless you have a good reason for doing so. You'll just run the risk of running out of space. Or allocating more memory than you need. Use a dynamic array.
Rather than a dynamic array, you could use TList<T>.
Even so, one wonders why you are making your own stack class when there is the perfectly good TStack<T>.
You store the address of a local variable in your stack. In TStack.Add you add #Frm into the container. As soon as TStack.Add returns, #Frm is meaningless. That's because Frm is a local variable whose life ends when the function that owns it returns. I think you want to take a copy of Frm.
Picking up item 5 in more detail, your record is declared like this:
Rec = Record
Form : ^TForm;
Maximized : Boolean;
Width,
Height,
left,
Top : Integer;
End;
It is a mistake to use ^TForm. That is a pointer to a variable holding a pointer to an object. That's two levels of indirection, one too many. You must declare the Form field to be of type TForm. I suggest you revise the way Delphi object reference variables work. Delphi classes are what is known as reference types. A variable of type TMyClass where TMyClass is class(...) is already a pointer. The language automatically de-references the pointer when you use the . operator to access members.

How do I free the memory in this case?

I have this type definition:
KMenuClass = class
private
pMenuID: String;
public
property MenuID: String read pMenuID;
constructor Create(const paraMenuID: String);
end;
Then I do this:
constructor KMenuClass.Create(const paraMenuID: String);
begin
pMenuID:= paraMenuID;
end;
procedure TfrmPermissions.FormCreate(Sender: TObject);
begin
clSetup.Items.AddObject('ant', KMenuClass.Create('a007'));
clSetup.Items.AddObject('cat', KMenuClass.Create('x123'));
end;
The above two entries aer only test entries. I'm really looking at about 50 items.
clSetup is of type TCheckListBox and I'm basically storing a second string along with the Items string.
I can output a specific item like this:
Var
ThisItem : KMenuClass;
.
.
.
ThisItem := clSetup.Items.Objects[clSetup.ItemIndex] as KMenuClass;
ShowMessage(clSetup.Items[clSetup.ItemIndex] + ' : ' + ThisItem.MenuID);
But how do I dispose of my objects associated with each list item? Does it auto-dispose when I close the form?
Thanks!
Manually iterating over items.objects, items.count times and freeing each object is probably the only way, since the property is probably tstrings, and doesn't have an ownsobjects property (that only comes in at tstringlist).