When fetching a large number of records, you may run into an out-of-memory error, even though cxGrid itself can handle several times as many records, including in a 32-bit application. Replacing Delphi’s standard memory manager with Fast Memory Manager can help: in my case, FastMM4 increased the maximum number of records I could fetch by a factor of five.
Here are some cxGrid techniques collected from various sources that I have found useful in my own work. (Updated October 6, 2026)
Appearance
You can set cell colors programmatically in the OnCustomDrawCell event:
if (AViewInfo.GridRecord.Values[TableViewColumn1.Index])=1 then begin ACanvas.Brush.Color := clRed; ACanvas.Font.Style := [fsBold]; end;
To color only specific columns rather than the entire row, identify the column in the OnCustomDrawCell handler like this:
if TableView.Columns[AViewInfo.Item.Index].DataBinding.FieldName='COLUMN_1' then // Alternative: if AViewInfo.Item = TableViewCOLUMN_1 then
Bear in mind that custom-drawn colors are not included in an Excel export, and selection highlighting can also cause problems. To preserve the formatting, assign a predefined style in a handler such as TableViewStylesGet***Style:
procedure TMoneyMoveDetailForm.TableViewStylesGetContentStyle(Sender: TcxCustomGridTableView; ARecord: TcxCustomGridRecord; AItem: TcxCustomGridTableItem; out AStyle: TcxStyle); begin if not ARecord.IsData then Exit; // (AItem as TcxGridDBBandedColumn) to identify the current column if (ARecord.Values[TableViewID_MONEY.Index])=null then begin AStyle :=cxStyleBold; end; end;
Displaying an image or icon in a cell
procedure TMsgForm.TableViewIMAGECustomDrawCell(Sender: TcxCustomGridTableView; ACanvas: TcxCanvas; AViewInfo: TcxGridTableDataCellViewInfo; var ADone: Boolean); Var r : TRect; iImageIndex : Integer; begin If (AViewInfo.GridRecord.Values[TableViewIMAGE.Index] = 1) then begin R := AViewInfo.Bounds; ACanvas.Brush.Color := AViewInfo.Params.Color; ACanvas.FillRect(R); // draw the image. R := AViewInfo.Bounds; Inc(r.Top,1); ACanvas.DrawImage(ImageList,r.Left,r.Top,20,True); ADone := True ; end; end;
Another option is:
procedure TFilesForm.TableViewIconCustomDrawCell(
Sender: TcxCustomGridTableView; ACanvas: TcxCanvas;
AViewInfo: TcxGridTableDataCellViewInfo; var ADone: Boolean);
begin
inherited;
with AViewInfo.ClientBounds do
cxImageList.Draw(ACanvas.Canvas, Left + 1, Top + 1, 0);
ADone := True;
end;
You can also choose ButtonEdit in the cell’s Properties and add one or more buttons. Assigning an Action to a button makes it functional, provided editing is enabled. The drawback is that the cell remains a text editor.
Using different colors for different grouping levels
procedure TPozSkladForm.TableViewStylesGetGroupStyle( Sender: TcxGridTableView; ARecord: TcxCustomGridRecord; ALevel: Integer; out AStyle: TcxStyle); begin if ALevel=0 then AStyle := cxStyleL0; // $00A0A0A0 if ALevel=1 then AStyle := cxStyleL1; // clSilver if ALevel=2 then AStyle := cxStyleL2; // $00E0E0E0 end;
One drawback of styles is that the focused-cell highlight can obscure the colors. To work around this, draw the row selection yourself in TableViewCustomDrawCell, for example with CellSelect=True:
if AViewInfo.GridRecord.Selected then begin
if AViewInfo.Selected then begin // white text for the focused cell
ACanvas.Font.Color := clWhite;
end else begin // blue text for the rest of the row, preserving cell backgrounds
ACanvas.Font.Color := clBlue;
ACanvas.Font.Style := [fsBold];
end;
end;
Watch out: accessing row data through GridRecord and a column index may not return what you expect when rows are grouped: the row being drawn may be a group header.
Displaying data: filtering, grouping and sorting
To hide the GroupByBox in TcxGrid—the area above the grid that says “drag a column header here to group by…”—use:
TableView.OptionsView.GroupByBox := false;
Setting a filter programmatically
TableView.DataController.Filter.BeginUpdate; TableView.DataController.Filter.Root.Clear; TableView.DataController.Filter.Root.AddItem(TableViewColumn1, cxFilter.foLike, BegString+"%", BegString+"%"); TableView.DataController.Filter.Active:=true; TableView.DataController.Filter.EndUpdate
When adding more than one condition, specify the Boolean operator before adding the next one. The default is AND; to use OR, set TableView.DataController.Filter.Root.BoolOperatorKind := fboOr;
Preserving the grid position after refreshing data
TableView.BeginUpdate(); r := TableView.Controller.TopRowIndex; f := TableView.Controller.FocusedRowIndex; GridQuery.Close; // close and reopen the dataset GridQuery.Open; TableView.Controller.TopRowIndex := r; TableView.Controller.FocusedRowIndex := f; TableView.EndUpdate;
Expanding or collapsing groups programmatically
TableView.DataController.Groups.FullCollapse; // Collapse TableView.DataController.Groups.FullExpand; // Expand TableView.DataController.Options := TableView.DataController.Options + [dcoGroupsAlwaysExpanded]; // Keep groups expanded
Adding or removing grouping programmatically
TableViewColumn1.GroupIndex := 1; // Group by this column TableView.DataController.Groups.ClearGrouping; // Remove grouping
Accessing grid data and metadata
Iterating over all visible rows
for I:=0 to cxGrid.DataController.FilteredRecordCount - 1 do begin cxGrid.DataController.Values[cxGrid.DataController.FilteredRecordIndex[i],YourColumnName.Index])
Iterating over selected rows and retrieving each record’s identifier
for i := 0 to GridView1.Controller.SelectedRecordCount-1 do begin ID_RASHODD := GridView1.DataController.Values[GridView1.Controller.SelectedRecords[i].RecordIndex, GridView1ID_RASHODD.Index]; end;
To find a grid column by its field name:
TableView1.GetColumnByFieldName('FIELD_NAME')
For TcxGridTableView and TcxGridBandedTableView without the “DB” prefix, you can add and remove data manually. For example:
// Delete all rows while TableView.DataController.RowCount>0 do TableView.DataController.DeleteRecord(0); // Add a row and set values using the appropriate types r := TableView.DataController.AppendRecord; TableView.DataController.SetValue(r, TableViewColumn1.Index, 'test1'); TableView.DataController.SetValue(r, TableViewColumn2.Index, 'test2'); TableView.DataController.SetValue(r, TableViewColumn3.Index, 12345);
Editing data in the grid
Populating a ComboBox in a cell
Choose ComboBox in Properties and populate its list in the dataset’s AfterScroll event. When I tried doing this in onInitPopUp, the previous list would appear. Readers pointed out in the comments that I was doing it incorrectly. Here is an example of populating the list:
TcxComboBoxProperties(TableViewCNAME.Properties).Items.Clear; while not LookupQuery.Eof do begin TcxComboBoxProperties(TableViewCNAME.Properties).Items.Add(LookupQuery['CNAME']); LookupQuery.Next; end;
Check and process the user’s selection in OnValidate, for example:
if LookupQuery.Locate('CNAME',DisplayValue,[]) then begin
// save to the database here
TableView.BeginUpdate;
// refresh the grid if needed
TableView.EndUpdate;
end else begin
ErrorText := 'Invalid selection';
Error := True;
end;
TcxComboBoxProperties(TableViewCNAME.Properties).Items.Clear;
Editing a number in the grid
Choose CalcEdit in Properties. In the OnEditValueChanged handler, use (Sender as TcxCalcEdit).Value and the dataset’s current record.
Detecting a right-click on a particular column
In the OnCellClick event:
if ACellViewInfo.Item.Name='DzTableViewNRM_NAME' then if AButton=mbRight then
Copying a cell’s displayed value to the clipboard
You can read the current cell with <TcxGridDBTableView>.Controller.FocusedRow.Values[FocusedColumn.Index]. However, for a column using LookupComboBox, this returns the key rather than the displayed text. The copying routine needs to account for that and call the hidden GetDisplayLookupText method. To access it, declare a descendant of TcxLookupComboBoxProperties:
type
TcxLookupComboBoxPropertiesAccess = class(TcxLookupComboBoxProperties); // to access .GetDisplayLookupText
// later in the code
with <TcxGridDBTableView>.Controller do begin
if FocusedColumn.Properties is TcxLookupComboboxProperties then begin
Clipboard.AsText := (TcxLookupComboBoxPropertiesAccess(FocusedColumn.Properties).GetDisplayLookupText(FocusedRow.Values[FocusedColumn.Index]));
end else begin
Clipboard.AsText := FocusedRow.Values[FocusedColumn.Index];
end;
end;
Finding what is under the pointer in MouseUp/MouseDown
procedure TPlanForm.TableView1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
AHitTest: TcxCustomGridHitTest;
begin
inherited;
AHitTest := (Sender as TcxGridSite).GridView.ViewInfo.GetHitTest(X,Y); // Possible types: // TcxGridColumnHeaderHitTest, TcxGridRecordCellHitTest, TcxGridGroupByBoxHitTest
if AHitTest is TcxGridColumnHeaderHitTest then begin
if TcxGridColumnHeaderHitTest(AHitTest).Column = TableView1COLUMN1 then begin
// First column header clicked; setting sorting here is a bad idea
end;
end;
end;
To process a checkbox change in a cell, use the OnChange event:
if (Sender as TcxCheckBox).Checked then
Finding the column currently being edited
TableView.VisibleColumns[TableView.Controller.FocusedColumnIndex].DataBinding.FieldName
Updating other cells in OnEditValueChanged
TableView.DataController.SetEditValue(TableViewFIELD_NAME.Index, new_Value, evsValue );
For some reason this approach does not always work for me. Here is another way:
TableView1.DataController.DataSource.DataSet.Edit;
TableView1.DataController.DataSource.DataSet.FieldByName('FIELD_NAME').Value := new_Value;
TableView1.DataController.DataSource.DataSet.Post;
Dragging and dropping onto grid rows
procedure TForm1.cxGrid2DBTableView1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
// Start dragging from the source grid (Auto is another option)
if ((Button = mbLeft) and (ssAlt in Shift)) or (ssRight in Shift) then
if (TcxGridSite(Sender).ViewInfo.GetHitTest(X, Y).HitTestCode in [htCell, htRecord])
then TcxGridSite(Sender).BeginDrag(False);
end;
var
ARecordIndex: Integer;
procedure TForm1.cxGrid2DBTableView1StartDrag(Sender: TObject; var DragObject: TDragObject);
var
AGridView: TcxGridDBTableView;
begin
// Get ARecordIndex of the dragged record
AGridView := TcxGridSite(Sender).GridView as TcxGridDBTableView;
ARecordIndex := AGridView.Controller.FocusedRecordIndex;
end;
procedure TForm1.cxGrid2DBTableView1DragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean);
begin
// Allow the drop
Accept := Source is TcxDragControlObject;
end;
procedure TForm1.cxGrid2DBTableView1DragDrop(Sender, Source: TObject; X, Y: Integer);
begin
// Display text from the drop target row
self.Caption := TcxGridRecordCellHitTest(TcxGridSite(Sender).ViewInfo.GetHitTest(X, Y)).GridRecord.Values[cxGrid1DBTableView1FULLNAME.Index];
end;
Changing the grid at runtime
Adding a column at runtime:
var XCol : TcxGridDBBandedColumn; begin XCol := TableView.CreateColumn; XCol.Position.BandIndex := 1; XCol.DataBinding.FieldName:='quant'+sl[i]; XCol.DataBinding.ValueType := 'Float'; XCol.Caption := ss[i]; XCol.Width := 50; XCol.Tag := StrToInt(sl[i]); XCol.Summary.FooterKind := skSum; XCol.Summary.GroupFooterKind := skSum; XCol.Summary.GroupKind := skSum; XCol.Summary.FooterFormat := '0.##'; XCol.Summary.GroupFooterFormat := '0.##'; XCol.Summary.GroupFormat := '0.##';
Deleting a column at runtime
TableView.Columns[i].Destroy;
Adding bands:
b0 := TableView.Bands.Add; b0.Position.BandIndex := b.Index;
Using a custom formula for FooterSummary
To calculate a footer summary using values from other footer summaries, use DataControllerSummaryAfterSummary. Doing this in the cell’s OnGetText event will not work because the summaries have not yet been calculated when that event runs.
procedure T*****Form.TableViewDataControllerSummaryAfterSummary(ASender: TcxDataSummary); var itog_summa, kmsumma :Variant; begin inherited; itog_summa := ASender.FooterSummaryValues[ASender.FooterSummaryItems.IndexOfItemLink(TableViewITOG_SUMMA)]; kmsumma := ASender.FooterSummaryValues[ASender.FooterSummaryItems.IndexOfItemLink(TableViewKMSUMMA)]; ASender.FooterSummaryValues[ASender.FooterSummaryItems.IndexOfItemLink(TableViewPERCENT)] := itog_summa + kmsumma; end;
For GroupSummary, you need to walk through all grouping levels recursively and calculate the required formula for each group’s summaries. See this implementation example.
Bugs and workarounds
With a component this complex, you will occasionally run into bugs. In some cases, the footer totals lost their leading digits: for example, 14.8 appeared as 4.8, regardless of the specified format. I worked around this by calculating the total myself in the footer summary’s OnGetText event:
procedure T****Form.TableViewTcxGridDBDataControllerTcxDataSummaryFooterSummaryItems3GetText(
Sender: TcxDataSummaryItem; const AValue: Variant; AIsFooter: Boolean;
var AText: String);
var
s : Double;
i : Integer;
begin
s := 0;
for i:=0 to TableView.DataController.FilteredRecordCount - 1 do begin
try
s := s + TableView.DataController.GetValue(TableView.DataController.FilteredRecordIndex[i], Sender.Field.Index);
except
end
end;
AText := FormatFloat(Sender.Format, s);
end;
That was my old workaround. In 2026 I finally tracked down the cause: a bug in Delphi 7’s standard RTL! I describe it in this article.
TcxLookupComboBox
Showing a hint inside the input field with TextHint. The component has this property, but it is hidden. To expose it, declare a descendant class:
type TcxLookupComboBoxAccess = class(TcxLookupComboBox); // to access the hidden .TextHint property // Then assign the hint, for example in OnCreate: TcxLookupComboBoxAccess(MyLookupCoboBox).TextHint := 'Hint text';
Adding your own button to the input field, for example to open or edit a lookup list. This also works for other controls that have Properties; see the control documentation.
with MycxLookupComboBox.Properties do begin
Images := cxImageList;
Result := Buttons.Add;
Result.Default := True;
Result.Kind := bkGlyph;
Result.LeftAlignment := False;
Result.Action := Action; // The action to execute
end;

