2

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.

Juke
  • 135
  • 2
  • 9
  • 1
    Are you sure that Form1 is pointing to the instance of your form ?. Can you show us the code where you create the form ?. – Marc Guillot Feb 25 '20 at 07:50
  • Forgot to mention here that I am using MDI and both forms are MDIChildren. The form is created from the ParentMainForm. – Juke Feb 25 '20 at 08:05
  • 1
    Please, ALWAYS when you get an error, copy (right click the message window and then hit Ctrl-C) the error message to your question. And please don't expand your question here in comments. [edit] your question. – Tom Brunberg Feb 25 '20 at 08:05
  • 1
    Please show the code you are using, and show it in context, nit just as a single statement like ShowMessage (...) - readers should not have to guess. – MartynA Feb 25 '20 at 08:17
  • @MartynA I will create an RME for this. Give me a minute. – Juke Feb 25 '20 at 08:19
  • RME provided. Hope that's clear enough now. I feel that ChildForm2 cannot have direct access from ChildForm1. Since, it has to go through first with the ParentForm? Need some light here... – Juke Feb 25 '20 at 08:53
  • 1
    You have to assign `ChildForm1:=TChildForm1.Create(Self); ChildForm1.Show();`. In your code `ChildForm1` is nil. – BrakNicku Feb 25 '20 at 08:57
  • @BrakNicku You got it! It works now! Thank you! Can you come up an answer here so I can tag it and close this already. – Juke Feb 25 '20 at 09:07
  • 2
    So, Juke. If you had followed @Fabrizio suggestion to check assignment of Form1 (et al) you would have had the answer! Or at least a clear indication what might be wrong. – Tom Brunberg Feb 25 '20 at 09:18
  • @TomBrunberg Yes I understand and realized it already. – Juke Feb 25 '20 at 09:24

2 Answers2

2

Probably, one of the objects is not assigned, I suspect it could be the Columns[1] (note that the Columns collection is zero based index, so the first column is Columns[0])

Try this:

if(not Assigned(Form1)) then 
  raise Exception.Create('Form1 not assigned');

if(not Assigned(Form1.DBGrid1)) then 
  raise Exception.Create('Form1.DBGrid1 not assigned');

if(Form1.DBGrid1.Columns.Count < 2) then 
  raise Exception.Create('Form1.DBGrid1 has not the Columns[1] item');
Fabrizio
  • 7,603
  • 6
  • 44
  • 104
  • Actually the columns already exist in the form it referred from. I was referring to specific column in this case. I am not sure if the code you gave works. I tried to access it in the same form using ShowMessage and it did execute using this — ShowMessage(DBGrid1.Columns[1].Title.Caption); – Juke Feb 25 '20 at 07:48
  • 1
    So I think that `Form1` object is not assigned or it does not belong to the expected class type. Try to check it – Fabrizio Feb 25 '20 at 07:51
  • I forgot to mention that I am using MDI here. Both forms are MDIChildren by the way. – Juke Feb 25 '20 at 08:03
1

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

MartynA
  • 30,454
  • 4
  • 32
  • 73
  • I know. I just learnt it from here. I'd changed already some of my codes pertaining to this. Thank you so much. – Juke Feb 25 '20 at 14:14