Currently when i click on a button it will create some shapes on a new form. Once i close the new form how can i destroy the shapes it made.
I can add more info if needed but was hopeing there was a simple way to destroy all TMachine instances when the form closed.
TMachine is a TShape Class
procedure TFLayout1.GetClick(Sender: TObject);
var
azone: string;
adept: string;
machine : TMachine;
begin
fdb.count := 0; //keeps track of number of machines in zone
azone := MyDataModule.fDB.GetZone(Name); //gets name of zone
adept := TButton(Sender).Name; //gets name of dept
fdeptlayout.ListBox1.Clear;
fdeptlayout.show;
with fdeptlayout.ADOQuery1 do
begin
sql.Clear;
sql.BeginUpdate;
sql.Add('SELECT');
sql.Add(' *');
sql.Add('FROM');
sql.Add(' `MList`');
sql.Add('WHERE `Zone` = :myzone ');
sql.Add(' AND `Dept` = :mydept');
sql.EndUpdate;
parameters.ParamByName('myzone').Value := azone;
parameters.ParamByName('mydept').Value := adept;
open;
end;
//gets number of machines in total
while not fdeptlayout.ADOQuery1.Eof do
begin
fdb.count := fdb.count+1;
fdeptlayout.ADOQuery1.Next;
end;
//restarts back at first query
fdeptlayout.ADOQuery1.First;
//clears the last x value
fdb.LastX :=0;
//creates the shape
while not fdeptlayout.ADOQuery1.Eof do
begin
machine := MachineShape.TMachine.Create(self);
machine.Parent := fdeptlayout;
machine.PlaceShape(44,44,'CM402','first','123/33/123');
fdeptlayout.ListBox1.Items.Add(fdeptlayout.ADOQuery1.FieldByName('Name').AsString);
fdeptlayout.ADOQuery1.Next;
end;
end;
TMachine Class
unit MachineShape;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, extctrls,myDataModule,Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TMachine = class(TShape)
private
{ Private declarations }
public
{ Public declarations }
procedure PlaceShape(sizeW,sizeH :integer; name, order,asset : string);
end;
implementation
Procedure TMachine.PlaceShape(sizeW,sizeH :integer; name, order,asset : string);
begin
self.width := sizeW;
self.height := sizeH;
self.top := 136;
self.left := MyDataModule.fDB.LastX +2;//set left
MyDataModule.fDB.lastx := left + sizeW;
showmessage(inttostr(mydatamodule.fDB.LastX));
end;
end.
FDeptLayout
unit DeptLayout;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls,mydatamodule, Vcl.Forms, Vcl.Dialogs, Data.DB, Data.Win.ADODB, Vcl.StdCtrls,
Vcl.ExtCtrls;
type
TfDeptLayout = class(TForm)
ADOQuery1: TADOQuery;
ListBox1: TListBox;
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
end;
var
fDeptLayout: TfDeptLayout;
implementation
{$R *.dfm}
procedure TfDeptLayout.FormClose(Sender: TObject; var Action: TCloseAction);
begin
end;
end.
The shown code is taking advantage of the VCL ownership model and the form will free it for you, as you just pass the form itself as the owner of your components when you create it:
machine := MachineShape.TMachine.Create(self);
as this is called from the TFLayout1 class, when the particular instance of the form is destroying itself, it will free all the owned components.
For a little more info, you can read the article: Owner vs. Parent in Delphi.
Edit
From comments, it resulted you create the TMachine instances on a class different of the form on which you show it, and you don't destroy the form instance when you close it, so, you can reach what you want making this changes:
Make the form in which the shapes are shown the owner, changing your code to create them to this:
//don't use self, now the parent is the instance referenced by fdeptlayout
machine := MachineShape.TMachine.Create(fdeptlayout);
On your Tfdeptlayout class, add a OnClose handler with this code:
begin
for I := ComponentCount - 1 downto 0 do
if Components[I] is TMachine then
Components[I].Free;
end;
That said, you really have to read the documentation and referenced articles to gain some understanding of what's going on behind the scenes in your Delphi application.
You are assigning an Owner to your TMachine objects. The shapes will be freed automatically when the Owner itself is freed.
Assuming TFLayout1 is your Form class, then by default it will not be freed automatically when it is closed. A closed Form is hidden by default so you can re-show when needed. To actually free it on close, you have to either set the Action parameter in the TForm.OnClose event to caFree, or call TForm.Free() directly sometime after the form is closed (such as if you are displaying the Form with ShowModal(), then you can call Free() after ShowModal() exits).
If you want to free the shapes yourself without relying on the behavior of an Owner, then set the Owner to nil when you create the shapes, and store your TMachine pointers in a TList that you can loop through when needed to free each shape, or a TObjectList with its OwnsObjects property set to true that you can Clear() when needed. Such as in the Form's OnClose event.
Related
I want to position a MessageBox in a particular position with respect to the active cell in a string grid and this is no problem using MessageDlgPos() except that I want to prevent the box running off the right or bottom of the screen when the active cell is close to the right or bottom. What I need for this is a way of getting the dimensions of the box but I cannot see a simple way of getting these. Anyone know how without creating my own box?
The MessageDlg...() functions do not support what you are asking for. The dimensions of the dialog are not known until the dialog is being displayed, and you have no way to access the dialog window directly to query/re-position it, except maybe with a WH_CBT hook from SetWindowsHookEx().
That being said...
On Windows Vista+ with Vcl.Dialogs.UseLatestCommonDialogs=true and Visual Styles enabled, the MessageDlg...() functions call the Win32 TaskDialogIndirect() API to display a message box. You have no control over that dialog's dimensions, so you would have to wait for that dialog to issue a TDN_DIALOG_CONSTRUCTED notification to then query its actual dimensions before it is displayed, so you can then adjust its position as needed. However, the MessageDlg...() functions do not provide access to any of TaskDialogIndirect()'s notifications (TCustomTaskDialog, which is used internally, does have an OnDialogConstructed event, amongst other events). So, if you wanted to reposition this dialog, you would have to call TaskDialogIndirect() yourself with a custom callback function (or, use the VCL's TTaskDialog wrapper).
On pre-Vista, or with UseLatestCommonDialogs=false or Visual Styles disabled, the MessageDlg...() functions display a custom VCL TForm via Vcl.Dialogs.CreateMessageDialog() instead, which you can call directly, and then pretty much query, manipulate, and show the returned TForm however you want. Just be sure to Free() it when you are done using it.
You could use an actual TTaskDialog. You can create you own version of it, add a TaskDialogConstructed procedure and get the dimension in the TaskDialogConstructed procedure. Something along the lines of the following.
type
TTaskDialog = class(Vcl.Dialogs.TTaskDialog)
protected
procedure TaskDialogConstructed(Sender: TObject);
end;
procedure TTaskDialog.TaskDialogConstructed(Sender: TObject);
var
TaskDialog: TTaskDialog;
R: TRect;
begin
TaskDialog := Sender as TTaskDialog;
Win32Check(GetWindowRect(TaskDialog.Handle, R));
{... Do whatever with R ...}
end;
function ExecuteTaskDialog(AOwner: TComponent; ATitle, AText: string; ACommonButtons: TTaskDialogCommonButtons = [tcbOK]): integer;
var
TaskDialog: TTaskDialog;
begin
TaskDialog := TTaskDialog.Create(AOwner);
with TaskDialog do
begin
Caption := Application.Title;
Title := ATitle;
Text := AText;
MainIcon := tdiNone;
Flags := Flags + [tfUseHiconMain];
CommonButtons := ACommonButtons;
CustomMainIcon.LoadFromResourceName(HInstance, 'MAINICON');
OnDialogConstructed := TaskDialogConstructed;
Execute;
Result := ModalResult;
Free;
end;
end;
Create the MessageDlg yourself. Add an OnActivate or OnShow event. In this method, ask / change the properties of the dialog.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.StdCtrls;
type
Tfrm = class(TForm)
btn: TButton;
procedure btnClick(Sender: TObject);
private
procedure OnDlgActivate(Sender: TObject);
public
{ Public-Deklarationen }
end;
var
frm: Tfrm;
implementation
uses
Vcl.Dialogs, System.TypInfo;
{$R *.dfm}
procedure Tfrm.btnClick(Sender: TObject);
var
Ldlg : TForm;
LiRet : integer;
begin
Ldlg := CreateMessageDialog('Hallo World!', mtInformation,mbYesNo, mbYes);
try
Ldlg.OnActivate := OnDlgActivate;
LiRet := Ldlg.ShowModal;
finally
Ldlg.free;
end;
end;
procedure Tfrm.OnDlgActivate(Sender: TObject);
var
Lfrm: TForm;
LcTxt: string;
begin
Lfrm := Sender as TForm;
LcTxt := Format('%s %sLeft: %d / Top: %d', [Lfrm.ClassName, sLineBreak, Lfrm.Left, Lfrm.Top]);
ShowMessage(LcTxt);
end;
end.
I'm backing up a database using Devart controls. Everything runs fine until I try to free the form having the backup component(TDump) and a progress bar on it. I moved the call to the finally portion of code and checked if it was assigned before trying to free it still same problem
procedure TfrmMain.CmdBackupExecute(Sender: TObject);
var
SaveDialog: TSaveDialog;
QRYString: String;
frmBackup: TfrmBackup;
Password: String;
BackupPassword: String;
MasterPassword: String;
CurrentFrame: TFrameType;
OldUser: String;
OldPassword: String;
begin
dmVintage.tblSettings.Open;
BackupPassword:= dmVintage.tblSettings.FieldByName('BackupRestorePWord').AsString;
MasterPassword:= dmVintage.tblSettings.FieldByName('MasterPWord').AsString;
InputPassword('Enter Backup Password', Password);
if Password = BackupPassword then
try
try
//close current frame and change to root
CurrentFrame:= FrameManager.CurrentFrameType;
FrameManager.Clear;
dmVintage.connMain.LoginPrompt:= False;
OldUser:= dmVintage.connMain.Username;
OldPassword:= dmVintage.connMain.Password;
dmVintage.connMain.Connected:= False;
dmVintage.connMain.Username:= 'root';
dmVintage.connMain.Password:= MasterPassword;
dmVintage.connMain.Connect;
SaveDialog:= TsaveDialog.Create(frmMain);
SaveDialog.Filter := 'SQL file|*.sql';
SaveDialog.DefaultExt:= '.sql';
SaveDialog.FileName:= 'VintageData';
if SaveDialog.Execute then
begin
frmBackup:= TfrmBackup.Create(frmMain);
frmBackup.Show;
frmBackup.mdVintage.BackupToFile(AddTimestampToFilename(SaveDialog.FileName), QryString);
//FreeAndNil(frmBackup);
//FreeAndNil(SaveDialog);
dlgI('Backup Seccessful');
end;
Except on E: Exception do
dlgW2('TfrmMain.CmdBackupExecute', E.Message);
end;
finally
//ShowMessage('Finally');
if Assigned(frmBackup) then
FreeAndNil(frmBackup);
if Assigned(SaveDialog) then
FreeAndNil(SaveDialog);
//reset connection and load old frame
dmVintage.connMain.Connected:= False;
dmVintage.connMain.Username:= OldUser;
dmVintage.connMain.Password:= OldPassword;
dmVintage.connMain.Connect;
dmVintage.connMain.LoginPrompt:= True;
FrameManager.LoadFrame(CurrentFrame);
end
else
dlgE('Invalid Backup Password');
end;
function TfrmMain.AddTimestampToFilename(Value: String): String;
var
Extension: String;
FileName: String;
FormattedDataTime: String;
begin
Extension:= ExtractFileExt(Value);
FileName:= ChangeFileExt(Value, '');
DateTimeToString(FormattedDataTime, 'yyyymmdd_hhmm', Now);
FileName:= FileName + '_' + FormattedDataTime;
Result:= ChangeFileExt(FileName, Extension);
end;
The Backup form is very simple with a few labels the TDump component and a progress bar.
unit uBackup;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, uDataVintage, DADump, MyDump,
Vcl.ComCtrls, Vcl.StdCtrls;
type
TfrmBackup = class(TForm)
mdVintage: TMyDump;
lblBackingUpTable: TLabel;
lblTable: TLabel;
Label3: TLabel;
pbBackup: TProgressBar;
procedure mdVintageBackupProgress(Sender: TObject; ObjectName: string;
ObjectNum, ObjectCount, Percent: Integer);
private
{ Private declarations }
public
{ Public declarations }
end;
implementation
{$R *.dfm}
procedure TfrmBackup.mdVintageBackupProgress(Sender: TObject;
ObjectName: string; ObjectNum, ObjectCount, Percent: Integer);
begin
Application.ProcessMessages;
if lblTable.Caption <> ObjectName then
lblTable.Caption:= ObjectName;
pbBackup.Position:= Percent;
end;
end.
Use frmBackup.Release when freeing (especially non-modal) forms.
Freeing forms
The form is not shown in a modal way, and you couldn't, because you're controlling it from the main form, which wouldn't work on a modal form.
I think you get the access violation because you 'bluntly' free the form, while the form itself is also still visible and handling messages. Because of that, the form code (the general TForm code) might at some point still try to do something to the form, even though your instance has already been cleaned up. Even when you call 'Close` in the code, you have this issue, because closing is also not a synchronous process, and requires the form to handle messages.
In general the solution is to call frmBackup.Release instead of frmBackup.Free. That way, the form queues a message for itself. It will first handle the other stuff it has to do, and at some point encounter this message and start a graceful clean-up procedure before it finally frees itself. This is typically the way to close a form from, say, a button-click event on the form itself, but I think it will get you out of this pickle as well.
General tips on Free and FreeAndNil
You don't need to call FreeAndNil in most cases, and especially not on a local variable for which you know exactly when it was assigned a value or not. The only thing FreeAndNil does, is make your reference nil, which is not needed at all for a variable that goes out of scope three lines later anyway.
There is no need at all to call if assigned before calling FreeAndNil, or even Free. Assigned is only checking if the reference is nil, which is what Free also does internally. That right: This is valid code that won't throw errors:
var
o: TObject;
begin
o := nil;
o.Free;
This VCL Form program generates the Invalid Pointer Operation notice:
Uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls,
DcadMenu_u;
type
TForm1 = class(TForm)
MenuTestRichEdit: TRichEdit;
LoadButton: TButton;
procedure ButtonLoadClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.ButtonLoadClick(Sender: TObject);
var
menu : TDcadMenu;
item1, item2 : TDcadMenuItem;
strlist :tstringlist;
i : integer;
begin
menu := tDcadMenu.Create();
item1 := TDcadMenuItem.create ('Option1', 'Do Option1', false, false, true);
menu.add (item1);
item2 := TDcadMenuItem.create ('Option2', 'Do Option2', false, false, true);
menu.add (item2);
strlist := tstringlist.Create;
Try
For i := 0 to Menu.Count - 1 DO
begin
item1 := menu.Items[i];
strlist.Add (Item1.lblset + ' | ' + Item1.lblmsg );
end;
Form1.MenuTestRichEdit.Lines := strlist;
finally
item1.free;
item2.Free;
menu.free;
strlist.Free;
end;
end;
The code works fine and generates the item list in the Richedit component. I suspect I am freeing an object that is already being handled, but not clear on what the cause is specifically. Can someone explain this?
We can't see the implementation of TDcadMenu, but normally adding items to a class gives the ownership of the items to that class, so there is no need to free the items outside of the class. As #Remy comments, it is normally safe to free them before before freeing the menu object, though.
In your code you are reassigning item1, and when freeing the items, Item1 and Item2 both shares the same instance as menu.Items[1]. This means that you have a double free, which gives your invalid pointer notice.
item1.free;
item2.Free; // <- Double free of same instance
I have the following code:
Project.dpr
program Project2;
uses
madExcept,
madLinkDisAsm,
madListHardware,
madListProcesses,
madListModules,
Spring.Container,
Vcl.Forms,
uRegistrations in '..\Memory leak II\uRegistrations.pas',
Unit3 in 'Unit3.pas' {MainForm},
Unit4 in 'Unit4.pas' {SecondaryForm},
Unit5 in 'Unit5.pas';
{$R *.res}
begin
RegisterTypes(GlobalContainer);
Application.Initialize;
Application.MainFormOnTaskbar := True;
// MainForm:=TMainForm.Create(nil);
Application.CreateForm(TMainForm, MainForm);
MainForm.SecondaryForm := Globalcontainer.Resolve<ISecondaryForm>;
Application.Run;
end.
uRegistrations.pas that registers the interface
unit uRegistrations;
interface
uses
Spring.Container;
procedure RegisterTypes(Container: TContainer);
implementation
uses
Unit5,
Unit4;
procedure RegisterTypes(Container: TContainer);
begin
container.RegisterType<ISecondaryForm, TSecondaryForm>.DelegateTo(
function: TSecondaryForm
begin
result := TSecondaryForm.Create(nil);
end);
Container.Build;
end;
end.
Unit3.pas holding the main form
unit Unit3;
interface
uses
Winapi.Windows,
Winapi.Messages,
System.SysUtils,
System.Variants,
System.Classes,
Vcl.Graphics,
Unit5,
Vcl.Controls,
Vcl.Forms,
Vcl.Dialogs;
type
TMainForm = class(TForm)
private
{ Private declarations }
FSecondaryForm: ISecondaryForm;
public
{ Public declarations }
property SecondaryForm: ISecondaryForm read FSecondaryForm write FSecondaryForm;
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
end.
Unit4.pas with the secondary form
unit Unit4;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
unit5,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs;
type
TSecondaryForm = class(TForm, ISecondaryForm)
private
{ Private declarations }
public
{ Public declarations }
end;
//var
// SecondaryForm: TSecondaryForm;
implementation
{$R *.dfm}
end.
and finally Unit5.pas with the Interface declaration
{$M+}
unit Unit5;
interface
type
ISecondaryForm=interface
['{62D63E9A-A3AD-435B-8938-9528E70D78B1}']
end;
implementation
end.
It compiles and runs regularly but when I close the application i have three memory leaks.
allocation number: 8482 program up time: 721 ms type: Brush Handle
handle: $461027f5 style: BS_SOLID color: $f0f0f0
allocation number: 8318 program up time: 697 ms type: TSecondaryForm
address: $d51ac64 size: 924 access rights: read/write
allocation number: 8267 program up time: 693 ms type: Font Handle
handle: $1d0a28f1 face: Tahoma height: -11
Why does this happens and how can I solve it ?
EDIT
After the answer, I implemented the following solutions (the comments highlight the errors I got:
procedure RegisterTypes(Container: TContainer);
begin
container.RegisterType<ISecondaryForm, TSecondaryForm>.DelegateTo(
function: TSecondaryForm
begin
result := TSecondaryForm.Create(nil);
result.Owner:=Application.MainForm;//cannot assign to a read-only property
result.Parent:=Application; //incompatible types
result.Parent:=application.MainForm;//memory leak
end);
Container.Build;
end;
I have also tried to amend the OnClose method of TSecondaryForm in the following way:
procedure TSecondaryForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action:=caFree; //memory leak
end;
but I got a memory leak.
What am I doing wrong with all the technique above ?
In the end I just made the two methods _AddRef and _Release manage the reference counting as suggested in the comments and I have no more memory leaks.
TSecondaryForm = class(TForm, ISecondaryForm)
private
{ Private declarations }
protected
FRefCount: Integer;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
{ Public declarations }
end;
function TSecondaryForm._AddRef: Integer;
begin
Result := InterlockedIncrement(FRefCount);
end;
function TSecondaryForm._Release: Integer;
begin
Result := InterlockedDecrement(FRefCount);
if Result=0 then
self.Free;
end
If you want a form (or any class that inherits from TComponent) to be handled by interface reference counting then you need to implement it yourself (look at System.TInterfacedObject as an example of how to do it).
You basically need to reimplement IInterface to the class you want to enable reference counting on:
type
TInterfacedForm = class(TForm, IInterface)
// look at System.TInterfacedObject
end;
If you are doing so, keep in mind that it then should not be handled by the owner mechanism. If you register it to the container and use its default creation mechanism it will pass nil to the owner as of Spring4D 1.2 - see Spring.Container.Resolvers.TComponentOwnerResolver). In any version before you need to explicitly create it with nil inside of DelegateTo.
If you are dealing with any controls over interfaces that are put onto other controls (like frames) via their parent property keep also in mind that in such case another memory management mechanism comes into play which might destroy such a component if its parent is getting destroyed - if you are just dealing with interfaced forms that is not a problem but I thought I mention it here for completeness.
TComponent descendants (like TForm) disable interfaces reference counting, hence nobody is freeing the secondary form. The memory model is owner based, that is, when the parent object that owns an object is freed, it frees all it's children.
So, you could either pass an owner to the form on the factory function (maybe Application, or Application.MainForm) and adhere to TComponent's memory model or add a hook on the OnClose event of the form and set Action to caFree. The former will destroy the form when the application is closed, and the latter will destroy it as soon as the secondary form is closed (as soon as possible)
So, lately we (me and my coworkers) have been chatting about migrating to FireDac, we are currently using IBO and DBX, but mostly IBO. And then we decided to take everything from IBO to FireDac, but entering in every form, changing every IBOQuery, adding all fields, settings all the display format, etc, etc, etc, would take too much time, so we decided to make a component do it, seemed like an easy task, but I just started and I'm already stuck in something that seems simple, but that I never came across before. First let's look at the component code:
unit UMyComponent;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IB_Components, IB_Access,
IBODataset, Vcl.StdCtrls, Vcl.Buttons, Vcl.Grids, Vcl.DBGrids, Data.DB,
uADStanIntf, uADStanOption, uADStanParam, uADStanError,
uADDatSManager, uADPhysIntf, uADDAptIntf, uADStanAsync, uADDAptManager,
uADCompDataSet, uADCompClient;
type
TMyComponent = class(TComponent)
private
FADConnection: TADConnection;
FConverter: String;
procedure Iniciar;
procedure SetADConnection(const Value: TADConnection);
procedure SetConverter(const Value: String);
published
property Converter: String read FConverter write SetConverter;
property ADConnection: TADConnection read FADConnection write SetADConnection;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('MyComponents', [TMyComponent]);
end;
{ TMyComponent }
procedure TMyComponent.Iniciar;
var
Form: TForm;
IBOQuery: TIBOQuery;
i: Integer;
procedure _ConverterIBOQuery;
var
ADQuery: TADQuery;
qName: String;
begin
qName := IBOQuery.Name;
if qName.Contains('OLD_') then
Exit;
IBOQuery.Name := 'OLD_'+ qName;
if (FindComponent(qName) = nil) then
begin
ADQuery := TADQuery.Create(Form);
ADQuery.Name := qName;
ADQuery.Connection := FADConnection;
ADQuery.SQL := IBOQuery.SQL;
{
I need to add the fields here, but I need them having a reference,
like the ones you Right Click > Fields Editor > Add All Fields (CTRL + F)
because in the final form of this component, it won't rename the old query
with an 'OLD_' prefix, it will destroy it, and the fields will be gone too,
so I need to add them (having the reference) in order to not rewrite any of my code
}
end;
end;
begin
if Owner is TForm then
Form := TForm(Owner);
if Assigned(Form) then
begin
for i := 0 to (Form.ComponentCount -1) do
{
I know it will stop in the first query it come across,
but I'm trying to full convert only one to know if it's actually possible
}
if (Form.Components[i] is TIBOQuery) then
begin
IBOQuery := TIBOQuery(Form.Components[i]);
Break;
end;
if Assigned(IBOQuery) then
_ConverterIBOQuery;
end;
end;
procedure TMyComponent.SetConverter(const Value: String);
begin
FConverter := UpperCase(Value[1]);
if (FConverter = 'S') then
Iniciar;
FConverter := '';
end;
procedure TMyComponent.SetADConnection(const Value: TADConnection);
begin
FADConnection := Value;
end;
end.
I already tried some of methods found on the internet, such as:
Creating a variable of TField
Using FieldDefs/FieldDefList, updating them and creating the fields
"Hacking" the ADQuery with a "fake" class in order to use the
CreateFields procedure
And none of them did what I was expecting, so I'm questioning
Can I create the field references via code? And, if it's possible, how?
And with references I mean, for example, you have IBOQuery1, and the SQL is
SELECT NAME
FROM COUNTRY
After that, you go to the Fields Editor > Add All Fields (CTRL + F), and then you have the reference IBOQuery1NAME, which is a TStringField and you can just call IBOQuery1NAME.AsString instead of IBOQuery1.FieldByName('NAME').AsString
TL;DR
Trying to create a component that migrate a IBOQuery to ADQuery, but I can't create the references
After many attempts and research, I found an old question with a problem similar to mine, and happily there was a answer with exactly what I wanted
How to add a field programatically to a TAdoTable in Delphi
The answer was provided by the user: Мסž
procedure AddAllFields(DataSet: TDataset);
var
FieldsList: TStringList;
FieldName: WideString;
Field: TField;
WasActive: boolean;
FieldDef: TFieldDef;
i: Integer;
begin
WasActive := DataSet.Active;
if WasActive then
DataSet.Active := False;
try
FieldsList := TStringList.Create;
try
DataSet.FieldDefs.Update;
// make a list of all the field names that aren't already on the DataSet
for i := 0 to DataSet.FieldDefList.Count - 1 do
with DataSet.FieldDefList[i] do
if (FieldClass <> nil) and not(faHiddenCol in Attributes) then
begin
FieldName := DataSet.FieldDefList.Strings[i];
Field := DataSet.FindField(FieldName);
if (Field = nil) or (Field.Owner <> DataSet.Owner) then
FieldsList.Add(FieldName);
end;
// add those fields to the dataset
for i := 0 to FieldsList.Count - 1 do
begin
FieldDef := DataSet.FieldDefList.FieldByName(FieldName);
Field := FieldDef.CreateField(DataSet.Owner, nil, FieldName, False);
try
Field.name := FieldName + IntToStr(random(MaxInt)); // make the name unique
except
Field.Free;
raise ;
end;
end;
finally
FieldsList.Free;
end;
finally
if WasActive then
DataSet.Active := true;
end;
end;