1
votes

I've trying to add some custom columns to a TGrid, for example column that will consist of TCalendarEdit's and livebind it to DB.
I came out with two ways of how to implement something like this.

  1. Put CalendarEdit in all rows.
    Problem is: I can create custom TCalendarEdit column,
    TDateColumn = class(TColumn)
    protected
    function CreateCellControl: TStyledControl; override;
    end;
    function TDateColumn.CreateCellControl: TStyledControl;
    begin
    Result := TDateCell.Create(grid);
    Result.Parent := grid;
    end;

    can put it in Grid,
    procedure TForm1.FormCreate(Sender: TObject);
    var
    cec: TDateColumn;
    begin
    cec:=TDateColumn.Create(grid1);
    grid1.AddObject(cec);
    end;

    but then i livebind it to dataset LinkGridToDataSource1.Columns.Items[LinkGridToDataSource1.Columns.Count-1].MemberName:='date';
    it drops to default string column view!

  2. Draw a control over Grid cell then i click on and destroy it then i click on any other place on Grid (and write it's data to Grid).
    Problem is: How can i get coordinates and size of cell OnClick?

Thank you.

3
Q1: How are you creating your custom cell and column? Q2: I'm not sure what you're trying to achieve here.Mike Sutton
Thank you for your interest! I have edited my question. The question is still topical.Alez

3 Answers

1
votes

I only got a solution to the first part. It's a little bit tricky because of the weird ColumnCreate implementation in the original source.

What's the reason?

Have a look at unit Fmx.Bind.Grid (path on Win32 %programfiles%\Embarcadero\RAD Studio\10.0\source\databinding\components) and method (starts at line 500)

function TLinkGridToDataSourceControlManager.CreateColumn( 
  const ADescription : TCreateColumnDescription; AGrid : TCustomGrid ) : TColumn;

The Columns are created in relation to ADescription.ColumnStyle or if empty on ADescription.MemberType. But it is not based on registered classes, it's hard coded.

This is weird, because the ColumnStyleName is build from the ColumnClass without the leading T (e.g. Class TStringColumn => ColumnStyle StringColumn).

Why didn't Emba just search for registered Classes based on ColumnStyle?

FindClass( 'T' + ADescription.ColumnStyle )

If so, you were able to register your own ColumnClasses TMyColumn, set the ColumnStyle property to MyColumn and everything would be fine. You were not able to see this ColumnStyle in the PropertyEditor unless you install this ColumnClass as a package (no, this wont work, because PropertyEditor is also hard coded), but who cares about that it can be set inside OI.

Let's get flexible

To get this fixed you have to do some steps by hand

  1. Copy Fmx.Bind.Grid.pas to new path (project did or a valid Delphi Search/Library Path)
  2. Rename it to Fmx.Bind.GridAdv.pas
  3. Now you have to replace Fmx.Bind.Grid to Fmx.Bind.GridAdv at lines in this copy

    line 9, 10, 455

  4. To get the flexibility inside replace this (starting at line 500)

    function TLinkGridToDataSourceControlManager.CreateColumn(
      const ADescription: TCreateColumnDescription; AGrid: TCustomGrid): TColumn;
    begin
      Result := nil;
      if ADescription.ColumnStyle <> '' then
    

    with this

    function TLinkGridToDataSourceControlManager.CreateColumn( 
      const ADescription : TCreateColumnDescription; AGrid : TCustomGrid ) : TColumn;
     // ** MOD START **
     type
       TColumnClass = class of TColumn;
     var
       LColumnClass : TColumnClass;
     // ** MOD END **
     begin
       Result := nil;
       if ADescription.ColumnStyle <> '' then
     // ** MOD START **
         begin
           LColumnClass := TColumnClass( FindClass( 'T' + ADescription.ColumnStyle ) );
           if LColumnClass <> nil
           then
             begin
               Result := LColumnClass.Create( FCustomGrid );
             end else 
     // ** MOD END **
    

    and some lines below we have to close the begin

    // ** MOD START **
        end;
    
    // ** MOD END **
    
      if Result = nil then
        case ADescription.MemberType of
    
  5. Save the file

Custom Column

As a sample i will use a simple TNumberColumn derived from TStringColumn. Remember you have to register your custom column classes.

unit FMX.Grid.Columns;

interface

uses
  FMX.Grid, FMX.Types, FMX.Menus;

type
  TNumberColumn = class( TStringColumn )
  protected
    function CreateCellControl : TStyledControl; override;
  end;

implementation

{ TNumberColumn }

function TNumberColumn.CreateCellControl : TStyledControl;
begin
  Result                            := inherited;
  ( Result as TTextCell ).TextAlign := TTextAlign.taTrailing;
end;

initialization

RegisterFmxClasses( [TNumberColumn] );

end.

How to use?

Just create your forms as usual and create your bindings with the grid for some columns.

To get our patch running you have to take care of the uses order. The patched unit has to be after the original unit.

unit Main_ViewU;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Rtti, System.Classes,
  System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.Grid,
  FMX.Layouts, Data.Bind.GenData, Data.Bind.EngExt, FMX.Bind.DBEngExt,

  FMX.Bind.Grid, // <-- original unit
  FMX.Bind.GridAdv, // <-- patched unit

  FMX.Grid.Columns, // CustomColumns unit

  System.Bindings.Outputs, FMX.Bind.Editors,
  Data.Bind.Components, Data.Bind.Grid, Data.Bind.ObjectScope;

type
  TForm1 = class( TForm )
    Grid1 : TGrid;
    DataGeneratorAdapter1 : TDataGeneratorAdapter;
    AdapterBindSource1 : TAdapterBindSource;
    BindingsList1 : TBindingsList;
    LinkGridToDataSource1 : TLinkGridToDataSource;
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1 : TForm1;

implementation

{$R *.fmx}

end.

Last step, just set for some Columns the ColumnStyle to NumberColumn and run the program to see theses columns aligned right.

Complete Sample Project Source except the Fmx.Bind.GridAdv.pas

0
votes

I don't what your code for your cell or column is, but I would expect to see something like that below.

You can do whatever you want in MouseDown in the cell. If you'd rather handle it in the column, create an event and set it in CreateCellControl:

type TDateCell = class(TCalendarEdit)
  protected
    procedure MouseDown(Button: TMouseButton;Shift: TShiftState;X, Y: Single);override;
  end;

type TDateColumn = class(TColumn)
  protected
    function CreateCellControl: TStyleControl;override;
  end;

procedure TDateCell.MouseDown(Button: TMouseButton;Shift: TShiftState;X, Y: Single)
begin
  inherited;
  //Custom stuff here with X,Y
end;

function TDateColumn.CreateCellControl: TStyledControl;
begin
  Result := TDateCell.Create;
  Result.Parent := Self;
end;
0
votes

Livebindings control the creation of the grid columns and cells so that sub-classing the columns and bindmanager and other livebinding components is probably neither practical nor elegant. Work with what you have.

You can get access to a cell in the grid OnPainting event

Somecell := TTextCell(Grid1.Columns[Col].Children[Row]);

You probably want to set the TextCell to Visible := False and create your TCalendarEdit (Owner is the Form, Parent is the Column) in the same Position. Use the TextCell.Text to write the date to the CalendarEdit.