Jump to content
Gustav Schubert

loading example data into fmx TStringGrid

Recommended Posts

FMX: What is the recommended way to load example data into TStringGrid?

I have this, but it no longer works:
 

unit FrmMain;

interface

uses
  System.SysUtils,
  System.Types,
  System.UITypes,
  System.Classes,
  FMX.Types,
  FMX.Controls,
  FMX.Forms,
  Data.Bind.EngExt,
  FMX.Bind.DBEngExt,
  FMX.Bind.Editors,
  Data.Bind.Components,
  Data.Bind.DBScope,
  Data.Bind.DBLinks,
  Fmx.Bind.DBLinks,
  FMX.Layouts,
  FMX.Grid,
  Data.DB,
  Datasnap.DBClient,
  FMX.Edit,
  System.Bindings.Outputs,
  FMX.StdCtrls,
  System.Rtti,
  FMX.Grid.Style,
  FMX.Controls.Presentation,
  FMX.ScrollBox;

type
  TFormMain = class(TForm)
    StringGrid: TStringGrid;
    TestBtn: TButton;
    procedure FormCreate(Sender: TObject);
    procedure TestBtnClick(Sender: TObject);
  private
    ClientDataSet: TClientDataSet;
    DataSource: TDataSource;
    BindScope: TBindScopeDB;
    BindingsList: TBindingsList;

    gl: TBindDBGridLink;
    Counter: Integer;
  end;

var
  FormMain: TFormMain;

implementation

{$R *.fmx}

procedure TFormMain.FormCreate(Sender: TObject);
var
  dn: string;
  fn: string;
  pn: string;
  ML: TStringList;
begin
  TestBtn.Enabled := False;

  ReportMemoryLeaksOnShutdown := True;

  dn := 'C:\Users\Public\Documents\Embarcadero\Studio\37.0\Samples\Data\';
  fn := 'country.xml';
  pn := dn + fn;

  if FileExists(pn) then
  begin
    ClientDataSet := TClientDataSet.Create(self);
    BindingsList := TBindingsList.Create(self);

    ML := TStringList.Create;
    ML.LoadFromFile(pn);
    ClientDataSet.Active := False;
    ClientDataSet.XMLData := ML.Text;
    ClientDataSet.Active := True;
    ML.Free;

    DataSource := TDataSource.Create(self);
    DataSource.AutoEdit := False;
    DataSource.DataSet := ClientDataSet;

    BindScope := TBindScopeDB.Create(self);
    BindScope.DataSet := ClientDataSet;
    BindScope.DataSource := DataSource;

    TestBtn.Enabled := True;
  end;
end;

procedure TFormMain.TestBtnClick(Sender: TObject);
begin
  if (gl <> nil)  then
    gl.Active := False;

  gl.Free;

  gl := TBindDBGridLink.Create(BindingsList);
  gl.AutoBufferCount := False;
  gl.Category := 'DB Links';
  gl.DataSource := BindScope;
  gl.GridControl := StringGrid; // <-- Exception in D13

// in D12.3: ok
// in D13: Crash because Position = nil in TControl.GetBoundRect

{
function TControl.GetBoundsRect: TRectF;
begin
//  if Position = nil then
//    result := TRectF.Create(0, 0, 0, 0)
//  else
    Result := TRectF.Create(Position.X, Position.Y, Position.X + Width, Position.Y + Height);
end;
}

  gl.Active := True;

  Inc(Counter);
  Caption := IntToStr(Counter);
end;

end.

 

Share this post


Link to post
2 minutes ago, Gustav Schubert said:

<-- Exception in D13

What is the exception? Please run it in the debugger and show the callstack at that point. 

Share this post


Link to post
12 minutes ago, Dave Nottage said:

What is the exception?

