TGrid - FireMonkey Guide

Back to FireMonkey Library

Unit: FMX.Grid

Parents: 
TGrid: TStyledControl via TCustomGrid
TColumn: TStyledControl
Cells: See text

A FireMonkey grid is, as with other controls, simply a container for other controls. In this case those controls are of type TColumn. Columns in turn are containers for other objects which make up the cells. The cells themselves can be any descendant of TStyledControl, effectively any visible control.

FireMonkey comes with a number of pre-defined column classes:

Column classCell classCell class parent
TColumnTTextCellTEdit
TCheckColumnTCheckCellTCheckBox
TProgressColumnTProgressCellTProgressBar
TPopupColumnTPopupCellTPopupBox
TImageColumnTImageCellTImageControl
TStringColumnSee TStringGrid

All the columns have TColumn as their parent (except TColumn, of course).

Since any descendant of TStyledControl can be a grid cell it’s a fairly simple process to create a column containing any class. This will be discussed in Custom Grid Columns.

Creating Grids and Columns

Grids can, obviously, be created at design time, and you can add columns by double clicking the grid component to show the items editor.

You can create a grid and/or add columns at run-time simply by creating an instance of a column and setting it’s parent to be the grid:

procedure TForm1.Button1Click(SenderTObject);
var 
GridTGrid;
  
ColumnTColumn;
begin
  Grid 
:= TGrid.Create(Self);
  
Grid.Parent := Self;
  
  
Column := TColumn.Create(Self);
  
Grid.AddObject(Column);
  
  
//or
  
Column := TColumn.Create(Self);
  
Column.Parent := Grid;
end

Setting and Getting Cell Data

TGrid does not store any data internally. You can set a cells data and read it back, but each grid only creates the minimum number of cells it needs to display them given the current control size and row height. When a grid is scrolled cells will be recycled and you will need to set new data into those recycled cells.

TGrid actually fetches data on demand through the OnGetValue event whenever it needs to update the display of a cell. OnGetValue passes in the row and column of the cell whose data it needs and expects a TValue record to be returned containing the data.

TValue, declared in the System.RTTI unit, is a record which can store any type of data, similar to a Variant, except that it is type-safe and trying to read data of the wrong type will result in an access violation. Since TValue is a record there is no need to ‘free’ it.

A TValue uses generics to specify the data type to read or write. The example below shows an example of setting data for each of the built in cell types,

procedure TForm1.Grid1GetValue(SenderTObject; const ColRowInteger;
  var 
ValueTValue);
begin
  
if Col 0 then
    
//TTextCell
    
Value := TValue.From<String>(IntToStr(Row))
  else if 
Col 1 then
    
//TCheckCell
    
Value := TValue.From<Boolean>((Row mod 2) = 0)
  else if 
Col 2 then
    
//TProgressCell
    
Value := TValue.From<Single>(Row)
  else if 
Col 3 then
    
//TPopupColumn
    //  - doesn't accept data
  
else if Col 4 then
    
//TImageColumn
    
Value := TValue.From<TBitmap>(ImageControl1.Bitmap);
    
//Or Value := TValue.From<String>('C:/Pictures/Kitten.png');
end

The grid passes the TValue to the cell control’s SetData method (introduced by TControl) so the type of data to pass in will depend on what that method accepts. In addition to the ‘correct’ type (eg. a single for a TProgressBar) many SetData methods will parse out a string value such as the TImageControl example above taking a filename as a string.

OnCallbackGetValue

A grid can also get it’s data via a generic procedure using the OnCallBackGetValue event. This is called via the grid’s protected CallbackGetValue method and I’ll admit I’m rather unsure of it’s intended usage. It may be that it is an internal method and was exposed by accident.

If you want to use it you’ll have to assign it at run-time (it’s declared as public and thus doesn’t show up in the property editor) and you assign a value by calling the procedure which is passed in the ACallback parameter. All of which doesn’t appear to provide any benefits over using the standard OnGetValue to the end user. If you can enlighten me on this, please do.

procedure TForm1.Grid1CallbackGetValue(SenderTObject; const ColRowInteger;
  
ACallbackTProc<TValue>);
begin
  
if Col 0 then
    ACallback
