I am trying to access the Caption
of the dbgrid.field from another form.
I am using MDI here and both forms are MDIChildren.
I tried to execute the following ShowMessage
from another form but it caused an access violation:
ShowMessage(Form1.DBGrid1.Columns[1].Title.Caption); // 1st try
ShowMessage(Unit1.Form1.DBGrid1.Columns[1].Title.Caption); // 2nd try
Uses set already between 2 forms.
The error message is
Access Violation at address 000010363F9 in module. Read of address 000000006F0.
What am I missing here?
UPDATE: Here's the exact replicate (RME) of this case.
MDI Parent
unit MainUnit;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Menus;
type
TParentForm = class(TForm)
mmMenu: TMainMenu;
miForm1: TMenuItem;
miForm2: TMenuItem;
procedure miForm1Click(Sender: TObject);
procedure miForm2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
ParentForm: TParentForm;
implementation
uses
Form1Unit, Form2Unit;
{$R *.dfm}
procedure TParentForm.miForm1Click(Sender: TObject);
begin
TChildForm1.Create(self).Show;
end;
procedure TParentForm.miForm2Click(Sender: TObject);
begin
TChildForm2.Create(self).Show;
end;
end.
MDI ChildForm1
unit Form1Unit;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Data.DB, Datasnap.DBClient,
Datasnap.Provider, MemDS, DBAccess, Uni, UniProvider, MySQLUniProvider,
Vcl.Grids, Vcl.DBGrids;
type
TChildForm1 = class(TForm)
dbgrd1: TDBGrid;
ucn1: TUniConnection;
mup1: TMySQLUniProvider;
uq1: TUniQuery;
dsp1: TDataSetProvider;
cds1: TClientDataSet;
ds1: TDataSource;
smlntfldcds1actor_id: TSmallintField;
strngfldcds1first_name: TStringField;
strngfldcds1last_name: TStringField;
dtmfldcds1last_update: TDateTimeField;
private
{ Private declarations }
public
{ Public declarations }
end;
var
ChildForm1: TChildForm1;
implementation
uses
MainUnit, Form2Unit;
{$R *.dfm}
end.
MDI ChildForm2
unit Form2Unit;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TChildForm2 = class(TForm)
btn1: TButton;
procedure btn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
ChildForm2: TChildForm2;
implementation
uses
MainUnit, Form1Unit;
{$R *.dfm}
procedure TChildForm2.btn1Click(Sender: TObject);
begin
ShowMessage(Form1Unit.ChildForm1.dbgrd1.Columns[2].Title.Caption);
end;
end.
Error Message
Access Violation at address 0081B314 in module 'Project7.exe'. Read of address 000003D0.
Writing code like Form1Unit.ChildForm1.dbgrd1.Columns[2].Title.Caption)
is creating an accident waiting to happen,
because it assumes that the instance of ChildForm1 you want to operate on is the auto-created ChildForm1
.
Using auto-created forms, except perhaps the main form, is generally considered to be bad practice because it encourages accidents like this, so it is probably best to get out of the habit of using them.
A less accident prone technique for accessing one form (or datamodule) from another is to write the code on the "other" form in a way which requires you to specify the object instance to operate on. Something like this:
procedure TChildForm2.DoSomethingWithForm1(Form1Instance : TForm1);
begin
ShowMessage(Form1Instance.dbgrd1.Columns[2].Title.Caption);
end;
procedure TChildForm2.btn1Click(Sender: TObject);
begin
DoSomethingWithForm1(Form1Unit.ChildForm1);
end;
The point of doing it that way is that it forces you to think about which Form1 instance you mean, because getting that right can be very important when you have multiple instances of the same form (and even when you don't, because it might prompt you to wonder whether the instance has been created).