FMX.Controls.TControl.GetBoundsRect
FMX.ScrollBox.TScrollContent.IsVisibleChild($CB85650)
FMX.Controls.TControl.RecalcUpdateRect
FMX.Controls.TControl.InternalSizeChanged
FMX.Controls.TControl.HandleSizeChanged
FMX.Controls.TControl.SizeChanged(???)
FMX.Controls.TControl.RecalcSize
FMX.ScrollBox.Style.TStyledCustomScrollBox.RealignContent
FMX.Grid.Style.TStyledGrid.MMInvalidateContentSize(???)
FMX.Presentation.Messages.TMessageSender.SendMessage(???)
FMX.Grid.TGridModel.InvalidateContentSize
FMX.Grid.TGridModel.RemoveColumn(???)
FMX.Grid.TCustomGrid.ContentBeforeRemoveObject(???)
FMX.ScrollBox.TScrollContent.DoRemoveObject($CB85650)
FMX.Types.TFmxObject.RemoveObject(???)
FMX.Types.TFmxObject.Destroy
FMX.Controls.TControl.Destroy
FMX.Grid.TColumn.Destroy
System.TObject.Free
Fmx.Bind.DBLinks.TBindDBStringGridColumnCreator.ClearColumns
Data.Bind.DBLinks.TBaseBindDBGridLink.ClearColumns(TBindDBStringGridColumnCreator($CB95270) as IBindDBGridLinkControlManager)
Fmx.Bind.DBLinks.TCustomBindDBGridLink.ClearColumns(TBindDBStringGridColumnCreator($CB95270) as IBindDBGridLinkControlManager)
Fmx.Bind.DBLinks.TCustomBindDBGridLink.ClearGeneratedExpressions(???)
Fmx.Bind.DBLinks.TCustomBindDBGridLink.UpdateColumns
Data.Bind.DBLinks.TInternalBindGridLink.ApplyComponents
Data.Bind.Components.TActivatedContainedBindComponent.UpdateControlChanged
Data.Bind.Components.TCommonBindComponent.SetControlComponent($62A29B0)
Fmx.Bind.DBLinks.TBaseBindDBGridControlLink.SetControl(???)
FrmMain.TFormMain.TestBtnClick($4EC7280)
FMX.Controls.TControl.Click
FMX.StdCtrls.TCustomButton.Click
FMX.Controls.TControl.MouseClick(???,???,45,14)
FMX.Forms.TCommonCustomForm.MouseUp(mbLeft,[ssLeft],141,38,???)
FMX.Platform.Win.WndProc(460274,514,0,2490509)
FMX.Platform.Win.TPlatformWin.HandleMessage
FMX.Forms.TApplication.HandleMessage
FMX.Platform.Win.TPlatformWin.Run
FMX.Forms.TApplication.Run
SG02.SG02

I get access violation, Position is nil in TControl.GetBoundsRect.

Share this post


Link to post

I had a similar bug but I couldn't reproduce it anymore. Looks like RemoveColumn triggers some calls that act too late, when Position etc are already destroyed (use-after-nil).  I did submit a QC issue but never was activated as I couldn't add reproduceable steps.

Share this post


Link to post

I now have a workaround: Need to unload the Presentation before attempting the critical assignment.

 

procedure TFormMain.TestBtnClick(Sender: TObject);
begin
  StringGrid.UnloadPresentation; // to avoid problems later

  if (gl <> nil)  then
    gl.Active := False;

  gl.Free;

  gl := TBindDBGridLink.Create(BindingsList);
  gl.AutoBufferCount := False;
  gl.Category := 'DB Links';
  gl.DataSource := BindScope;
  gl.GridControl := StringGrid; // <-- critical line

  gl.Active := True;

  StringGrid.LoadPresentation; // now it is safe to have a presentation

  Inc(Counter);
  Caption := IntToStr(Counter);
end;

Depending on who the Owner is of these databinding components, Form or Application, it may be necessary to do the same (unloading the presentation of the string grid) when the application closes down. I needed to do this in FormClose, FormDestroy would be tool late.

Share this post


Link to post

This topic is about the access violation in TControl.GetBoundsRect when using a TStringGrid with data binding, Win32 platform. It is a "regression" in Delphi 13.

 

By now, there are two known manifestations of the problem: when assigning to the GridControl property of TBindDBGridLink, and when closing down the application.

 

New: I have investigated the second manifestation, when closing down. To do so I am passing nil as Owner when creating components, so that I have control over the order of destruction in FormDestroy.

 

Order of destruction matters, StringGrid is key here. When the StringGrid is destroyed first, no problem. When StringGrid is not destroyed first, then I need the workaround, and unload the presentation, to avoid the problem.

 

Updated test program:

 

unit FrmMain;

interface

uses
  System.SysUtils,
  System.Classes,
  Data.Bind.Components,
  Data.Bind.DBScope,
  Data.DB,
  Datasnap.DBClient,
  FmX.Bind.DBEngExt,
  Fmx.Bind.DBLinks,
  FmX.Bind.Editors,
  FMX.Forms,
  FMX.Controls,
  FMX.Grid,
  FMX.Grid.Style,
  FMX.Edit,
  FMX.StdCtrls,
  FMX.ScrollBox,
  FMX.Controls.Presentation;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    TestBtn: TButton;
    StringGrid: TStringGrid;
    ClientDataSet: TClientDataSet;
    DataSource: TDataSource;
    BindScope: TBindScopeDB;
    BindingsList: TBindingsList;
    gl: TBindDBGridLink;
    procedure TestBtnClick(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

procedure TForm1.FormCreate(Sender: TObject);
var
  dn: string;
  fn: string;
  pn: string;
  ML: TStringList;
begin
  ReportMemoryLeaksOnShutdown := True;

  TestBtn := TButton.Create(nil);
  TestBtn.Parent := Self;
  TestBtn.Position.X := 10;
  TestBtn.Position.Y := 10;
  TestBtn.Name := 'TestBtn';
  TestBtn.OnClick := TestBtnClick;
  TestBtn.Enabled := False;

  StringGrid := TStringGrid.Create(nil);
  StringGrid.Parent := Self;
  StringGrid.Position.X := 10;
  StringGrid.Position.Y := 60;
  StringGrid.Width := 600;
  StringGrid.Height := 200;
  StringGrid.Name := 'StringGrid';

  ClientDataSet := TClientDataSet.Create(nil);
  BindingsList := TBindingsList.Create(nil);

  dn := 'C:\Users\Public\Documents\Embarcadero\Studio\37.0\Samples\Data\';
  fn := 'country.xml';
  pn := dn + fn;

  if FileExists(pn) then
  begin
    ML := TStringList.Create;
    ML.LoadFromFile(pn);
    ClientDataSet.Active := False;
    ClientDataSet.XMLData := ML.Text;
    ClientDataSet.Active := True;
    ML.Free;

    DataSource := TDataSource.Create(nil);
    DataSource.AutoEdit := False;
    DataSource.DataSet := ClientDataSet;

    BindScope := TBindScopeDB.Create(nil);
    BindScope.DataSet := ClientDataSet;
    BindScope.DataSource := DataSource;

    TestBtn.Enabled := True;
  end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  StringGrid.UnloadPresentation;

  gl.Free;
  BindScope.Free;
  BindingsList.Free;

  DataSource.Free;
  ClientDataSet.Free;

  StringGrid.Free;
  TestBtn.Free;
end;

procedure TForm1.TestBtnClick(Sender: TObject);
begin
  TestBtn.Enabled := False;

  StringGrid.UnloadPresentation;

  gl := TBindDBGridLink.Create(nil);
  gl.AutoBufferCount := False;
  gl.Category := 'DB Links';
  gl.DataSource := BindScope;
  gl.GridControl := StringGrid;
  gl.Active := True;

  StringGrid.LoadPresentation;
end;

end.

 

  • Like 1

Share this post


Link to post
1 hour ago, Gustav Schubert said:

Updated test program:

Can you upload your test program here? Alternatively, create an entry in QP and attach there?

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×