Jump to content
Vandrovnik

tStringGrid.OnDrawColumnCell - hot to get column field name?

Recommended Posts

Hello,

 

I have a tStringGrid linked to tIBQuery. In tStringGrid.OnDrawColumnCell, I would like to change font color or style in dependence of data in that column.

Now I am able to do something like this:

procedure TZasobaForm.grZasobaDrawColumnCell(Sender: TObject;
          const Canvas: TCanvas; const Column: TColumn; const Bounds: TRectF;
          const Row: Integer; const Value: TValue; const State: TGridDrawStates);
 begin
 Canvas.Font.Size:=grZasoba.TextSettings.Font.Size;
 if (Column<>nil)and(Column.Index=1) then begin
  Canvas.Font.Style:=[tFontStyle.fsBold];
 end;
 Canvas.Fill.Color:=TAlphaColorRec.Black;
 Canvas.FillText(Bounds, (Value.AsString), false, 1, [], TTextAlign.taLeading, TTextAlign.taCenter);
end;

But instead of using if Column.Index=..., I would like to use something like if Column.DataField='PRICE'... How can I obtain field name for the Column? Columns can be moved on runtime, so I cannot expect that Price is always second column in the grid.

In .fmx, there is:

         object grZasoba: TStringGrid
            Align = Client
            CanFocus = True
            ClipChildren = True
            Size.Width = 391.000000000000000000
            Size.Height = 208.000000000000000000
            Size.PlatformDefault = False
            TextSettings.Trimming = None
            StyledSettings = [Family, Style, FontColor]
            TabOrder = 1
            RowCount = 0
            Options = [ColumnResize, ColumnMove, ColLines, RowLines, RowSelect, AlwaysShowSelection, Tabs, Header, HeaderClick]
            DefaultDrawing = False
            OnDrawColumnCell = grZasobaDrawColumnCell
            Viewport.Width = 387.000000000000000000
            Viewport.Height = 183.000000000000000000
          end

  object BindSourceZasoba: TBindSourceDB
    DataSet = qZasoba
    ScopeMappings = <>
    Left = 88
    Top = 240
  end

  object BindingsList1: TBindingsList
    Methods = <>
    OutputConverters = <>
    Left = 20
    Top = 5
    object LinkGridToDataSourceBindSourceDB12: TLinkGridToDataSource
      Category = 'Quick Bindings'
      DataSource = BindSourceZasoba
      GridControl = grZasoba
      Columns = <
        item
          MemberName = 'PRICE'
          Width = 40
        end
        ...

 

Kind regards,

 

Karel

 

 

 

 

Share this post


Link to post
On 2/15/2020 at 3:39 AM, Vandrovnik said:

But instead of using if Column.Index=..., I would like to use something like if Column.DataField='PRICE'... How can I obtain field name for the Column?

You are on the right track.  You need to use the Column parameter.  In this case, you can use the TColumn.Header property to know which column is being drawn.

Share this post


Link to post
8 minutes ago, Remy Lebeau said:

You are on the right track.  You need to use the Column parameter.  In this case, you can use the TColumn.Header property to know which column is being drawn.

Thank you for the answer, but tColumn.Header contains DisplayLabel of the field (which will change with translations to other languages, so I cannot depend on it). There must be the field name, somewhere 🙂

Share this post


Link to post
5 hours ago, Vandrovnik said:

There must be the field name, somewhere 🙂

If there is, I haven't found it yet.  It is very easy in VCL (TColumn.Field and TColumn.FieldName), but apparently is more difficult in FMX (why? This is one of many reasons why I hate FMX, it over-complicates things that used to be easy).  I suggested using TColumn.Header after reading this answer to FMX.TGrid how to allow user to move columns without messing up the data, which shows the TColumn.Header being used in a call to TClientDataSet.FieldByName().

 

Share this post


Link to post

A string grid may or may not be connected to a database table field and hence to expect the field name reference from the grid is a folly. You could use a simple list to keep the mappings of header=field name data of find some unused text property in the column class to store the field name. 

Share this post


Link to post

Agree with NIRAV KAKU in the principle but anyway if you want to access LiveBinded fieldname of a column, fieldname is the wrong name you have to search a membername.

 

My test in ideal conditions (only one StringGrid, only Stringgrid is Binded)  

 

Consider this code, list of all columns linked

procedure TForm1.Button1Click(Sender: TObject);
var B : TContainedBindComponent;
    C : TLinkGridToDataSourceColumns;
    ib,ic : integer;
begin
  // have to search in the whole bindingslist
  for ib:=0 to BindingsList1.BindCompCount-1 do
  begin
   B:=BindingsList1.BindComps[ib];
   // check if the component is the stringgrid  
   if (B.ControlComponent=StringGrid1) then     
   begin
     // look for all linked columns
     for ic := 0 to TLinkGridToDatasource(B).Columns.Count-1 do
      begin
       C :=TLinkGridToDataSourceColumns(TLinkGridToDatasource(B).Columns);
       Memo1.lines.add(c[ic].MemberName);
      end;
   end;
  end;
end;

From this  you can write a function, something like function FieldNameOfGrid(const Grid : TCustomGrid; col : integer) : String

function TForm100.FieldNameOfGridCol(const Grid: TcustomGrid;
  const col: Integer): String;
var B : TContainedBindComponent;
    ib : integer;
begin
  for ib:=0 to BindingsList1.BindCompCount-1 do
  begin
   B:=BindingsList1.BindComps[ib];
   if (B.ControlComponent=Grid) then
     try
       result:=TLinkGridToDataSourceColumns(TLinkGridToDatasource(B).Columns)[col].MemberName;
       break; 
     except
       result:='';
     end;
  end;
end;

Perfectible I think but working, even with unlinked columns, but only if columns are defined 
image.png.38f3cdd333b9f6921a026e88d1e92b94.png

Edited by Serge_G
  • Like 1

Share this post


Link to post

I suppose this information already is stored somewhere, so I would like to find it and use it. I cannot rely on the header, but of course I could use tag, build my own list or something like that.

 

It seems that this connection between tStringGrid and data is also partly broken - well, in runtime I can move columns and set their width with mouse, but when the grid is filled again, order and width of columns are lost (this happens in my case when user selects another master record - data for details in the grid has to reload from database, columns where supposed to keep their position and width).

Share this post


Link to post

So, partial solution... Works with field names (does not depend on headers), works when columns are moved. Works, when data is reloaded from database. But does not keep column width, when user changes the width in runtime (I did not find any event for column width change).

 

// defined as private member in form:
grZasobaLink: TLinkGridToDataSource;

// I do not know how to obtain BindingsList directly from the StringGrid, so I am passing it as param
function GetLinkToDataSource(aBindingsList: tBindingsList;
         aGrid: tStringGrid): TLinkGridToDataSource;
 var a: integer;
     BindComponent: tContainedBindComponent;
 begin
 result:=nil;
 if (aBindingsList=nil)or(aGrid=nil) then exit;
 for a:=0 to aBindingsList.BindCompCount-1 do begin
  BindComponent:=aBindingsList.BindComps[a];
  if (BindComponent.ControlComponent=aGrid)and(BindComponent is tLinkGridToDatasource) then begin
   result:=tLinkGridToDatasource(BindComponent);
   exit;
  end;
 end;
end;

// keep the link, since I will need it often
procedure TZasobaForm.FormCreate(Sender: TObject);
 ...
 grZasobaLink:=GetLinkToDataSource(BindingsList1, grZasoba);
end;

procedure TZasobaForm.grZasobaDrawColumnCell(Sender: TObject;
          const Canvas: TCanvas; const Column: TColumn; const Bounds: TRectF;
          const Row: Integer; const Value: TValue; const State: TGridDrawStates);
 var FieldName: string;
 begin
 Canvas.Font.Size:=grZasoba.TextSettings.Font.Size;
 if Assigned(grZasobaLink)
  then FieldName:=UpperCase(grZasobaLink.Columns[Column.Index].MemberName) // Field name! :-)
  else FieldName:='';
 if (FieldName='FYZICKA') then begin
  Canvas.Font.Style:=[tFontStyle.fsBold];
 end;
 Canvas.Fill.Color:=TAlphaColorRec.Black;
 Canvas.FillText(Bounds, (Value.AsString), false, 1, [], TTextAlign.Leading, TTextAlign.Center);
end;

// handle column move
procedure TZasobaForm.grZasobaColumnMoved(Column: TColumn;
          FromIndex, ToIndex: Integer);
 begin
 if Assigned(grZasobaLink) then grZasobaLink.Columns[FromIndex].Index:=ToIndex;
end;

 

Share this post


Link to post
5 hours ago, Serge_G said:

Agree with NIRAV KAKU in the principle but anyway if you want to access LiveBinded fieldname of a column, fieldname is the wrong name you have to search a membername.

Thank you for helping me to get the right way!

Share this post


Link to post
20 hours ago, Vandrovnik said:

Thank you for helping me to get the right way!

A pleasure 😉 .

But this still does not satisfy me fully, so I persist.

My goal was to have the members linked even if no columns were defined, I found a way applying the principle : "if no columns are defined then all fields of the datasource are displayed".

Having the linked datasource is easy TLinkGridToDatasource(B).DataSource, having the member list not so but I found that TBaseObjectBindSource have an interface IScopeMemberNames

private

  ColumnList : TStringList;


procedure TForm1.Button1Click(Sender: TObject);
var B : TContainedBindComponent;
    SL : TStringList;
    S : String;
    IMembers : IScopeMemberNames;
    ib : integer;
begin
  for ib:=0 to BindingsList1.BindCompCount-1 do
  begin
   B:=BindingsList1.BindComps[ib];
   if (B.ControlComponent is TCustomGrid) then
   begin
      if TBaseObjectBindSource(TLinkGridToDatasource(B).DataSource).GetInterface(IScopeMemberNames,IMembers)
      then begin
      IMembers.GetMemberNames(ColumnList);
      for S in ColumnList do
       Memo1.Lines.Add(S);
      end;
   end;

  end;
end;

Now it's just a question of using a StringList to store the fieldnames :classic_happy: and to arrange it  function of the grid (defined columns, and move), more "classic code" I think even if, at this time, I have no idea (Sunday not propitious to :classic_laugh:)  

 

P.S.

Quote

But does not keep column width, when user changes the width in runtime

The only way I think about is to have a sort of SaveGridSettings / LoadGridSettings procedure

something using ObjectBinaryToText  and reverse

function SaveGridSettings(Component: TComponent): string;
var
  BinStream:TMemoryStream;
  StrStream: TStringStream;
  s: string;
begin
  BinStream := TMemoryStream.Create;
  try
    StrStream := TStringStream.Create(s);
    try
      BinStream.WriteComponent(Component);
      BinStream.Seek(0, soFromBeginning);
      ObjectBinaryToText(BinStream, StrStream);
      StrStream.Seek(0, soFromBeginning);
      Result:= StrStream.DataString;
    finally
      StrStream.Free;
    end;
  finally
    BinStream.Free
  end;
end;

 

 

 

 

Edited by Serge_G
PS

Share this post


Link to post

You can simplify all this, at design time the link is well known and named (LinkGridtodatasourcexxxxxx).

Just rename your link in the BindingsList to grZasobaLink

 

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

×