Lorsqu’on récupère un grand nombre d’enregistrements, une erreur de mémoire insuffisante peut survenir, alors que cxGrid est capable d’en traiter plusieurs fois davantage, même dans une application 32 bits. Remplacer le gestionnaire de mémoire standard de Delphi par Fast Memory Manager peut aider : dans mon cas, FastMM4 a multiplié par cinq le nombre maximal d’enregistrements que je pouvais récupérer.
Voici quelques techniques pour utiliser cxGrid, recueillies auprès de différentes sources et qui m’ont été utiles dans mes projets. (Mis à jour le 6 octobre 2026)
Apparence
On peut définir les couleurs des cellules par programmation dans l’événement OnCustomDrawCell :
if (AViewInfo.GridRecord.Values[TableViewColumn1.Index])=1 then begin ACanvas.Brush.Color := clRed; ACanvas.Font.Style := [fsBold]; end;
Pour colorer seulement certaines colonnes plutôt que toute la ligne, on peut identifier la colonne dans le gestionnaire OnCustomDrawCell ainsi :
if TableView.Columns[AViewInfo.Item.Index].DataBinding.FieldName='COLUMN_1' then // Autre possibilité : if AViewInfo.Item = TableViewCOLUMN_1 then
Attention : les couleurs appliquées par dessin personnalisé ne sont pas conservées lors de l’export vers Excel, et la mise en évidence de la sélection peut également poser problème. Pour conserver la mise en forme, affectez un style prédéfini dans un gestionnaire tel que TableViewStylesGet***Style :
procedure TMoneyMoveDetailForm.TableViewStylesGetContentStyle(Sender: TcxCustomGridTableView; ARecord: TcxCustomGridRecord; AItem: TcxCustomGridTableItem; out AStyle: TcxStyle); begin if not ARecord.IsData then Exit; // (AItem as TcxGridDBBandedColumn) pour identifier la colonne courante if (ARecord.Values[TableViewID_MONEY.Index])=null then begin AStyle :=cxStyleBold; end; end;
Afficher une image ou une icône dans une cellule
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;
Autre possibilité :
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;
On peut aussi choisir ButtonEdit dans les Properties de la cellule et ajouter un ou plusieurs boutons. Affecter une Action à un bouton le rend fonctionnel, à condition que l’édition soit activée. L’inconvénient est que la cellule reste un éditeur de texte.
Utiliser des couleurs différentes selon le niveau de regroupement
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 inconvénient des styles est que la mise en évidence de la cellule active peut masquer les couleurs. Pour contourner cela, on peut dessiner soi-même la sélection de la ligne dans TableViewCustomDrawCell, par exemple avec CellSelect=True :
if AViewInfo.GridRecord.Selected then begin
if AViewInfo.Selected then begin // texte blanc dans la cellule active
ACanvas.Font.Color := clWhite;
end else begin // texte bleu dans le reste de la ligne, sans masquer les fonds
ACanvas.Font.Color := clBlue;
ACanvas.Font.Style := [fsBold];
end;
end;
Attention : accéder aux données d’une ligne par GridRecord et l’indice de colonne ne donne pas toujours les valeurs attendues lorsque les lignes sont regroupées : la ligne dessinée peut être un en-tête de groupe.
Affichage des données : filtres, regroupement et tri
Pour masquer le GroupByBox de TcxGrid, c’est-à-dire la zone au-dessus de la grille portant le texte « drag a column header here to group by… », utilisez :
TableView.OptionsView.GroupByBox := false;
Définir un filtre par programmation
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 vous ajoutez plusieurs conditions, précisez l’opérateur logique avant d’ajouter la suivante. Par défaut, il s’agit de AND ; pour utiliser OR, définissez TableView.DataController.Filter.Root.BoolOperatorKind := fboOr;
Conserver la position dans la grille après actualisation
TableView.BeginUpdate(); r := TableView.Controller.TopRowIndex; f := TableView.Controller.FocusedRowIndex; GridQuery.Close; // fermer puis rouvrir le jeu de données GridQuery.Open; TableView.Controller.TopRowIndex := r; TableView.Controller.FocusedRowIndex := f; TableView.EndUpdate;
Développer ou réduire les groupes par programmation
TableView.DataController.Groups.FullCollapse; // Réduire TableView.DataController.Groups.FullExpand; // Développer TableView.DataController.Options := TableView.DataController.Options + [dcoGroupsAlwaysExpanded]; // Maintenir les groupes développés
Ajouter ou supprimer un regroupement par programmation
TableViewColumn1.GroupIndex := 1; // Regrouper selon cette colonne TableView.DataController.Groups.ClearGrouping; // Supprimer le regroupement
Accéder aux données et métadonnées de la grille
Parcourir toutes les lignes visibles
for I:=0 to cxGrid.DataController.FilteredRecordCount - 1 do begin cxGrid.DataController.Values[cxGrid.DataController.FilteredRecordIndex[i],YourColumnName.Index])
Parcourir les lignes sélectionnées et récupérer l’identifiant de chaque enregistrement
for i := 0 to GridView1.Controller.SelectedRecordCount-1 do begin ID_RASHODD := GridView1.DataController.Values[GridView1.Controller.SelectedRecords[i].RecordIndex, GridView1ID_RASHODD.Index]; end;
Pour obtenir une colonne de la grille à partir du nom de son champ :
TableView1.GetColumnByFieldName('FIELD_NAME')
Dans TcxGridTableView et TcxGridBandedTableView, sans le préfixe « DB », on peut ajouter et supprimer les données manuellement. Par exemple :
// Supprimer toutes les lignes while TableView.DataController.RowCount>0 do TableView.DataController.DeleteRecord(0); // Ajouter une ligne et affecter des valeurs selon leur type 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);
Modifier les données dans la grille
Remplir un ComboBox dans une cellule
Choisissez ComboBox dans Properties et remplissez sa liste dans l’événement AfterScroll du jeu de données. Lorsque j’essayais de le faire dans onInitPopUp, c’était la liste précédente qui apparaissait. Des lecteurs m’ont signalé dans les commentaires que je m’y prenais mal. Voici un exemple pour remplir la liste :
TcxComboBoxProperties(TableViewCNAME.Properties).Items.Clear; while not LookupQuery.Eof do begin TcxComboBoxProperties(TableViewCNAME.Properties).Items.Add(LookupQuery['CNAME']); LookupQuery.Next; end;
Vérifiez et traitez le choix de l’utilisateur dans OnValidate, par exemple :
if LookupQuery.Locate('CNAME',DisplayValue,[]) then begin
// enregistrer dans la base de données
TableView.BeginUpdate;
// actualiser la grille si nécessaire
TableView.EndUpdate;
end else begin
ErrorText := 'Sélection incorrecte';
Error := True;
end;
TcxComboBoxProperties(TableViewCNAME.Properties).Items.Clear;
Modifier un nombre dans la grille
Choisissez CalcEdit dans Properties. Dans le gestionnaire OnEditValueChanged, utilisez (Sender as TcxCalcEdit).Value et l’enregistrement courant du jeu de données.
Détecter un clic droit sur une colonne donnée
Dans l’événement OnCellClick :
if ACellViewInfo.Item.Name='DzTableViewNRM_NAME' then if AButton=mbRight then
Copier le texte affiché d’une cellule dans le presse-papiers
La valeur de la cellule courante est accessible avec <TcxGridDBTableView>.Controller.FocusedRow.Values[FocusedColumn.Index]. Mais pour une colonne utilisant LookupComboBox, cette expression renvoie la clé et non le texte affiché. La fonction de copie doit en tenir compte et appeler la méthode masquée GetDisplayLookupText. Pour y accéder, déclarez une classe dérivée de TcxLookupComboBoxProperties :
type
TcxLookupComboBoxPropertiesAccess = class(TcxLookupComboBoxProperties); // pour accéder à .GetDisplayLookupText
// plus loin dans le 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;
Identifier l’élément sous le pointeur dans 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); // Types possibles : // TcxGridColumnHeaderHitTest, TcxGridRecordCellHitTest, TcxGridGroupByBoxHitTest
if AHitTest is TcxGridColumnHeaderHitTest then begin
if TcxGridColumnHeaderHitTest(AHitTest).Column = TableView1COLUMN1 then begin
// Clic sur le premier en-tête de colonne ; éviter de modifier le tri ici
end;
end;
end;
Pour traiter le changement d’une case à cocher dans une cellule, utilisez l’événement OnChange :
if (Sender as TcxCheckBox).Checked then
Identifier la colonne en cours de modification
TableView.VisibleColumns[TableView.Controller.FocusedColumnIndex].DataBinding.FieldName
Modifier d’autres cellules dans OnEditValueChanged
TableView.DataController.SetEditValue(TableViewFIELD_NAME.Index, new_Value, evsValue );
Pour une raison qui m’échappe, cette méthode ne fonctionne pas toujours chez moi. On peut aussi procéder ainsi :
TableView1.DataController.DataSource.DataSet.Edit;
TableView1.DataController.DataSource.DataSet.FieldByName('FIELD_NAME').Value := new_Value;
TableView1.DataController.DataSource.DataSet.Post;
Glisser-déposer sur les lignes de la grille
procedure TForm1.cxGrid2DBTableView1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
// Démarrer le glissement depuis la grille source (on peut aussi activer 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
// Obtenir ARecordIndex de l'enregistrement déplacé
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
// Autoriser le dépôt
Accept := Source is TcxDragControlObject;
end;
procedure TForm1.cxGrid2DBTableView1DragDrop(Sender, Source: TObject; X, Y: Integer);
begin
// Afficher le texte de la ligne de destination
self.Caption := TcxGridRecordCellHitTest(TcxGridSite(Sender).ViewInfo.GetHitTest(X, Y)).GridRecord.Values[cxGrid1DBTableView1FULLNAME.Index];
end;
Modifier la grille à l’exécution
Ajouter une colonne à l’exécution :
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.##';
Supprimer une colonne à l’exécution
TableView.Columns[i].Destroy;
Ajouter des bandes :
b0 := TableView.Bands.Add; b0.Position.BandIndex := b.Index;
Définir une formule personnalisée pour FooterSummary
Pour calculer un résumé de pied de grille à partir des valeurs d’autres résumés, utilisez DataControllerSummaryAfterSummary. Cela ne fonctionne pas dans l’événement OnGetText de la cellule, car les résumés ne sont pas encore calculés à ce moment-là.
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;
Pour GroupSummary, il faut parcourir récursivement tous les niveaux de regroupement et calculer la formule souhaitée pour les résumés de chaque groupe. Voir cet exemple d’implémentation.
Bugs et solutions de contournement
Avec un composant aussi complexe, on finit parfois par rencontrer des bugs. Dans certains cas, les totaux du pied de grille perdaient leurs chiffres de tête : par exemple, 14.8 s’affichait sous la forme 4.8, quel que soit le format choisi. J’avais contourné le problème en calculant moi-même la somme dans l’événement OnGetText du résumé de pied :
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;
C’était mon ancien contournement. En 2026, j’ai enfin trouvé la cause : un bug dans la bibliothèque RTL standard de Delphi 7 ! Je l’explique dans cet article, disponible en russe.
TcxLookupComboBox
Afficher une indication dans le champ de saisie avec TextHint. Le composant possède cette propriété, mais elle est masquée. Pour la rendre accessible, déclarez une classe dérivée :
type TcxLookupComboBoxAccess = class(TcxLookupComboBox); // pour accéder à la propriété masquée .TextHint // Affecter ensuite le texte indicatif, par exemple dans OnCreate : TcxLookupComboBoxAccess(MyLookupCoboBox).TextHint := 'Texte indicatif';
Ajouter votre propre bouton au champ de saisie, par exemple pour ouvrir ou modifier une liste de référence. Cela fonctionne également avec d’autres contrôles possédant des Properties ; voir la documentation des contrôles.
with MycxLookupComboBox.Properties do begin
Images := cxImageList;
Result := Buttons.Add;
Result.Default := True;
Result.Kind := bkGlyph;
Result.LeftAlignment := False;
Result.Action := Action; // L'action à exécuter
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: Спасибо за замечание!