How do I free the memory in this case? - dispose

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).

Related

Delphi AV when using TStringList in a custom class

In Delphi Rio, I have created a class whose purpose is to read a record from a database. This record is purely read only, and after reading, I need to derive some additional properties. My problem has to do with a Stringlist I am wanting to use in my class definition. I have a private class member called fVENDORS_TO_COLORCODE. This is a comma separated string. I am wanting to make a property that is a TStringlist. I am using TStringList.CommaToText to load my value into the Tstringlist. I do this in the Create Constructor. The issue I am having is that while the StringList is valid in the constructor, it is nil outside of the constructor, and I don't know what I am doing wrong. Here is the relevant portions of code.
type
TProfileDef = class(TObject)
private
fNAME: String;
fVENDORS_TO_COLORCODE: String; // incoming comma separated string. Example string: Microsoft,IBM
fVENDORS_TO_COLORCODE_SL : TStringList;
..
public
constructor Create(ProfileName: String);
destructor Destroy; override;
published
property NAME: String read fNAME;
property VENDORS_TO_COLORCODE: String read fVENDORS_TO_COLORCODE;
property VENDORS_TO_COLORCODE_SL : TStringList read fVENDORS_TO_COLORCODE_SL;
..
end;
implementation
destructor TProfileDef.Destroy;
begin
inherited;
fVENDORS_TO_COLORCODE_SL.Free;
end;
constructor TProfileDef.Create(ProfileName: String);
var
fVENDORS_SL: TStringList;
fVENDORS_TO_COLORCODE_SL: TStringList;
TempVendorList : String;
begin
inherited Create;
fName := ProfileName;
.. [Find my record based on ProfileName, and load the DB columns into the private variables]..
// Load the Color Code String into a StringList;
fVENDORS_TO_COLORCODE_SL := TStringList.Create;
fVENDORS_TO_COLORCODE_SL.CommaToText := fVENDORS_TO_COLORCODE;
end;
Within the Constructor, the fVENDORS_TO_COLORCODE_SL stringlist is created, and data is added...
The issue is when I try to use it...
var
TestClass: TProfileDef;
begin
TestClass := TProfileDef.Create('Sample Profile');
// TestClass.Name is valid
// TestClass.VENDORS_TO_COLORCODE_SL is nil, and trying to access gives AV
Somehow I am defining this wrong, but I can't determine what it is is, in order to correct it.
Your class has a private field
fVENDORS_TO_COLORCODE_SL: TStringList;
Your constructor should create a TStringList object and have this variable point to it. I assume that is your intention, at least. However, your constructor has a local variable with the same name, fVENDORS_TO_COLORCODE_SL, so the line
fVENDORS_TO_COLORCODE_SL := TStringList.Create;
indeed creates a TStringList object, but the pointer is saved to this local variable, and the class's field with the same name remains nil.
Solution: Remove the declaration of the local variable in the constructor.
// Load the Color Code String into a StringList;
fVENDORS_TO_COLORCODE_SL := TStringList.Create;
This line here in the constructor is the problem. You have two variables named fVENDORS_TO_COLORCODE_SL. One is a private member of the class declared in the private section of th class declaration, the other is a local variable declared in the var section of the constructor.
Guess which one takes precedence. That's right, the local variable in the constructor. That line initialized the local variable named fVENDORS_TO_COLORCODE_SL, the private class member with the same name is still nil.
As a general rule I preface local variables in a method with an l and only preface class members with an f to avoid just this sort of problem.
Rename your local variables in the constructor like so:
constructor TProfileDef.Create(ProfileName: String);
var
lVENDORS_SL: TStringList;
lVENDORS_TO_COLORCODE_SL: TStringList;
lTempVendorList : String;
begin
Then update your code and rebuild. Things should start to become obvious pretty quickly.

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.

Delphi dynamic form array and data array management

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.