Добрый день!
Не подскажите, как можно сохранить настройки и расположение колонок в реестре или ini? В dxMasterView это было из коробки, а вот в cxGrid я не нашел такого способа. Смысл, чтобы сохранить стили, размеры и порядок колонок в cxGridTableView.
Юзайте TcxPropertiesStore, но в гридах вьюшку надо заменить на Banded , т.е. вместо например TcxDBGridTableView , надо заменить на TCxDbBandedTableView. Это надо что бы было доступно свойство position (т.е. расположение столбца в гриде)
Отличная статья! У меня задача: заменить значения чисел на текстовые значения. Спасибо.
Спасибо вам за статью.
Но есть замечание относительно пункта “Редактирование данных пользователем”, подпункт “Организация списка ComboBox в ячейке”. На самом деле в OnInitPopup тоже можно сделать выпадающий список и он будет актуальным, нужно просто обращаться не к столбцу, а к Sender’-у. Что-то типа этого:
procedure TrdbXXX.YYYPropertiesInitPopup( Sender: TObject); begin if XQuery.Active then begin (Sender as TcxComboBox).Properties.Items.Clear; XQuery.First; while notXQuery.Eof do begin (Sender as TcxComboBox).Properties.Items.Add(XQuery['FieldName']); XQuery.Next; end; end; end;a7in: Спасибо за замечание!