Vandrovnik 214 Posted February 15, 2020 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
Remy Lebeau 1397 Posted February 18, 2020 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
Vandrovnik 214 Posted February 18, 2020 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
Remy Lebeau 1397 Posted February 19, 2020 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
NIRAV KAKU 0 Posted February 22, 2020 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
Serge_G 87 Posted February 22, 2020 (edited) 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 Edited February 22, 2020 by Serge_G 1 Share this post Link to post
Vandrovnik 214 Posted February 22, 2020 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
Vandrovnik 214 Posted February 22, 2020 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
Vandrovnik 214 Posted February 22, 2020 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
Serge_G 87 Posted February 23, 2020 (edited) 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 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 ) 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 February 23, 2020 by Serge_G PS Share this post Link to post
Serge_G 87 Posted February 24, 2020 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