Програмування

Шпаргалка з DevExpress cxGrid і Delphi 7

Під час вибірки великої кількості записів можна отримати помилку нестачі пам’яті, хоча сама cxGrid легко обробляє в кілька разів більше записів навіть у 32-бітній версії. Достатньо замінити стандартний менеджер пам’яті Delphi на Fast Memory Manager. У моєму випадку FastMM4 чудово допоміг розширити можливості, збільшивши граничну вибірку вп’ятеро.

Деякі прийоми роботи з компонентом cxGrid, зібрані з різних джерел і корисні особисто мені. (Оновлено 06.10.2026)

 

Зовнішній вигляд

Програмно забарвлювати комірки можна в подіях OnCustomDrawCell.

if (AViewInfo.GridRecord.Values[TableViewColumn1.Index])=1 then begin
	ACanvas.Brush.Color := clRed;
	ACanvas.Font.Style := [fsBold];
end;

Якщо потрібно забарвити лише окремі стовпці (а не весь рядок), потрібний стовпець в обробнику OnCustomDrawCell можна визначити так:

if TableView.Columns[AViewInfo.Item.Index].DataBinding.FieldName='COLUMN_1' then
або
if AViewInfo.Item = TableViewCOLUMN_1 then

Слід враховувати, що таке забарвлення не переноситься під час експорту в Excel, а колір виділення іноді вибирається невдало. Для цього потрібно забарвлювати комірки, виключно присвоюючи заздалегідь визначений «стиль» в обробниках типу TableViewStylesGet***Style:

procedure TMoneyMoveDetailForm.TableViewStylesGetContentStyle(Sender: TcxCustomGridTableView; ARecord: TcxCustomGridRecord; AItem: TcxCustomGridTableItem; out AStyle: TcxStyle);
begin
	if not ARecord.IsData then Exit;
	// (AItem as TcxGridDBBandedColumn) якщо потрібно визначити, у якому стовпці ми перебуваємо
	if (ARecord.Values[TableViewID_MONEY.Index])=null then begin
		AStyle :=cxStyleBold;
	end;
end;

Виведення зображення або піктограми в комірці

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);

		// малюємо зображення.
		R := AViewInfo.Bounds;
		Inc(r.Top,1);
		ACanvas.DrawImage(ImageList,r.Left,r.Top,20,True);
		ADone := True ;
	end;
end;

Але можна зробити й так:

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;

Є ще варіант: установити в Properties комірки компонент ButtonEdit, у якому можна додати одну чи більше кнопок. Якщо призначити кнопці Action, вона реагуватиме на натискання. Щоправда, лише за умови ввімкненого редагування. Недолік способу — комірка містить лише текст.

Забарвлення різних рівнів групування різними кольорами

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;

Відчутний недолік забарвлення через стилі (Style) полягає в тому, що курсор перекриває його й кольору не видно. Це можна обійти, якщо в обробнику TableViewCustomDrawCell, наприклад, самостійно виділяти рядок (для режиму CellSelect=True):

  if AViewInfo.GridRecord.Selected then begin
    if AViewInfo.Selected then begin // під інверсним курсором зробимо білий шрифт
      ACanvas.Font.Color := clWhite;
    end else begin // решту рядка виділимо синім шрифтом, щоб не змінювати фонове забарвлення комірок
      ACanvas.Font.Color := clBlue;
      ACanvas.Font.Style := [fsBold];
    end;
  end;

Зверніть увагу! Під час доступу до даних рядка через GridRecord та індекс стовпця можна не отримати очікуваних значень у разі групування рядків, коли відображуваний рядок виявиться заголовком групи.

Відображення даних (фільтри, групування, сортування)

У TcxGrid приховати групування GroupByBox і прибрати область над грідом із написом “drag a column header here to group by ..”:

TableView.OptionsView.GroupByBox := false;

Програмно задати фільтр

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

Якщо додається більше однієї умови, перед додаванням наступної бажано вказати логічний оператор (типово буде AND). Можна задати OR: TableView.DataController.Filter.Root.BoolOperatorKind := fboOr;

Збереження позиції курсора в гріді після оновлення

