Al recuperar muchos registros, puede aparecer un error de memoria insuficiente, aunque cxGrid sea capaz de manejar varias veces más registros, incluso en una aplicación de 32 bits. Sustituir el gestor de memoria estándar de Delphi por Fast Memory Manager puede ayudar: en mi caso, FastMM4 multiplicó por cinco el número máximo de registros que podía recuperar.
Estas son algunas técnicas de trabajo con cxGrid que he recopilado de distintas fuentes y que me han resultado útiles. (Actualizado el 6 de octubre de 2026)
Aspecto visual
Los colores de las celdas se pueden establecer mediante código en el evento OnCustomDrawCell:
if (AViewInfo.GridRecord.Values[TableViewColumn1.Index])=1 then begin ACanvas.Brush.Color := clRed; ACanvas.Font.Style := [fsBold]; end;
Para colorear solo determinadas columnas, en lugar de toda la fila, se puede identificar la columna en el manejador de OnCustomDrawCell así:
if TableView.Columns[AViewInfo.Item.Index].DataBinding.FieldName='COLUMN_1' then // Alternativa: if AViewInfo.Item = TableViewCOLUMN_1 then
Hay que tener en cuenta que estos colores personalizados no se conservan al exportar a Excel, y que el resaltado de selección también puede dar problemas. Para conservar el formato, asigne un estilo predefinido en un manejador como TableViewStylesGet***Style:
procedure TMoneyMoveDetailForm.TableViewStylesGetContentStyle(Sender: TcxCustomGridTableView; ARecord: TcxCustomGridRecord; AItem: TcxCustomGridTableItem; out AStyle: TcxStyle); begin if not ARecord.IsData then Exit; // (AItem as TcxGridDBBandedColumn) para identificar la columna actual if (ARecord.Values[TableViewID_MONEY.Index])=null then begin AStyle :=cxStyleBold; end; end;
Mostrar una imagen o un icono en una celda
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;
Otra opción es:
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;
También se puede elegir ButtonEdit en Properties de la celda y añadir uno o varios botones. Al asignar una Action a un botón, este tendrá una función, siempre que esté habilitada la edición. El inconveniente es que la celda sigue siendo un editor de texto.
Usar colores distintos para los diferentes niveles de agrupación
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;
Un inconveniente de los estilos es que el resaltado de la celda activa puede ocultar sus colores. Para evitarlo, se puede dibujar la selección de la fila en TableViewCustomDrawCell, por ejemplo con CellSelect=True:
if AViewInfo.GridRecord.Selected then begin
if AViewInfo.Selected then begin // texto blanco en la celda activa
ACanvas.Font.Color := clWhite;
end else begin // texto azul en el resto de la fila, conservando los fondos
ACanvas.Font.Color := clBlue;
ACanvas.Font.Style := [fsBold];
end;
end;
Atención: al acceder a los datos de una fila mediante GridRecord y el índice de columna, puede que no se obtengan los valores esperados si hay filas agrupadas: la fila que se está dibujando podría ser una cabecera de grupo.
Presentación de datos: filtros, agrupación y ordenación
Para ocultar GroupByBox en TcxGrid, es decir, la zona encima de la rejilla que dice “drag a column header here to group by…”, utilice:
TableView.OptionsView.GroupByBox := false;
Establecer un filtro mediante código
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
Si se añade más de una condición, conviene indicar el operador lógico antes de añadir la siguiente. El valor predeterminado es AND; para usar OR, establezca TableView.DataController.Filter.Root.BoolOperatorKind := fboOr;
Conservar la posición en la rejilla al actualizar los datos
TableView.BeginUpdate(); r := TableView.Controller.TopRowIndex; f := TableView.Controller.FocusedRowIndex; GridQuery.Close; // cerrar y abrir el conjunto de datos GridQuery.Open; TableView.Controller.TopRowIndex := r; TableView.Controller.FocusedRowIndex := f; TableView.EndUpdate;
Expandir o contraer grupos mediante código
TableView.DataController.Groups.FullCollapse; // Contraer TableView.DataController.Groups.FullExpand; // Expandir TableView.DataController.Options := TableView.DataController.Options + [dcoGroupsAlwaysExpanded]; // Mantener los grupos expandidos
Añadir o quitar agrupaciones mediante código
TableViewColumn1.GroupIndex := 1; // Agrupar por esta columna TableView.DataController.Groups.ClearGrouping; // Quitar la agrupación
Acceder a los datos y metadatos de la rejilla
Recorrer todas las filas visibles
for I:=0 to cxGrid.DataController.FilteredRecordCount - 1 do begin cxGrid.DataController.Values[cxGrid.DataController.FilteredRecordIndex[i],YourColumnName.Index])
Recorrer las filas seleccionadas y obtener el identificador de cada registro
for i := 0 to GridView1.Controller.SelectedRecordCount-1 do begin ID_RASHODD := GridView1.DataController.Values[GridView1.Controller.SelectedRecords[i].RecordIndex, GridView1ID_RASHODD.Index]; end;
Para obtener una columna por el nombre de su campo:
TableView1.GetColumnByFieldName('FIELD_NAME')
En TcxGridTableView y TcxGridBandedTableView, sin el prefijo “DB”, se pueden añadir y eliminar datos manualmente. Por ejemplo:
// Eliminar todas las filas while TableView.DataController.RowCount>0 do TableView.DataController.DeleteRecord(0); // Añadir una fila y asignar valores según su tipo 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);
Edición de datos en la rejilla
Rellenar un ComboBox dentro de una celda
Seleccione ComboBox en Properties y rellene la lista en el evento AfterScroll del conjunto de datos. Cuando intentaba hacerlo en onInitPopUp, aparecía la lista anterior. En los comentarios me señalaron que lo estaba haciendo mal. Ejemplo de cómo rellenarla:
TcxComboBoxProperties(TableViewCNAME.Properties).Items.Clear; while not LookupQuery.Eof do begin TcxComboBoxProperties(TableViewCNAME.Properties).Items.Add(LookupQuery['CNAME']); LookupQuery.Next; end;
La selección del usuario se comprueba y se procesa en OnValidate, por ejemplo:
if LookupQuery.Locate('CNAME',DisplayValue,[]) then begin
// guardar en la base de datos
TableView.BeginUpdate;
// actualizar la rejilla si hace falta
TableView.EndUpdate;
end else begin
ErrorText := 'Selección no válida';
Error := True;
end;
TcxComboBoxProperties(TableViewCNAME.Properties).Items.Clear;
Editar un número en la rejilla
Seleccione CalcEdit en Properties. En el manejador de OnEditValueChanged, utilice (Sender as TcxCalcEdit).Value y el registro actual del conjunto de datos.
Detectar un clic derecho en una columna concreta
En el evento OnCellClick:
if ACellViewInfo.Item.Name='DzTableViewNRM_NAME' then if AButton=mbRight then
Copiar al portapapeles el texto mostrado en una celda
El valor de la celda actual se puede obtener con <TcxGridDBTableView>.Controller.FocusedRow.Values[FocusedColumn.Index]. Sin embargo, en las columnas con LookupComboBox se obtiene la clave, no el texto visible. La función de copia debe tenerlo en cuenta y llamar al método oculto GetDisplayLookupText. Para acceder a él, declare una clase derivada de TcxLookupComboBoxProperties:
type
TcxLookupComboBoxPropertiesAccess = class(TcxLookupComboBoxProperties); // para acceder a .GetDisplayLookupText
// más adelante en el código
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;
Identificar qué hay bajo el puntero en 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); // Tipos posibles: // TcxGridColumnHeaderHitTest, TcxGridRecordCellHitTest, TcxGridGroupByBoxHitTest
if AHitTest is TcxGridColumnHeaderHitTest then begin
if TcxGridColumnHeaderHitTest(AHitTest).Column = TableView1COLUMN1 then begin
// Clic en la cabecera de la primera columna; no conviene ordenar aquí
end;
end;
end;
Para procesar el cambio de una casilla de verificación en una celda, utilice el evento OnChange:
if (Sender as TcxCheckBox).Checked then
Identificar la columna que se está editando
TableView.VisibleColumns[TableView.Controller.FocusedColumnIndex].DataBinding.FieldName
Actualizar otras celdas desde OnEditValueChanged
TableView.DataController.SetEditValue(TableViewFIELD_NAME.Index, new_Value, evsValue );
Por algún motivo, este método no siempre me funciona. También se puede hacer así:
TableView1.DataController.DataSource.DataSet.Edit;
TableView1.DataController.DataSource.DataSet.FieldByName('FIELD_NAME').Value := new_Value;
TableView1.DataController.DataSource.DataSet.Post;
Arrastrar y soltar sobre filas de la rejilla
procedure TForm1.cxGrid2DBTableView1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
// Iniciar el arrastre desde la rejilla de origen (también se puede activar Auto)
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
// Obtener ARecordIndex del registro arrastrado
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
// Permitir soltar
Accept := Source is TcxDragControlObject;
end;
procedure TForm1.cxGrid2DBTableView1DragDrop(Sender, Source: TObject; X, Y: Integer);
begin
// Mostrar texto de la fila de destino
self.Caption := TcxGridRecordCellHitTest(TcxGridSite(Sender).ViewInfo.GetHitTest(X, Y)).GridRecord.Values[cxGrid1DBTableView1FULLNAME.Index];
end;
Modificar la rejilla en tiempo de ejecución
Añadir una columna en tiempo de ejecución:
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.##';
Eliminar una columna en tiempo de ejecución
TableView.Columns[i].Destroy;
Añadir bandas:
b0 := TableView.Bands.Add; b0.Position.BandIndex := b.Index;
Usar una fórmula personalizada para FooterSummary
Para calcular un resumen del pie utilizando valores de otros resúmenes del pie, utilice DataControllerSummaryAfterSummary. No se puede hacer en el evento OnGetText de la celda, porque en ese momento los resúmenes todavía no se han calculado.
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;
Para GroupSummary, hay que recorrer recursivamente todos los niveles de agrupación y calcular la fórmula correspondiente para los resúmenes de cada grupo. Véase este ejemplo de implementación.
Errores y soluciones alternativas
En un componente tan complejo, es inevitable encontrarse algún error. En ciertos casos, los totales del pie perdían las cifras iniciales: por ejemplo, aparecía 4.8 en lugar de 14.8, independientemente del formato elegido. Lo resolví provisionalmente calculando la suma por mi cuenta en el evento OnGetText del resumen del pie:
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;
Esa era mi solución anterior. En 2026 encontré por fin la causa: ¡un fallo en la biblioteca RTL estándar de Delphi 7! Lo explico en este artículo.
TcxLookupComboBox
Mostrar un texto de ayuda dentro del campo con TextHint. El componente tiene esta propiedad, pero está oculta. Para hacerla accesible, declare una clase derivada:
type TcxLookupComboBoxAccess = class(TcxLookupComboBox); // para acceder a la propiedad oculta .TextHint // Asignar el texto de ayuda, por ejemplo en OnCreate: TcxLookupComboBoxAccess(MyLookupCoboBox).TextHint := 'Texto de ayuda';
Añadir un botón propio al campo de entrada, por ejemplo para abrir o editar una lista de consulta. También funciona con otros controles que tienen Properties; véase la documentación de los controles.
with MycxLookupComboBox.Properties do begin
Images := cxImageList;
Result := Buttons.Add;
Result.Default := True;
Result.Kind := bkGlyph;
Result.LeftAlignment := False;
Result.Action := Action; // La acción que se ejecutará
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: Спасибо за замечание!