При выборке большого количества записей можно нарваться на ошибку недостатка памяти, хотя сама cxGrid с легкостью обрабатывает в несколько раз больше записей даже на 32-битной версии, достаточно лишь заменить стандартный менеджер памяти Delphi на «Fast Memory Manager», в моем случае FastMM4 отлично помог расширить возможности, увеличив предельную выборку в 5 раз.
Некоторые приемы работы с компонентом cxGrid, собранные из разных источников и полезные лично мне. (Обновлено 12.12.2022)
Внешний вид
Программную раскраску ячеек можно делать в событиях 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); // draw the image. 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;
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;
Спасибо вам за статью.
Но есть замечание относительно пункта «Редактирование данных пользователем», подпункт «Организация списка ComboBox в ячейке». На самом деле в OnInitPopup тоже можно сделать выпадающий список и он будет актуальным, нужно просто обращаться не к столбцу, а к Sender’-у. Что-то типа этого:
a7in: Спасибо за замечание!
Отличная статья! У меня задача: заменить значения чисел на текстовые значения. Спасибо.