TableView.BeginUpdate();
r := TableView.Controller.TopRowIndex;
f := TableView.Controller.FocusedRowIndex;
GridQuery.Close; // тут відкриваємо й закриваємо грід
GridQuery.Open;
TableView.Controller.TopRowIndex := r;
TableView.Controller.FocusedRowIndex := f;
TableView.EndUpdate;

Програмно розгорнути або згорнути групи

TableView.DataController.Groups.FullCollapse; // Згорнути
TableView.DataController.Groups.FullExpand; // Розгорнути
TableView.DataController.Options := TableView.DataController.Options + [dcoGroupsAlwaysExpanded]; // Закріпити розгорнутий стан

Програмно задати або прибрати групування

TableViewColumn1.GroupIndex := 1; // задати групування за стовпцем
TableView.DataController.Groups.ClearGrouping; // Прибрати групування

Доступ до даних і метаданих гріда

Пройти в циклі всі видимі рядки:

for I:=0 to cxGrid.DataController.FilteredRecordCount - 1 do begin
cxGrid.DataController.Values[cxGrid.DataController.FilteredRecordIndex[i],YourColumnName.Index])

Цикл за всіма виділеними рядками з отриманням ідентифікатора кожного

for i := 0 to GridView1.Controller.SelectedRecordCount-1 do begin
ID_RASHODD := GridView1.DataController.Values[GridView1.Controller.SelectedRecords[i].RecordIndex, GridView1ID_RASHODD.Index];
end;

Отримати стовпець гріда за його назвою

TableView1.GetColumnByFieldName('FIELD_NAME')

Для таблиць типу TcxGridTableView або TcxGridBandedTableView (без “DB”) дані можна додавати й видаляти вручну. Наприклад, так:

// Видалення всіх рядків із таблиці
while TableView.DataController.RowCount>0 do
  TableView.DataController.DeleteRecord(0);

// Додавання рядка даних і заповнення комірок значеннями відповідно до їхнього типу
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);

Редагування даних користувачем

Організація списку ComboBox у комірці:

У Properties задаємо ComboBox, заповнюємо список у подіях AfterScroll набору даних. Якщо намагатися робити це в onInitPopUp, відкривається попередній список (у коментарях підказали, що я робив неправильно). Приклад заповнення:

TcxComboBoxProperties(TableViewCNAME.Properties).Items.Clear;
while not LookupQuery.Eof do begin
	TcxComboBoxProperties(TableViewCNAME.Properties).Items.Add(LookupQuery['CNAME']);
	LookupQuery.Next;
end;

Вибір користувача перевіряється й використовується в методі OnValidate приблизно так:

if LookupQuery.Locate('CNAME',DisplayValue,[]) then begin
	// тут записуємо в базу
	TableView.BeginUpdate;
	// тут оновлюємо таблицю за потреби
	TableView.EndUpdate;
end else begin
	ErrorText := 'Помилка вибору';
	Error := True;
end;
TcxComboBoxProperties(TableViewCNAME.Properties).Items.Clear;

Редагування числа в гріді:

У Properties задаємо CalcEdit, а в обробнику OnEditValueChanged використовуємо (Sender as TcxCalcEdit).Value і поточне положення в наборі даних.

Перехопити натискання правої кнопки миші на певному стовпці:

У події OnCellClick:

if ACellViewInfo.Item.Name='DzTableViewNRM_NAME' then
if AButton=mbRight then

Зробити функцію копіювання комірки в буфер обміну

Отримати значення поточної комірки можна так: := <TcxGridDBTableView>.Controller.FocusedRow.Values[FocusedColumn.Index]. Але якщо в таблиці є стовпці з LookupComboBox, замість видимого значення ви отримаєте ключ. Тому функція отримання значення має це враховувати й використовувати .GetDisplayLookupText. Цей метод прихований, тож для доступу потрібно у своїх оголошеннях успадкувати клас TcxLookupComboBoxProperties:

type
  TcxLookupComboBoxPropertiesAccess = class(TcxLookupComboBoxProperties); // для доступу до .GetDisplayLookupText

// далі в коді

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;

Визначити положення курсора за подією 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); // Може бути одним із // TcxGridColumnHeaderHitTest, TcxGridRecordCellHitTest, TcxGridGroupByBoxHitTest
  if AHitTest is TcxGridColumnHeaderHitTest then begin
    if TcxGridColumnHeaderHitTest(AHitTest).Column = TableView1COLUMN1 then begin
      // Визначили, що натиснули заголовок першого стовпця (але задавати тут сортування — погана ідея)
    end;
  end;
end;

Обробка позначки прапорця в комірці. У події OnChange:

if (Sender as TcxCheckBox).Checked then

Дізнатися, у якому стовпці відбувається редагування:

TableView.VisibleColumns[TableView.Controller.FocusedColumnIndex].DataBinding.FieldName

Зміна сусідніх комірок під час обробки редагування в OnEditValueChanged:

TableView.DataController.<strong>SetEditValue</strong>(TableViewFIELD_NAME.Index, new_Value, evsValue );

Слід сказати, що цей спосіб чомусь не завжди працює, але можна й інакше:

TableView1.DataController.DataSource.DataSet.Edit;
TableView1.DataController.DataSource.DataSet.FieldByName('FIELD_NAME').Value := new_Value;
TableView1.DataController.DataSource.DataSet.Post;

Використання drag&drop для перетягування на рядки таблиці

procedure TForm1.cxGrid2DBTableView1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
// Початок перетягування з таблиці-джерела (утім, можна просто ввімкнути 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
// Отримуємо ARecordIndex запису, який перетягуємо
  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
// Дозвіл приймання

    Accept := Source is TcxDragControlObject;
end;

procedure TForm1.cxGrid2DBTableView1DragDrop(Sender, Source: TObject; X, Y: Integer);
begin
// Виведемо текст із рядка, на якому виконали Drop
    self.Caption := TcxGridRecordCellHitTest(TcxGridSite(Sender).ViewInfo.GetHitTest(X, Y)).GridRecord.Values[cxGrid1DBTableView1FULLNAME.Index];
end;

Зміна таблиці під час виконання

Додавання стовпця під час виконання:

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.##';

Видалення стовпця під час виконання:

TableView.Columns[i].Destroy;

Додавання смуг (bands):

b0 := TableView.Bands.Add;
b0.Position.BandIndex := b.Index;

Задати довільну формулу обчислення FooterSummary

Задати довільну формулу обчислення FooterSummary комірки з використанням інших комірок FooterSummary можна в події DataControllerSummaryAfterSummary. У події OnGetText самої комірки це не вдасться, оскільки на момент її виклику Summary ще не обчислено.

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;

Щодо аналогічного обчислення для GroupSummary, тут доведеться рекурсивно пройти всі Level і на кожному обчислити потрібну формулу для всіх GroupSummary. Приклад реалізації.

Збої

Так, у такого складного компонента вони неминучі, й іноді з ними можна зіткнутися. Наприклад, у деяких випадках під час підсумовування у Footer у мене губилися старші розряди: замість 14.8 відображалося 4.8, незалежно від заданого формату. Тоді довелося написати власне обчислення суми й викликати його в події OnGetText для FooterSummary:

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;

Так я обходив помилку раніше, але у 2026 році знайшов її: це виявилася помилка стандартної бібліотеки RTL Delphi 7! Докладніше про це тут.

TcxLookupComboBox

Показати підказку в самому полі введення (.TextHint). Виявляється, компонент має цю властивість, але вона прихована. Щоб відкрити до неї доступ, потрібно в оголошеннях успадкувати клас:

type
  TcxLookupComboBoxAccess = class(TcxLookupComboBox); // для доступу до прихованого поля .TextHint

// А вже, наприклад, в OnCreate присвоїти підказку:

TcxLookupComboBoxAccess(MyLookupCoboBox).TextHint := 'Текст підказки';

Додати власну додаткову кнопку в поле введення, наприклад для відкриття довідника або редагування. Це також працюватиме для всіх елементів керування, які мають Properties (список елементів керування).

with MycxLookupComboBox.Properties do begin
    Images := cxImageList;
    Result := Buttons.Add;
    Result.Default := True;
    Result.Kind := bkGlyph;
    Result.LeftAlignment := False;
    Result.Action := Action; // Це дія, яка виконуватиметься
end;

Обговорення 4

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

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

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

Залишити коментар

Вашу електронну адресу не буде оприлюднено. Обов’язкові поля позначено *