(TValue.From<String>(IntToStr(Row)));
end

OnSetValue

When data in a cell changes the OnSetValue event is called, again with the column and row and a TValue containing the data. Again the example shows how to accept data for all the built in column types.

procedure TForm1.Grid1SetValue(SenderTObject;const ColRowInteger;var ValueTValue);
var 
AStringString;
  
ABooleanBoolean;
  
ASingleSingle;
begin
  
if Col 0 then
    
//TTextCell
    
AString := Value.AsType<String>
  else if 
Col 1 then
    
//TCheckCell
    
ABoolean := Value.AsType<Boolean>
  else if 
Col 2 then
    
//TProgressCell
    
ASingle := Value.AsType<Single>
  else if 
Col 3 then
    
//TPopupColumn
    //  - doesn't send data
  
else if Col 4 then
    
//TImageColumn
    //  - doesn't send data
  
;
end

And just to reiterate, because TValue is type checked it’s only possible to extract the data type it contains. If you wanted your progress cell’s value as a string,

AString := Value.As<string>; 

will give an access violation. Instead you’ll have to extract the data as a floating point number and use, for example, FloatToStr (SysUtils) to do the conversion.

Sizing

Changing the number of rows is a simple act of setting the RowCount property.

Adding columns uses the same process as that described under Creating Grids and Columns above. Deleting them is simply a case of freeing or re-parenting the appropriate column object,

Columns[1].Free;
Columns[2].Parent := Grid2

You can reorder columns using the child management methods of TFMXObject and, as we saw in the above example, you can access a column by it’s index using the Columns property. The number of columns is given by the ColumnCount property.

Navigation

Cells can be selected with the ColumnIndex (column) and Selected (row) properties. These properties can, of course, be read to determine the currently selected cell and can be used in conjunction with the OnSelChanged event which fires whenever the row or column is changed.

The topmost visible row of the grid is given in the read/write TopRow property. Changing this will modify the read only BottomRow. VisibleRows (read only) gives the number of rows on display, which will be affected by the Height and RowHeight.

ColumnByIndex is a simple remapping of the Columns property demonstrated in the Sizing section above.

ColumnByPoint and RowByPoint are useful when handling mouse events or whenever you need to find the cell at a particular screen position. The co-ordinates for the methods are relative to the top-left of the grid control. You can convert a position on the form to that of the control using the AbsoluteToLocal method of TControl (i.e. the grid), and convert screen co-ordinates to those of the form with TForm‘s ScreenToClient method.

procedure TForm1.Button1Click(SenderTObject);
var
  
IInteger;
  
PTPointF;
begin
  P 
:= Grid1.AbsoluteToLocal(ScreenToClient(TPointF.Create(200200)));
  
:= Grid1.RowByPoint(P.XP.Y);
  
ShowMessage(IntToStr(I));
end

By default grid cells can be edited. To stop this set ReadOnly to true. OnEditingDone is called, despite what it’s name may imply, whenever the contents of a cell are changed and not when the cell loses focus. To be strictly accurate it fires when the OnChange event fires for the cell control, at least for the built in column classes.

For a TTextCell it fires from OnTyping when the column’s ApplyImmediately property is true, otherwise it fires from OnChange. In the former (default) state some editing events may be missed (I noticed operations performed through the mouse menu didn’t cause the event to fire, so ApplyImmediately = false may be more reliable, but this ultimately depends on the underlying control rather than the grid). Unfortunately ApplyImmediately is of public, rather than published, scope so it can’t be changed at design time.

Appearance

The following properties are available to modify the appearance: AlternatingRowBackground (shows different colours for alternating rows, depending on the style), RowHeight (which doesn’t affect the header row), ShowSelectedCell (the current row will still be highlighted), ShowVertLines and ShowHorzLines.

Headers

If ShowHeader is true a header row will be shown containing the text from each column’s Header property. See TColumn. As far as I can tell the header height is currently hard coded and not easily modifiable.

Other

Finally the UpdateColumns method causes the columns to be updated including each visible cell’s contents (non-visible cells do not, as we’ve seen before, have any contents to be updated).;

See also

TColumn
Custom Grid Columns
TStringGrid

Version: XE4

Categories:

div title=