Sin categorizar

Guía rápida de DevExpress cxGrid y Delphi 7

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;

Comentarios 4

  1. Добрый день!
    Не подскажите, как можно сохранить настройки и расположение колонок в реестре или ini? В dxMasterView это было из коробки, а вот в cxGrid я не нашел такого способа. Смысл, чтобы сохранить стили, размеры и порядок колонок в cxGridTableView.

    1. Юзайте TcxPropertiesStore, но в гридах вьюшку надо заменить на Banded , т.е. вместо например TcxDBGridTableView , надо заменить на TCxDbBandedTableView. Это надо что бы было доступно свойство position (т.е. расположение столбца в гриде)

  2. Отличная статья! У меня задача: заменить значения чисел на текстовые значения. Спасибо.

  3. Спасибо вам за статью.
    Но есть замечание относительно пункта «Редактирование данных пользователем», подпункт «Организация списка 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: Спасибо за замечание!

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *