Sorry if there's the same question with mine.
In Delphi i make function like this:
function TModuleDatabase.LoadCountryList():TDictionary<integer, String>;
var
UQ: TUniQuery;
UC: TUniConnection;
CountryList: TDictionary<integer, String>;
begin
CountryList := TDictionary<integer, String>.Create;
UC := UniConnection2;
UQ := TUniQuery.Create(nil);
try
UQ.Connection := UC;
try
UQ.SQL.Clear;
UQ.SQL.Add('SELECT ID,NAME FROM COUNTRY ORDER BY NAME ASC');
UQ.Open;
while not UQ.Eof do
begin
CountryList.Add(UQ.Fields.FieldByName('ID').AsInteger,UQ.Fields.FieldByName('NAME').AsString);
UQ.Next;
end;
Result := CountryList;
except
on E:Exception do
ModuleMsgDialog.WarningMsg(E.Message);
end;
finally
UQ.Close;
UQ.Free;
CountryList.Free;
end;
end;
I separate the function to other DataModule to make me not repeat this function every time in each form. But when i call this funtion from a form:
procedure TCompanyDetailsForm.FormCreate(Sender: TObject);
var
i: Integer;
sItem: String;
CountryList: TDictionary<integer, String>;
begin
PageControl1.ActivePage := AddressTab;
CountryList := ModuleDatabase.LoadCountryList();
for i in CountryList.Keys do
begin
LocationCbbx.Items.AddObject(CountryList.Items[i],TObject(i));
end;
end;
The Problem is at CountryList.Free;. All item in dictionary already freed before use.
If i don't do free, there will make memory leaks.
How the best ways to transfer data before doing free. Or how to free value at other form or unit after call.
Thank you for your help.
You have two main options.
Option 1 – Caller provides an instantiated object
Here you let the caller take responsibility for lifetime. The caller passes in an instantiated object, the callee populates it.
procedure PopulateCountryDict(Countries: TDictionary<Integer, string>);
begin
// populate Countries here
end;
Option 2 – Caller returns a newly instantiated object, which is also populated
This is viable, but the caller has to assume responsibility for the lifetime once the callee returns. It looks like this:
function CreateAndPopulateCountryDict: TDictionary<Integer, string>;
begin
Result := TDictionary<Integer, string>.Create;
try
// populate Result here
except
Result.Free; // until this function returns, we are responsible for lifetime
raise;
end;
end;
The calling code looks like this:
var
Countries: TDictionary<Integer, string>
....
Countries := CreateAndPopulateCountryDict;
try
// do stuff with Countries
finally
Countries.Free;
end;
As an extension to David's answer there is another option using a callback
procedure LoadCountryList( ACallback : TProc<TDictionary<integer,string>> );
var
LCountryList : TDictionary<integer,string>;
begin
// create the instance
LCountryList := TDictionary<integer,string>.Create;
try
// fill the dictionary
// execute the callback
ACallback( LCountryList );
finally
// free the instance
LCountryList.Free;
end;
end;
and then use this in your code
procedure TCompanyDetailsForm.FormCreate(Sender: TObject);
begin
PageControl1.ActivePage := AddressTab;
LoadCountryList(
procedure ( CountryList : TDictionary<integer,string> )
var
i: Integer;
begin
for i in CountryList.Keys do
begin
LocationCbbx.Items.AddObject(CountryList.Items[i],TObject(i));
end;
end );
end;
You should create dictinary in FormCreate method, and destroy or clear where do you need. Not in LoadCountryList function.
Related
I have thoses objects :
TmyObject1=class
public
Status: integer;
end;
TmyObject2=class
public
StatusA: integer;
StatusB: integer;
end;
I have many objects like those one with many status fields declared. I would like to call a function and let the function update the value of one of the status field. quite easy I declare the function like this :
function MyFunction(var AStatus: Integer);
and I call it like this
MyFunction(myObject1.Status);
or for example
MyFunction(myObject2.StatusB);
that good but now my problem arrive, in MyFunction I create a thread and I want to let the possibility to the thread to update also the value of Status. something like this :
function MyFunction(var AStatus: Integer);
begin
MyTread.??ObjectStatus?? := AStatus;
MyTread.start;
end;
procedure TmyThread.execute;
begin
...
??ObjectStatus?? := NewStatus
...
end;
How can I do ? what type must be the TmyThread.??ObjectStatus??. I was thinking to gave to MyTread a pointer address but I m afraid that if memory relocation between the start and the end of the thread that the pointer address could become wrong (Code must work on ios/android/windows/etc.). any other options to solve my problem ?
Instead of a var parameter you can provide getter and setter to your function:
function MyFunction(GetStatus: TFunc<Integer>; SetStatus: TProc<Integer>);
begin
MyThread.GetStatus := GetStatus;
MyThread.SetStatus := SetStatus;
MyThread.start;
end;
procedure TmyThread.execute;
begin
...
OldStatus := GetStatus;
SetStaus(NewStatus);
...
end;
Calling this function requires some anonymous methods now:
MyFunction(
function: Integer
begin
result := myObject1.Status;
end,
procedure(Arg: Integer)
begin
myObject1.Status := Arg
end);
Of course you have to make sure that myObject1 is available during the thread execution.
Delphi XE6 - I have a Unit (EMAIL1.pas) which does related processing. This is meant to be a standalone unit I can incorporate into multiple programs. My initial procedure is called GetDetailsFromEmailAddress. It has two parameters, an email address which I lookup and a "group of data" which will get updated, currently defined as a var. This can be a record or a class, I don't really care. It is just a group of related strings (firstname, last name, city, etc). Let's call this EmpRec.
My challenge is that this procedure creates an instance of a class (JEDI VCL HTMLParser) which uses a method pointer to call a method (TableKeyFound). This routine needs to update EmpRec. I do not want to change this code (HTMLPArser routine) to add additional parameters. There are several other procedures that my UNIT creates. All of them need to read/update EmpRec. How do I do this?
I need a way to "promote" the variable EmpRec which is passed in this one routine (GetDetailsFromEmailAddress) to be GLOBAL within this UNIT so that all the routines can access or change the various elements. How do I go about this? I do NOT really want to have to define this as a GLOBAL / Application wide variable.
Code sample below. So.. How does the routine TableKeyFoundEx get access to the EmpRec variable?
procedure GetDetailsFromEmailAddress(Email: string; var EmpRec: TEmpRec);
begin
...
// Now create the HTML Parser...
JvHtmlParser1 := TJvHTMLParser.Create(nil);
// On event KeyFoundEx, call Parsehandlers.TableKeyFoundEx;
JvHtmlParser1.OnKeyFoundEx := ParseHandlers.TableKeyFoundEx;
...
end.
procedure TParseHandlers.TableKeyFoundEx(Sender: TObject; Key, Results, OriginalLine: String; TagInfo: TTagInfo;
Attributes: TStrings);
begin
..
// NEED ACCESS to EmpRec here, but can't change procedure definition
end;
There are two different ways I would approach this:
use the parser's Tag property:
procedure GetDetailsFromEmailAddress(Email: string; var EmpRec: TEmpRec);
begin
...
JvHtmlParser1 := TJvHTMLParser.Create(nil);
JvHtmlParser1.OnKeyFoundEx := ParseHandlers.TableKeyFoundEx;
JvHtmlParser1.Tag := NativeInt(#EmpRec);
...
end;
procedure TParseHandlers.TableKeyFoundEx(Sender: TObject; Key, Results, OriginalLine: String; TagInfo: TTagInfo; Attributes: TStrings);
var
EmpRec: PEmpRec; // assuming PEmpRec = ^TEmpRec
begin
EmpRec := PEmpRec(TJvHTMLParser(Sender).Tag);
...
end;
use a little TMethod hack to pass the record DIRECTLY to the event handler:
// Note: this is declared as a STANDALONE procedure instead of a class method.
// The extra DATA parameter is where a method would normally pass its 'Self' pointer...
procedure TableKeyFoundEx(Data: Pointer: Sender: TObject; Key, Results, OriginalLine: String; TagInfo: TTagInfo; Attributes: TStrings);
var
EmpRec: PEmpRec; // assuming PEmpRec = ^TEmpRec
begin
EmpRec := PEmpRec(Data);
...
end;
procedure GetDetailsFromEmailAddress(Email: string; var EmpRec: TEmpRec);
var
M: TMethod;
begin
...
JvHtmlParser1 := TJvHTMLParser.Create(nil);
M.Code := #TableKeyFoundEx;
M.Data := #EmpRec;
JvHtmlParser1.OnKeyFoundEx := TJvKeyFoundExEvent(M);
...
end;
In addition to the two options that Remy offers, you could derive a sub-class of TJvHTMLParser.
type
PEmpRec = ^TEmpRec;
TMyJvHTMLParser = class(TJvHTMLParser)
private
FEmpRec: PEmpRec;
public
constructor Create(EmpRec: PEmpRec);
end;
....
constructor TMyJvHTMLParser.Create(EmpRec: PEmpRec);
begin
inherited Create(nil);
FEmpRec := EmpRec;
end;
When you create the parser, do so like this:
procedure GetDetailsFromEmailAddress(Email: string; var EmpRec: TEmpRec);
var
Parser: TMyJvHTMLParser;
begin
Parser := TMyJvHTMLParser.Create(#EmpRec);
try
Parser.OnKeyFoundEx := ParseHandlers.TableKeyFoundEx;
....
finally
Parser.Free;
end;
end.
And in your OnKeyFoundEx you cast Sender back to the parser type to gain access to the record:
procedure TParseHandlers.TableKeyFoundEx(Sender: TObject; ...);
var
EmpRec: PEmpRec;
begin
EmpRec := (Sender as TMyJvHTMLParser).FEmpRec;
....
end;
I have 5 forms created at design time. I need to dynamically create an instance of each form and put on a tab.
My question: If the form names are in an array of strings and I call my procedure like this:
ShowForm(FormName[3]);// To show the 3rd form on a tab page.
How can I define and create the new instance for each form?
This is what I have for now:
procedure TForm1.ShowFormOnTab(pProcName:String);
var
NewForm: TfrmSetupItemCategories;//***HERE IS MY PROBLEM***
NewTab: TTabSheet;
FormName: String;
begin
NewTab := TTabSheet.Create(PageControl1);
NewTab.PageControl:= PageControl1;
NewTab.Caption:='hi';
PageControl1.ActivePage := NewTab;
if pProcName='ProcfrmSetupItemCategories' Then
begin
NewForm:=TfrmSetupItemCategories.Create(NewTab);
NewTab.Caption := NewForm.Caption;
end;
if pProcName='ProcfrmZones' Then
begin
NewForm:=TfrmZones.Create(NewTab);
NewTab.Caption := NewForm.Caption;
end;
.
.
.
end;
the line that reads "HERE IS MY PROBLEM" is where I need help. I can't reuse NewForm as a variable with a second form in this way...
Note: My problem is NOT the tab. Rather it's creating a new instance of the form using the same variable name.
Declare the NewForm variable as TForm:
var
NewForm: TForm;
begin
NewForm := TMyForm.Create(Tab1); //compiles OK
NewForm := TMyOtherForm.Create(Tab2); //also compiles OK
end;
I'm assuming TMyForm and TMyOtherForm both are derivatives of TForm.
DRY
You can also reduce your repeating code using a class reference variable, like this:
procedure TForm1.ShowFormOnTab(pProcName:String);
var
NewForm: TForm;
ClassToUse: TFormClass;
NewTab: TTabSheet;
FormName: String;
begin
NewTab := TTabSheet.Create(PageControl1);
NewTab.PageControl:= PageControl1;
NewTab.Caption:='hi';
PageControl1.ActivePage := NewTab;
if pProcName='ProcfrmSetupItemCategories' then
ClassToUse := TfrmSetupItemCategories
else if pProcName='ProcfrmZones' then
ClassToUse := TfrmZones
else
ClassToUse := nil;
if Assigned(ClassToUse) then
begin
NewForm := ClassTouse.Create(NewTab);
NewTab.Caption := NewForm.Caption;
//if you access custom properties or methods, this is the way:
if NewForm is TfrmZones then
TfrmZones(NewForm).ZoneInfo := 'MyInfo';
end;
end;
Register your classes and then create the forms from a string
As Sir Rufo points in his comment, you can even go further registering your classes (I'm not sure if this can be done in Lazarus, that exercise is up to you).
First, register the form classes you want to instantiate from the class name, previous to any call to your ShowFormOnTab method, for example:
procedure TMainForm.FormCreate(Sender: TObject);
begin
RegisterClass(TfrmSetupItemCategories);
RegisterClass(TfrmZones);
//and other classes
end;
Then, you can change the code to get the class reference from the class name string:
procedure TForm1.ShowFormOnTab(pProcName:String);
var
NewForm: TForm;
ClassToUse: TFormClass;
ClassNameToUse: string;
NewTab: TTabSheet;
FormName: String;
begin
NewTab := TTabSheet.Create(PageControl1);
NewTab.PageControl:= PageControl1;
NewTab.Caption:='hi';
PageControl1.ActivePage := NewTab;
//get rid of 'Proc' and add the T
//or even better, pass directly the class name
ClassNameToUse := 'T' + Copy(pProcName, 5, MaxInt);
ClassToUse := TFormClass(FindClass(ClassNameToUse));
if Assigned(ClassToUse) then
begin
NewForm := ClassTouse.Create(NewTab);
NewTab.Caption := NewForm.Caption;
//if you access custom properties or methods, this is the way:
if NewForm is TfrmZones then
TfrmZones(NewForm).ZoneInfo := 'MyInfo';
end;
end;
That way, the code remains the same for any number of classes.
For more info about this, take a look at Creating a Delphi form from a string in delphi.about.com.
Declare your variable as an ancestor type:
var
NewForm: TForm;
or
var
NewForm: TCustomForm;
Drawback: you'll need to cast the variable to the specific class if you want to call any methods of your form that you have declared yourself.
Use a 'soft' cast if you want to have the compiler check that NewForm is actually a TMyForm at runtime:
(NewForm as TMyForm).MyMethod;
When you are absolutely sure that NewForm is a TMyForm (like when you just created it), you can also use a 'hard' cast:
TMyForm(NewForm).MyMethod;
With registered classes, in the initialization of the used forms, you could shorten it to
Function CreateAndDock(pc:TPageControl;const FormName:String):Boolean;
begin
Result := false;
if Assigned(GetClass(FormName)) and GetClass(FormName).InheritsFrom(TCustomForm) then
With TFormClass( GetClass(FormName)).Create(pc.Owner) do
begin
ManualDock(pc);
Show;
Result := true;
end;
end;
procedure TForm4.Button1Click(Sender: TObject);
begin
ShowMessage(IntToStr(Integer(CreateAndDock(pagecontrol1,'TDockForm'))));
ShowMessage(IntToStr(Integer(CreateAndDock(pagecontrol1,'TNotExists'))));
end;
This is just a very simple question to which i can't find a good clear answer to. I don't quite have the time to read all the documentation for this since i'm in a time crunch.
But here it is.
I have made a new class on top of my TForm class like so:
Bucket = Class
glass: Integer;
steel: Integer;
End;
I then create a couple of objects in a method which belongs to TForm1
procedure TForm1.getMarbles;
var
objPlastic: Bucket;
objAlu: Bucket;
begin
// Initialize objects
objPlastic := Bucket.Create;
objAlu := Bucket.Create;
// Get Values from edtBox
val(Edit1.Text, objPlastic.steel, code);
val(Edit2.Text, objAlu.steel, code);
val(Edit3.Text, objPlastic.glass, code);
val(Edit4.Text, objAlu.glass, code);
end;
My problem is that I don't know how to use these objects in other methods. I tried defining them in every way i know so far in the other methods I want to use them in, but I can't get it to work.
Here is the method and what I have it currently set to (which returns 0 all the time):
procedure TForm1.marbleDrop(kind: string);
var
objPlastic: Bucket;
I: Integer;
begin
objPlastic := Bucket.Create;
if kind= 'plastic' then // the function is receiving this parameter
begin
for I := 0 to objPlastic.glass do
begin
showmessage(inttostr(objPlastic.glass)); //returns 0
end;
end;
end;
Sorry for this kind of question, but i couldn't find a better way.
BTW, this is a simplified version of the code I am using. I did my best to get out any typos since it's a translation of what I am actually using, but it's mainly about the idea. I don't have typos in my code in delphi.
In other to access the objects across methods, you have to either:
declare the objects as members of the Form class:
type
TForm1 = class(TForm);
...
private
objPlastic: Bucket;
objAlu: Bucket;
...
end;
procedure TForm1.getMarbles;
begin
// Initialize objects
if objPlastic = nil then objPlastic := Bucket.Create;
if objAlu = nil then objAlu := Bucket.Create;
// Get Values from edtBox
objPlastic.steel := StrToIntDef(Edit1.Text, 0);
objAlu.steel := StrToIntDef(Edit2.Text, 0);
objPlastic.glass := StrToIntDef(Edit3.Text, 0);
objAlu.glass := StrToIntDef(Edit4.Text, 0);
end;
procedure TForm1.marbleDrop(kind: string);
begin
if (kind = 'plastic') and (objPlastic <> nil) then
begin
ShowMessage(IntToStr(objPlastic.glass));
end;
end;
pass them as parameters of the methods themselves:
procedure TForm1.getMarbles(objPlastic, objAlu: Bucket);
begin
// Get Values from edtBox
if objPlastic <> nil then
begin
objPlastic.steel := StrToIntDef(Edit1.Text, 0);
objPlastic.glass := StrToIntDef(Edit3.Text, 0);
end;
if objAlu <> nil then
begin
objAlu.steel := StrToIntDef(Edit2.Text, 0);
objAlu.glass := StrToIntDef(Edit4.Text, 0);
end;
end;
procedure TForm1.marbleDrop(objWhichKind: Bucket);
begin
if objWhichKind <> nil then
begin
ShowMessage(IntToStr(objWhichKind.glass));
end;
end;
procedure TForm1.someMethod();
var
objPlastic: Bucket;
begin
objPlastic := Bucket.Create;
getMarbles(objPlastic, nil);
marbleDrop(objPlastic);
objPlastic.Free;
end;
Of course it returns zero. It is another object. You should pass it as you pass any other parameter variable. What you made is similar to
procedure TForm1.Drop1(kind: string);
begin
marbleDrop(); // here kind = "staal"
end;
procedure TForm1.marbleDrop();
var
kind: string;
begin
if kind = 'plastic' then // it is not !!! why ???
begin
....
end;
end;
You also has another problem - Memory leak
val(Edit4.Text, objAlu.glass, code);
end;
You just created two objects - and allocated Heap memory for them.
But you did not freed them. That is garbage left and it will grow and grow and grow - until the program would exhaust all Windows memory and be killed.
If you want to use memory without any accuracy and without "wasting" your time on thinking and learning - you'd better user some managed language running in virtual machine, like PHP, Python, Java and other JVM-based, C# and other .NEt-based.
To make good Delphi code you should have at least some understanding what you CPU does and why.
Specifically in your code you'd better
use records instead of classes
pass them as const- or var-parameters to avoid redundant copying.
Like that:
type TBucket = Record glass, steel: Integer; End;
type TForm1 = class (TForm)
.....
private
var objPlastic, objAlu: TBucket;
(* making variables more global: now they are form-local not function-local *)
......
procedure TForm1.getMarbles;
begin
objPlastic.steel := StrToIntDef(Edit1.Text, 0);
objAlu.steel := ...
Self.objPlastic.glass ... (* adding Self - just for clarity where those variable are taken from *)
Self.objAlu.glass ....
end;
procedure TForm1.marbleDrop(kind: string);
var
I: Integer;
begin
if kind = 'plastic' then // the function is receiving this parameter
begin
for I := 0 to Self.objPlastic.glass do
begin
showmessage(inttostr(objPlastic.glass));
//getting via common parent context - TForm1 object, referenced as Self pseudo-variable
marbleTell(objPlastic); // passing as parameter
end;
end;
end;
procedure TForm1.marbleTell(const arg: TBucket);
// do not forget to use const to pass variable by-reference not by-value
begin
showmessage(inttostr(arg.glass)); // getting via argument
end;
I have a form (form2) and I implemented the following PUBLIC method:
function ShowInterface(i:integer):boolean;
This form is in a package that will be DYNAMIC LOADED. Now I want to instantiate this form (form2) and execute the method above.
Important: I can't reference form2's unit in form1.
I tryed this code, but it never finds "ShowInterface" pointer (returns nil).
procedure TfrmForm1.Button1Click(Sender: TObject);
var
PackageModule: HModule;
AClass: TPersistentClass;
ShowInterface: function (i:integer):boolean;
frm: TCustomForm;
begin
PackageModule := LoadPackage('form2.bpl');
if PackageModule <> 0 then
begin
AClass := GetClass('TfrmForm2');
if AClass <> nil then // <<-- FINE!! IT FINDS OUT 'TfrmForm2' in 'form2.bpl')
begin
frm := TComponentClass(AClass).Create(Self) as TCustomForm;
ShowInterface := frm.MethodAddress('ShowInterface'); // <<-- HERE!! ALLWAYS RETURNS "NIL"
if #ShowInterface <> nil then
ShowInterface(1);
// but if I call frm.Show, it works fine. frm is "loaded"!!!
frm.Free;
end;
DoUnloadPackage(PackageModule);
end;
end;
Thanks in advance.
MethodAddress only works for published methods. Move it to the published section and it should work.
Or, if you have Delphi 2010, the extended RTTI offers a way to find public methods by name. (Or other visibility levels, if you change it from the default.)
As Mason and TOndrej said, I have to put the method in published section. (Thank you!)
But, some fixes were needed:
procedure TfrmForm1.Button1Click(Sender: TObject);
type
TShowInterface = function(i:integer):boolean of object;
var
PackageModule: HModule;
AClass: TPersistentClass;
Routine: TMethod;
ShowInterface : TShowInterface;
frm: TCustomForm;
begin
PackageModule := LoadPackage('form2.bpl');
if PackageModule <> 0 then
begin
AClass := GetClass('TfrmForm2');
if AClass <> nil then
begin
frm := TComponentClass(AClass).Create(Self) as TCustomForm;
Routine.Data := Pointer(frm);
Routine.Code := frm.MethodAddress('ShowInterface');
if Assigned(Routine.Code) then
begin
ShowInterface := TShowInterface(Routine);
ShowInterface(1); // showinterface executes a "ShowModal", so we can "free" form after this.
end;
frm.Free;
end;
DoUnloadPackage(PackageModule);
end;
end;
In D2007 and some earlier versions, that only works with published methods, or extended RTTI: {$METHODINFO ON}. I haven't used D2010 yet; it seems to have a new RTTI system which has been extended a lot.