Grids Reference Page/ru

From Free Pascal wiki

Deutsch (de) English (en) español (es) polski (pl) русский (ru)


Contents

Цель

Этот текст попытается показать пользователю некоторые аспекты использования компонентов сеток (Grid) в Lazarus. Он также предназначен для использования в качестве руководства для пользователей, которые никогда не использовали сетки (опытные пользователи тут обычно получают только информацию по новой функциональности).

Таким образом, этот текст пытается достичь следующих целей:

  1. Введение в сеточные компоненты для людей с небольшим опытом или без опыта после работы с Delphi.
  2. Документирование различий в использовании компонентов сеток в Delphi.
  3. Документирование новой функциональности сеток в Lazarus
  4. Описание по созданию и примеры использования таких компонентов в Lazarus

Введение

Как компонент сетка обеспечивает средства отображения данных в табличном формате. Наиболее очевидным характеристика сетки является то, что они состоят из клеток, образующих строки и столбцы.

Тип информации, которая может быть показана в сетке различна и зависит главным образом от того, что пользователь хочет, чтобы сета отобразила. Вообще эта информация может состоять из текста, цвета, изображения или их комбинации.

Учитывая большое разнообразие информации, которая может быть представлена, существует набор различных сеток, цель которых заключается в содействии пользователю в представлении разных видов информации.

Например, есть сетка предназначенная для отображения текста: StringGrid. Документация для этой сетки может быть найдена здесь

Дерево наследования

                     TCustomControl           
                             |                    
                             |                    
                        TCustomGrid               
                             |                    
               +-------------+------------+       
               |                          |       
         TCustomDrawGrid             TCustomDbGrid
               |                          |       
      +--------+--------+                 |       
      |                 |              TDBGrid    
  TDrawGrid      TCustomStringGrid                
                        |                         
                        |                         
                   TStringGrid

Начальный пример

Поскольку одной из целей этой страницы является помощь людям с небольшими или отсутствующими предшествующими знаниями Lazarus'а, давайте сделаем быстрый стартовый пример сетки в действии. Почему бы и нет, давайте сделаем традиционный пример "hello world", используя компонент TStringGrid.

  1. Создайте новое приложение.
    • В главном меню выберите: project->New Project
    • В диалоговом окне Create New Project нажмите кнопку "Create"
    • Будет показана новая пустая форма..
  2. Поместите сетку в форму
    • В палитре компонентов выберите вкладку "additional"
    • Нажмите значок TStringGrid []
    • Нажмите на форму рядом с левым верхним углом. Появится новая пустая сетка.
  3. Поместите кнопку в форму
    • с помощью палитры компонентов выберите вкладку "Standard"
    • Нажмите значок TButton []
    • Нажмите на пустую область формы. Появится новая кнопка.
  4. Дважды нажмите кнопку из шага 3 и запишите следующий код в обработчике щелчка кнопки:
    • Stringgrid1.Cells[1,1] := 'Привет, Мир!';
  5. Запустите программу, щелкнув значок воспроизведения []
    • по нажатию кнопки button1, текст приветствия будет отображаться в столбце 1 ячейки 1 (обратите внимание, что левый столбец и верхняя строка равны 0)

Различия между сетками Lazarus и Delphi

Текущий компонент сетки отличается от сетки Delphi по нескольким направления. При начальной разработке сетки Lazarus создавались с нуля без какой-либо попытки сделать их полностью совместимыми с Delphi.

На более позднем этапе совместимость с сетками Delphi стала желаемой целью, и сетки Lazarus стали более тесно соответствовать интерфейсу сетки Delphi. Однако даже это было сделано без попытки сделать каждое свойство или метод сетки Lazarus сопоставимым с его аналогом в Delphi. Кроме того (поскольку внутренняя структура сетки Lazarus сильно отличается от внутренних элементов сетки Delphi), некоторая функциональность Delphi невозможна или должна быть эмулирована иначе в сетке Lazarus [в отличие от того], как это будет сделано в сетке Delphi. Мы достигли большей совместимости с Delphi в процессе эволюции сетки Lazarus, и это соблазнительно.

Различия

Известные различия перечислены ниже, ни в каком специальном порядке.

  • Редакторы ячеек
  • Поведение в designtime
  • Отрисовка ячеек имеет некоторые отличия, см. раздел настройки сетки.

Новая функциональность

  • Пользовательские столбцы
  • События
  • Редактор сетки

Способы, которыми вы можете сделать сетку Lazarus более совместимой с Delphi

Вы можете сделать сетку Lazarus похожей и ведущей себя подобно [сетке] Delphi. Ниже перечислены параметры свойств, которые [позволят] достигнуть этого. Эти корректировки основаны на вновь созданной сетке. [Свойства,] помеченные тэгом [Code], должны быть определены в коде, тэгом [Design] могут быть изменены во время разработки.

  • [Design] TitleStyle := tsStandard;
  • [Design] DefaultRowHeight := 24;
  • [Code] EditorBorderStyle := bsNone; // это может работать только в Windows.
  • [Code] UseXORFeatures := true;
  • [Code] AllowOutBoundEvents := False; {SVN-ревизия 10992 или старше}
  • [Code] FastEditing := False; (поддерживается в dbgrid. StringGrid требует SVN-ревизию 10992 или старше)
  • [Design] AutoAdvance := aaNone;

Справочник по сеткам

Информация

Отправной точкой для [получения] справки о TCustomGrid, TDrawGrid, TCustomDrawGrid, TCustomStringGrid и TStringGrid является справка по модулю Grids.pas

Для TCustomDBGrid и TDBgrid это справка по модулю DBGrids.pas

В общем, любая ссылка Delphi о сетках должна помочь нам использовать сетки Lazarus (но не забывайте, что есть несколько различий между сетками Delphi и Lazarus, которые мы задокументировали); имея это в виду и в качестве временного места для справочной информации, это место будет использоваться для документирования тех вещей, которые не работают аналогично Delphi, а также любых новых функций.

TODO: остальная часть этого раздела должна исчезнуть; его содержание будет перенесено в справку по модулю Grids.pas

TCustomGrid

См. полную справку TCustomGrid

свойство AllowOutboundEvents

Protected[-свойство] в TCustomGrid, public[-свойство] в TCustomDrawGrid и его наследниках. Обычно, когда пользователь нажимает в точку над пустым пространством после ячеек (например, если сетка имеет три строки, но пользователь нажимает на воображаемую четвертую строку), текущий фокус ячейки перемещается ячейку, ближайшую к только что щелкнутой точке. Мы называем это исходящим событием. Значение по умолчанию - true, так как это изначальное поведение сетки. Это свойство было добавлено для моделирования поведения Delphi, когда исходящие события недоступны, поэтому для обеспечения совместимости с Delphi это свойство имеет значение false.

свойство Columns

Lazarus включает свойства columns в сетки TStringGrid и TDrawGrid. Это свойство добавляет так называемые пользовательские столбцы. Пользовательские столбцы представляют собой набор объектов, которые содержат свойства, применяемые к целым столбцам, например заголовки столбцов (для StringGrid он переопределяет значение, указанное в соответствующем свойстве Cells [ColumnIndex, RowTitle]), выравнивание текста, цвет фона, предпочтительный редактор и т.д.

Пользовательские столбцы добавляют дополнительные свойства или заменяют значения свойств по умолчанию в столбцах стандартной сетки. Но не только это; значение grid.ColCount может увеличиваться или уменьшаться, чтобы учесть количество настраиваемых столбцов, которые привязаны к сетке. На этом этапе это означает, что grid.ColCount = grid.FixedCols + grid.Columns.Count.

Например, если к базовой сетке с ColCount=5 и FixedCols=2 мы:

  • Добавим 3 пользовательских столбца, базовая сетка будет точно такой же, как раньше, 2 фиксированных столбца и 3 обычных столбца.
  • Добавим 1 пользовательский столбец, базовая сетка будет иметь ColCount=3, то есть 2 фиксированных столбца и 1 нормальный столбец.
  • Добавим 4 пользовательских столбца, базовая сетка будет иметь ColCount=6, то есть 2 фиксированных столбца и 4 нормальных столбца.

Из этого можно сделать вывод, что:

  • Свойства фиксированного столбца или счетчики не увеличиваются или не изменяются соответственно пользовательскими столбцами.
  • grid.ColCount обычно отличается от grid.Columns.Count (grid.ColCount=grid.Columns.Count, только когда FixedCols=0)

Во время разработки пользователь может получить доступ к свойству columns в Object Inspector, чтобы вызвать редактор столбцов. Оттуда вы можете добавлять, удалять или изменять настраиваемые столбцы. Редактор отображает список текущих пользовательских столбцов; путем выбора элементов в этом списке инспектор объектов заполняется свойствами, доступными для каждого столбца. Список настраиваемых столбцов также доступен в представлении дерева компонентов Object Inspector, где столбцы можно добавлять, удалять или изменять. Они появляются на нижнем уровне под сеткой контейнера.

В runtime столбцы могут быть изменены с помощью кода:

  var
    c: TGridColumn;
  begin
    // Добавляем пользовательский столбец в сетку
    c := Grid.Columns.Add;
    // модифицируем
    c.title.caption := 'Цена';        // Устанавливаем заголовок столбца
    c.align := taRightJustify;        // Выравниваем содержимое столбца по правому краю
    c.color := clMoneyGreen;          // Изменяем цвет по умолчанию на clMoneyGreen
    c.Index := 0;                     // Делаем этот столбец первым
    // доступ к существующему столбцу
    grid.columns[0].Width := 60;      // Изменяем ширину столбца 0 на 60 пикселей
    // удаляем существующий столбец
    grid.columns.delete(0);           // Удаляем столбец 0
    ....
  end;

Кроме того, когда задействуются пользовательские столбцы, сетки не допускают прямой модификации [свойства] grids.colcount; добавление или удаление столбцов должно выполняться с использованием свойства columns. Объяснение состоит в том, что существует некоторая несогласованность в постепенном удалении пользовательских столбцов с использованием ColCount: когда [значение] ColCount становится равным FixedCols, [это означает, что] сетка не имеет больше настраиваемых столбцов. Если теперь увеличить ColCount, новый столбец будет не настраиваемым, а нормальным столбцом.

В настоящее время нет планов, чтобы сетки использовали только настраиваемые столбцы.

свойство AutoFillColumns

Часто сетки шире, чем горизонтальное пространство, необходимое для столбцов - это оставляет неприятную пустую область справа от последнего столбца. Сетки LCL предоставляют механизм для расширения ширины указанных столбцов, так что пустое пространство заполняется автоматически.

Для этой цели необходимо либо добавить Columns, как описано выше, либо установив свойство ColCount, и свойство сетки AutoFillColumns должно быть установлено в true. Каждый столбец имеет свойство SizePriority. Если оно имеет значение 0, ширина столбца берется из свойства Width столбца. Но когда оно имеет ненулевое значение, ширина столбца корректируется до среднего доступного размера, оставшегося для всех столбцов с ненулевым SizePriority.

TCustomDBGrid

TCustomDBGrid основан на TDBGrid.

Они не отображают свойств Col и Row. Чтобы перейти к определенному столбцу, используйте, например, свойство SelectedIndex.

Интересным public-методом является AutoSizeColumns.

процедура AutoSizeColumns

Эта процедура устанавливает ширину столбца по размеру максимально широкого найденного текста. Его можно использовать после загрузки набора данных/установки [свойства] Active.

Однако, в противоположность [процедуре] TCustomStringGrid.AutoSizecolumns (см. ниже), эта [процедура] будет устанавливать столбцы максимально широко, пока вы не задействуете [параметр грида] dgAutoSizeColumns.

процедура InplaceEditor

См. пример из bug 23103 - и вставьте объяснение того, что он делает/почему это необходимо. Проверить входные значения? Изменить то, что показано?

procedure TForm1.DBGrid1KeyPress(Sender: TObject; var Key: char);
var
  S: String;
begin
  if (Key in [',','.']) then
  begin
    //в отличие от Delphi не все InPlaceEditor'ы - это редакторы для строкового типа, поэтому проверьте!
    if (DBGrid1.InplaceEditor is TStringCellEditor) then
    begin
      S := TStringCellEditor(DBGrid1.InplaceEditor).EditText;
      if Pos(',',S) > 0 then
        Key := #0
      else
        Key := ',';
    end;
  end;
end;

TCustomStringGrid

TCustomStringGrid служит основой для TStringGrid. Он может использоваться для [написания] производных компонентов [типа] TStringGrid, которые хотят скрыть published-свойства. Дополнительную информацию см. в new intermediate grids.

Следующие public-свойства или public-методы являются доступными для TStringGrid.

См. полную справку TCustomStringGrid

процедура AutoSizeColumn(aCol: Integer);

Эта процедура устанавливает ширину столбца [в соответствии с] размером самого широкого текст, который был ею найден во всех строках столбца aCol. Совет: см. параметр goDblClickAutoSize, чтобы позволить автоматически изменять размер столбцов при двойном нажатии на границу столбца.

процедура AutoSizeColumns;

Автоматически изменяет размеры всех столбцов так, чтобы они растягивались под самый длинный текст для каждого столбца. Это быстрый способ применения AutoSizeColumn() для каждого столбца в сетке.

процедура Clean; overload;

Очищает все ячейки в сетке, [будь то] фиксированные или нет.

процедура Clean(CleanOptions: TGridZoneSet); overload;

Очищает все ячейки в сетке в соответствии с данными CleanOptions. Дополнительную информацию см. в TGridZoneSet. Некоторые примеры:

  • Очистить все ячейки: grid.Clean([]); (аналогично grid.clean)
  • Очистить все нефиксированные ячейки: grid.Clean([gzNormal]);
  • Очистить все ячейки, но не касается заголовков столбцов сетки: Grid.Clean([gzNormal, gzFixedRows]);

процедура Clean(StartCol,StartRow,EndCol,EndRow: integer; CleanOptions:TGridZoneSet); overload;

делает то же самое, что и Clean(CleanOptions: TGridZoneSet), но ограничивается данными StartCol, StartRow, EndCol и EndRow. Примеры:

  • Очистить индекс столбца с 4 по 6, но не касаться заголовков столбцов сетки, много вариантов:
Grid.Clean (4,Grid.FixedRows, 6,Grid.RowCount-1,[]); //или Grid.Clean(4,0,6,Grid.RowCount-1, [gzNormal]);

и т.п.

процедура Clean(aRect: TRect; CleanOptions: TGridZoneSet); overload;

То же самое, что и Clean(StartCol,StartRow,EndCol,EndRow, CleanOptions), просто берется [прямоугольник] TRect вместо отдельных координат ячейки. Для очистки выделения полезно [сделать]:

grid.Clean(Grid.Selection,[]);

процедура SaveToCSVFile(AFileName: string; ADelimiter:Char=','; WithHeader:boolean=true);

Сохраняет содержимое сетки в файл формата данных с разделителями-запятыми (CSV) (добавлен в Lazarus с r32179).

Аргумент AFilename указывает имя файла, в котором содержимое будет сохранено. Если файл существует, содержимое будет перезаписано. Если он не существует, файл будет создан.

ADelimiter (необязательный аргумент) используется для предоставления настраиваемого разделителя, если требуется. По умолчанию создается формат CSV (то есть ADelimiter: = ',') для TAB-разделяемого файла ADelimiter должен быть #9.

Параметр WithHeader используется для определения необходимости включения или отключения "row header"[(заголовка строки)]. Заголовок строки - это список имен полей в начале выходного файла; его содержимое извлекается из последней фиксированной строки в сетке. Существует исключение из этого правила: если сетка имеет пользовательские столбцы, содержимое заголовка строки извлекается из заголовков пользовательских столбцов, а не из содержимого ячейки фиксированной строки.

Если WithHeader имеет значение true, а сетка не содержит фиксированную строку или настраиваемые столбцы, содержимое заголовка строки будет взято из первой строки в сетке.

Вывод CSV-данных обычно начинается с первой нефиксированной строки в сетке.

процедура LoadFromCSVFile(AFileName: string; ADelimiter:Char=','; WithHeader:boolean=true);

Загружает содержимое сетки из файла формата данных с разделителями-запятыми (CSV) (добавлен в Lazarus с r32179).

Столбцы будут добавлены в сетку или удалены из нее по мере необходимости, в соответствии с количеством полей, включенных в каждую строку файла CSV. Загрузка CSV-файла не будет изменять количество фиксированных строк, которые уже существовали в сетке.

Аргумент Afilename указывает имя исходного файла с содержимым CSV.

Дополнительный параметр ADelimiter может использоваться для указания другого разделителя. Например: для файла с TAB-разделителями ADelimiter должен быть #9. Другим популярным файловым форматом является файл с разделителем-запятой.

Параметр WithHeader используется для определения того, следует ли считать первую строку в файле CSV "строкой заголовка" или нет. Если сетка имеет фиксированные строки, а WithHeader - [значение] true, заголовки столбцов для последней фиксированной строки будут взяты из строки заголовка. Однако, обратите внимание, что если сетка имеет пользовательские столбцы, строка заголовка будет использоваться как источник заголовков столбцов, а заголовки столбцов всегда [будут] отображаются в первой фиксированной строке сетки или [будут] скрыты, если в сетке не [окажется] фиксированных строк.

Если в процедуре LoadFromCSVFile возникла проблема с загрузкой файла CSV (например, кавычки или пробелы начинают неправильно интерпретироваться), вы можете загрузить сетку вручную, например, CsvDocument ... и, конечно, патч для LoadFromCSVFile всегда приветствуется.

свойство Cols[index: Integer]: TStrings read GetCols write SetCols;

Получает/устанавливает список строк из/в индекса столбца данной сетки, начиная с индекса строки от 0 до RowCount-1.

Примеры
  • Пример определения [содержимого строки]:
//задать содержимое третьего столбца в сетке из списка ListBox
Grid.Cols[2] := ListBox1.Items;
  • Пример получения [содержимого строки]:
//задать содержимое Listbox, [получив значение] из столбца сетки с индексом 4:
procedure TForm1.FillListBox1;
var 
  StrTempList: TStringList;
begin
  StrTempList := TStringList(Grid.Cols[4]);
  if StrTempList<>nil then begin
    ListBox1.Items.Assign(StrTempList);
    StrTempList.Free;
  end;
end;
На заметку

Это свойство работает по-разному в Lazarus и Delphi при получении данных из сетки. В Lazarus создается временный объект TStringList для извлечения содержимого столбца. Пользователь должен освобождать этот объект после использования.

Это также означает, что изменения в возвращенном списке не будут влиять на содержимое или макет сетки.

См. пример получения [содержимого строки] выше.

свойство Rows[index: Integer]: TStrings read GetRows write SetRows;

Получает/определяет список строк из/в индекса строки данной сетки, начиная с индекса столбца 0 до столбца ColCount-1.

На заметку

Это свойство работает по-разному в Lazarus и Delphi при получении данных из сетки. В Lazarus создается временный объект TStringList для извлечения содержимого строки. Пользователь должен освобождать этот объект после использования.

Это также означает, что изменения в возвращенном списке не будут влиять на содержимое или макет сетки.

Примеры
  • Пример определения [содержимого строки]:
//задать содержимое третьего столбца в сетке из списка ListBox
Grid.Rows[2] := ListBox1.Items;
  • Пример получения [содержимого строки]:
//задать содержимое Listbox, [получив значение] из столбца сетки с индексом 4:
procedure TForm1.FillListBox1;
var 
  StrTempList: TStringList;
begin
  StrTempList := TStringList(Grid.Rows[4]);
  if StrTempList<>nil then begin
    ListBox1.Items.Assign(StrTempList);
    StrTempList.Free;
  end;
end;
  • Пример, который не работает, и его исправление: список извлеченных строк - только для чтения
// это не сработает и вызовет утечку памяти
// потому что возвращаемый StringList не освобождается
Grid.Rows[1].CommaText := '1,2,3,4,5';
Grid.Rows[2].Text := 'a'+#13#10+'s'+#13#10+'d'+#13#10+'f'+#13#10+'g'; 
 
//исправление первого случая
Lst:=TStringList.Create;
Lst.CommaText := '1,2,3,4,5';
Grid.Rows[1] := Lst;
Lst.Free;

свойство UseXORFeatures;

Boolean свойство, значение по умолчанию: False;

Это свойство [отвечает за то], как прямоугольник фокуса отображается в сетке. Когда [его значение равно] True, прямоугольник отрисовывается [вокруг ячейки] с использованием растровой операции XOR. Это позволяет нам видеть прямоугольник фокуса, вне зависимости от того, какой цвет фона [имеют] ячейки. Когда [его значение равно] False, пользователь может управлять цветом точек фокуса прямоугольника с помощью свойства FocusColor property

Он также контролирует внешний вид изменения размера столбца/строки. Когда [его значение равно] True, линия визуально показывает размер, который [только еще] будет иметь столбец или строка, если пользователь завершит операцию. Когда [его значение равно] False, изменение размера столбца или строки вступает в силу [сразу же], как [только] пользователь [начнет] перетаскивать мышь.

TValueListEditor

TValueListEditor - это элемент управления, полученный из TCustomStringGrid для редактирования пар ключ-значение.

свойство DisplayOptions

Управляет различными аспектами внешнего вида TValueListEditor.

свойство TitleCaptions

Устанавливает значения подписи заголовков (если [свойство] doColumnTitles [включено в набор свойств] DisplayOptions). Если DisplayOptions не [содержит] значения doColumnTitles, то используются заголовки [с надписями] по умолчанию.

свойство Strings

Предоставляет доступ к списку строк, которые содержат пары Key-Value.
Пары «ключ-значение» должны быть в форме:
KeyName=Value

свойство ItemProps

Вы можете использовать это свойство для управления тем, как элементы в столбцах [со значением] "Value" можно редактировать.
Это контролируется установкой свойств EditStyle и ReadOnly [свойства] ItemProp.

свойство KeyOptions

KeyOptions - это набор TKeyOptions, определяющий, может ли пользователь изменять содержимое столбца "Key".

  • KeyEdit: пользователь может редактировать имя ключа
  • KeyAdd: пользователь может добавить ключи (нажав Insert в сетке). KeyAdd требует KeyEdit.
  • KeyDelete: пользователь может удалить пары Key-Value (нажав Ctrl+Delete).
  • KeyUnique: если установлено, то ключи должны иметь уникальные имена. Попытка ввести дубликат ключа вызовет исключение.

свойство DropDownRows

Если редактор ячейки является списком выбора (ValueEdit1.ItemProps['key1'].EditStyle=esPickList), это свойство устанавливает DropDownCount[(высоту выпадающего)] отображаемого списка. По умолчанию используется значение 8.

функция DeleteRow

Удаляет пару Key-Value индексированной строки, полностью удаляя строку.

функция InsertRow

Вставляет строку в сетку и устанавливает пару Key-Value. Возвращает индекс вновь вставленной строки.

функция IsEmptyRow

Возвращает [значение] true, если ячейки индексированных строк пусты (Keys[aRrow]=; Valves[aRrow]=).

функция FindRow

Возвращает строку с указанным именем ключа.

функция RestoreCurrentRow

Отменяет редактирование в текущей строке (если редактор все еще в фокусе). [Событие] случается, когда пользователь нажимает клавишу Escape.

Измененное поведение некоторых свойств, унаследованных из TCustomStringGrid

Параметры Options

В соответствии с особенностями TValueListEditor его свойство Options имеет определенные ограничения

  • goColMoving не доступно в Options (вы не можете установить его).
  • goAutoAddRows может быть установлен только в том случае, если KeyAdd отмечен в KeyOptions. Установка KeyAdd автоматически установит goAutoAddRows.
  • goAutoAddRowsSkipContentCheck недоступен (на данный момент это приводит к сбою в TValueListeditor: требуется исправление).
свойство FixedRows

Может [иметь значение] только 1 (показать названия столбцов) или 0 (не показывать заголовки столбцов)

свойство ColCount

Всегда [будет значение] 2.

Общие замечания по использованию TValueListEditor

При манипулировании содержимым ValueListEditor (сетка) рекомендуется манипулировать лежащим в основе свойством Strings.
Если вы хотите вставлять или удалять строки, то либо делайте это путем прямого доступа к свойству Strings, либо используйте общедоступные методы из TValueListEditor: DeleteRow(), InsertRow(), MoveRow() и ExchangeRow().
Попытка использовать методы предка для управления строками или столбцами (например, Columns.Add) может привести к сбою.

Saving and loading the contenst of a TValueListEditor

Using the Strings property

If you just want to save and load the content of the TValueListEditor (e.g. the Key/Value pairs, not the layout) you can use Strings.SaveToFile and StringsLoadFromFile.
In case of LoadFromFile the RowCount property will be autmatically adjusted.
No sanity checks are performed upon LoadFromFile (so lines that do not represent a Key/Value pair will end up as Keys without Value).

Using TValueList.SaveToFile and TValueList.LoadFromFile

When using SaveToFile and LoadFromFile you get the additional benefits of also being able to save and load the layout, like with other grids.
SaveToFiel will also save information about wether or not the TValueListEditor uses ColumnTitles, and if so, it saves them.

In contrast to it's ancestors TValueList.LoadFromFile performs sanity checks on the file it tries to read. In particular RowCount must be specified and there cannot be a cell with RowIndex > RowCount or ColumnIndex other than 0 or 1.
If the file does not seem to be a valid TValueListEditor grid file, and exception is raised.

TValueList.SaveToFile and TValueList.LoadFromFile do not work correctly in Lazarus versions <= 2.1 r62044.

Using SaveToCSVFile and LoadFromCSV file

These methods should not be used at the moment, since for a TValueListEditor they are flawed.

Работа с сетками

Настройка сеток

Сетка - это компоненты, полученные из класса TCustomControl и не имеющие родственного виджета, связанного с ними, что означает, что сетки не ограничены по виду текущей темы интерфейса. Это может быть как преимуществом, так и недостатком: обычно программисты хотят создавать приложение с одинаково выглядящим интерфейсом. Хорошей новостью является то, что сетки Lazarus достаточно гибки, чтобы получить что-то из обоих миров; программисты могут легко сделать сетки похожими на другие собственные элементы управления, или они могут настроить сетку в мельчайших деталях, чтобы они могли получать почти одинаковый вид в любом интерфейсе платформы или виджета (то есть, за исключением полос прокрутки, поскольку их внешний вид все еще определяется текущей темой).

Свойства и События для настройки сеток

Некоторые свойства могут влиять на то, как выглядит сетка, действуя, когда ячейка собирается быть отрисованной в PrepareCanvas/OnPrepareCanvas, изменяя свойства холста по умолчанию, такие как цвет кисти или шрифт. Ниже приведен список таких свойств:

  • AlternateColor. Этим [свойством] пользователь может изменить цвет фона, появляющийся в чередующихся строках. Это позволяет легко считывать данные строк сетки.
  • Color. Это [свойство] устанавливает основной цвет, используемый для отрисовки фона нефиксированных ячеек.
  • FixedColor. Это цвет, используемый для создания фона фиксированных ячеек.
  • Flat. Это [свойство] устраняет трехмерный вид фиксированных ячеек [(т.е. делает их плоскими)].
  • TitleFont. Шрифт, используемый для отрисовки текста в фиксированных ячейках.
  • TitleStyle. Это свойство изменяет трехмерный вид неподвижных ячеек, имеет 3 значения [стилей]:
    • tsLazarus. Это стандартный вид.
    • tsNative. Это [свойство] пытается установить внешний вид, соответствующий текущей теме виджетов.
    • tsStandard. Этот стиль представляет собой более контрастный вид, как у сетки Delphi.
  • AltColorStartNormal. Boolean[-свойство]. Если [имеет значение] true: чередующийся цвет всегда находится во второй строке после фиксированных строк, первая строка после фиксированных строк будет всегда цветной. Если [имеет значение] false: цвет по умолчанию установлен в первую строку, как если бы не было фиксированных строк.
  • BorderColor. Это [свойство] устанавливает цвет границы сетки, используемую, когда Flat:=True и BorderStyle:=bsSingle;
  • EditorBorderStyle. Если установлено значение bsNone под [ОС] windows, редакторы ячейки не будут иметь границы, как в delphi по умолчанию для bsSingle, потому что граница может быть специфичной для темы в некоторых виджетах и обеспечивать единообразный внешний вид.
  • FocusColor. Цвет, используемый для отрисовки текущего фокуса ячейки, если [свойство] UseXORFeatures не установлено, по умолчанию это clRed.
  • FocusRectVisible. Включает/Выключает отрисовку фокуса ячейки.
  • GridLineColor. Цвет линий сетки в нефиксированной области.
  • GridLineStyle. Стиль Pen используется для отрисовки линий в нефиксированной области, возможны следующие варианты: psSolid, psDash, psDot, psDashDot, psDashDotDot, psinsideFrame, psPattern,psClear. По умолчанию - psSolid.
  • SelectedColor. Цвет, используемый для отрисовки фона ячейки на выбранных ячейках.
  • UseXORFeatures. Если установлено, прямоугольник фокуса рисуется с использованием режима XOR, поэтому он должен сделать видимым прямоугольник фокуса в сочетании с любым цветом ячейки. Это также влияет на просмотр движущихся столбцов.
  • DefaultDrawing. Boolean[-свойство]. Обычно сетки готовят холст сетки с использованием некоторых свойств в соответствии с видом окрашиваемой ячейки. Если пользователь пишет обработчик событий OnDrawCell, набор DefaultDrawing также рисует фон ячейки. Если пользователь сам рисует ячейку, лучше отключить это свойство, чтобы отрисовка не дублировалась. В StringGrid набор DefaultDrawing рисует текст в каждой ячейке.
  • AutoAdvance. [свойство определяет,] куда установится курсор ячейки при нажатии [клавиши] enter или после редактирования.
  • TabAdvance. [свойство определяет,] куда переместится курсор ячейки при нажатии [клавиши] Tab или Shift-Tab.
  • ExtendedColSizing. Если [значение равно] true, пользователь может изменять размер столбцов не только в заголовках, но и по высоте столбцов.


Другие свойства, которые также влияют на то, как выглядят сетки.

Options.

Свойство Options - это набор некоторых элементов для обеспечения разнообразной функциональности, но некоторые из них напрямую связаны с внешним видом сетки. Эти параметры можно установить в режиме разработки(designtime) или времени выполнения(runtime).
  • goFixedVertLine, goFixedHorzLine [свойство] рисует вертикальную или горизонтальную линию, соответственно разграничивая ячейки или столбцы в фиксированной области, по умолчанию [значение равно] active.
  • goVertLine, goHorzLine [свойство аналогично] предыдущему, но для нормальной просматриваемой области. Сетка может быть создана для имитации списка, [если] отключить оба этих элемента.
  • goDrawFocusSelected если этот элемент включен, фон выделения отрисовывается в сфокусированной ячейке в дополнение к пунктирному прямоугольнику фокуса (обратите внимание, что это не работает, когда установлена опция goRowSelect, в этом случае строка всегда отрисовывается так, как если бы была выбрана опция goDrawFocusSelected)
  • goRowSelect выбирает [для выделения] полную строку вместо отдельных ячеек
  • goFixedRowNumbering если [свойство] установлено, сетка будет нумеровать строки в первом фиксированном столбце
  • goHeaderHotTracking если [свойство] установлено, сетка попытается показать другой вид [ячейки, когда] курсор мыши будет находиться над любой фиксированной ячейкой. Для того, чтобы это сработало, желаемая зона ячейки должна быть включена с помощью свойства HeaderHotZones. Попробуйте комбинировать этот параметр с свойством TitleStyle:=tsNative, чтобы получить подсвечивание [соответственно] теме [виджета].
  • goHeaderPushedLook если этот параметр установлен, этот элемент включает [эффект] "нажатия" при клике [мышью] любой фиксированной ячейки. Зона "нажатия" ячеек становится доступной при использовании свойства HeaderPusedZones.

(напишите еще)

Описание процесса отрисовки сетки

Как и другие настраиваемые элементы управления, сетка отрисовывается с использованием метода рисования. В общих чертах сетка отрисовывается путем рисования всех строк и каждой строки путем рисования ее отдельных ячеек.

Процесс заключается в следующем:

  • Сначала определяется область видимых ячеек: каждая строка проверяется, пересекает ли она область отсечения холста; если все в порядке, то видимая область окрашивается путем рисования столбцов каждой строки.
  • Значения столбцов и строк используются для идентификации ячейки, которая должна быть окрашена, и снова каждый столбец проверяется на пересечение с областью отсечения; если все в порядке, некоторые дополнительные свойства, такие как прямоугольная протяженность ячейки и визуальное состояние, передаются в качестве аргументов методу DrawCell.
  • Когда процесс отрисовки выполняется, визуальное состояние каждой ячейки настраивается в соответствии с параметрами сетки и расположением в сетке. Визуальное состояние сохраняется в виде типа TGridDrawState, который представляет собой набор со следующими элементами:
    • gdSelected У ячейки будет выделенный вид.
    • gdFocused Ячейка будет иметь [рамку] фокуса.
    • gdFixed Ячейка будет нарисована [как] фиксированная.
    • gdHot [когда] мышь находится над этой ячейкой, то она отрисовывается с подсвечиванием.
    • gdPushed [когда по] ячейке [кликают мышью], она отрисовывается как нажимаемая [(кликабельная)]
  • DrawCell. Метод DrawCell является виртуальным и может быть переопределен в унаследованных сетках для выполнения пользовательского рисования. Информация, переданная DrawCell, помогает идентифицировать конкретную ячейку, которая окрашивается, физическую область, занятую экраном, и ее видимый статус. Подробнее см. ссылку DrawCell. Для каждой ячейки происходит следующее:
  • PrepareCanvas. В этом методе, если установлено свойство DefaultDrawing, холст сетки настраивается со свойствами по умолчанию для кисти и шрифта на основе текущего визуального состояния. Для нескольких свойств дизайн-тайма и времени выполнения выравнивание текста устанавливается в соответствии с выбором программиста в пользовательских столбцах, если они существуют. Если DefaultDrawing [имеет значение] false, цвет кисти устанавливается в clWindow и цвет шрифта в clWindowText, выравнивание текста задается значением defaultTextStyle сетки.
  • OnPrepareCanvas. Если программист написал обработчик событий для события OnPrepareCanvas, он вызывается в этот момент. Это событие можно использовать для простой настройки, например: изменения цвета фона ячейки, свойств шрифта, таких как цвет, шрифт и стиль, расположение текста, как различные комбинации левого, центрального, верхнего, нижнего, правого выравнивания и т.д. Любые изменения, сделанные на холсте [ранее] в этом событии будут потеряны, потому что следующая перерисовка ячейки снова сбросит содержимое холста до состояния по умолчанию. Поэтому безопасно делать изменения только для конкретной ячейки или клеток и забыть об этом для остальных. Использование этого события иногда помогает избежать использования события сетки OnDrawCell, где пользователи будут вынуждены дублировать код отрисовки сетки.
    Todo: образцы того, что можно сделать и что оставить для OnDrawCell? ...
  • OnDrawCell. Если не указан обработчик для события OnDrawCell, следующим сетка вызывает метод DefaultDrawCell, который просто рисует фон ячейки, используя текущий цвет и стиль кисти холста. Если обработчик OnDrawCell существует, сетка сначала рисует фон ячейки, но только если установлено свойство DefaultDrawing, [которое, в свою очередь,] вызывает событие OnDrawCell для создания пользовательской отрисовки клетки. Обычно программисты хотят делать пользовательскую отрисовку только для определенных ячеек и стандартную отрисовку для других; в этом случае они могут ограничить пользовательскую операцию определенной ячейкой или ячейками, просмотрев аргументы ACol, ARow и AState, а для других ячеек просто вызывают метод DefaultDrawCell и позволяют сетке позаботиться об этом.
  • Text. В этот момент (только для TStringGrid), если свойство DefaultDrawing [имеет значение] true, рисуется текстовое содержимое ячейки.
  • Grid lines Последний шаг для каждой ячейки состоит в том, чтобы нарисовать линии сетки: если параметры сетки goVertLine, goHorzLine, goFixedVertLine и goFixedHorzLine указаны, ячейка сетки рисуется в этой точке. Сетки только со строками или только со столбцами могут быть получены путем изменения этих параметров. Если программист предпочел бы иметь "тематический" вид, то это делается и в этот момент (см. свойство TitleStyle).
  • FocusRect. Когда все столбцы текущей строки были отрисованы, наступает время рисовать прямоугольник фокуса для текущей выбранной ячейки или для всей строки, если установлена опция goRowSelect.

Различия с Delphi

  • В Lazarus метод TCustomGrid.DrawCell не является абстрактным, и его реализация по умолчанию выполняет базовое заполнение фона ячейки.
  • В Delphi текст ячейки рисуется перед входом в событие OnDrawCell (см. bug report #9619).

Выбор ячейки сетки

Расположение текущей (сфокусированной) ячейки (или строки) сетки может быть изменено с помощью клавиатуры, мыши или кода. Чтобы успешно сменить фокус ячейки на другую позицию, мы должны проверить целевую позицию, чтобы увидеть, разрешено ли ей получать фокус ячейки. При использовании клавиатуры свойство AutoAdvance выполняет часть процесса, обнаруживая, что должно быть следующей сфокусированной ячейкой. При использовании кликов мыши или перемещения [посредством] кода фокус не будет перемещаться из текущей ячейки, если целевой ячейке не разрешено получать фокус.

Сетка вызывает функцию SelectCell, чтобы увидеть, является ли ячейка фокусируемой: если эта функция возвращает true, то целевая ячейка, идентифицированная с аргументами aCol и aRow, является фокусируемой (текущая реализация TCustomGrid просто возвращает true). TCustomDrawGrid и, следовательно, TDrawGrid и TStringGrid переопределяют этот метод, чтобы сначала проверить, [является ли] ячейка шире, чем 0; обычно вы не хотите, чтобы ячейка с нулевой шириной 0 выбиралась, [другими словами, вы хотите,] чтобы ячейка с этими характеристиками пропускалась автоматически в процессе поиска подходящей ячейки. Другая вещь, которую переопределяет метод SelectCell, - это вызов настраиваемое пользователем событие OnSelectCell: это событие принимает координаты ячейки как аргументы и всегда возвращает значение по умолчанию true.

Как только [становится] известно, что ячейка [является] фокусируемой, и мы уверены, что движение будет иметь место, сначала вызывается метод BeforeMoveSelection; это по очереди вызывает событие OnBeforeSelection. Аргументами этого метода являются координаты для новой сфокусированной ячейки. В этот момент [также будет] скрыт любой видимый редактор. Слово "перед" означает, что выбор еще не изменен, и к текущим сфокусированным координатам можно получить доступ по свойствам grid.Col и grid.Row.

После этого меняются координаты внутренней сфокусированной ячейки, а затем вызывается метод MoveSelection; цель этого метода заключается в том, чтобы инициировать событие OnSelection, если оно установлено (это уведомление, что сфокусированная ячейка к этому времени уже изменилась и координаты ячейки теперь доступны через свойства grid.row и grid.col).

Обратите внимание, что неэффективно использовать событие OnSelectCell для обнаружения изменений фокуса ячейки, так как это событие будет срабатывать несколько раз даже для одной и той же ячейки в процессе поиска подходящей ячейки. Для этого лучше использовать события OnBeforeSelection или OnSelection.

Различия с Delphi

  • Поведение SelectCell и OnSelectCell, вероятно, различны - в действительности не могу судить о различиях. В Lazarus они используются в таких функциях, как AutoAdvance, которая, насколько я знаю, не существует в Delphi.

Когда встроенных свойств недостаточно: наследники сетки

Наследники сетки обычно должны переопределять следующие методы:
DrawAllRows: Отрисовывать все видимые строки
DrawRow: Отрисовывать все ячейки в строке.
DrawRow отрисовывает все ячейки в строке, предварительно проверив, находится ли ячейка в области отсечения, и только [потом] рисует ячейку, если она есть.
DrawCell:
DrawCellGrid:
DrawCellText:
DrawFocusRect:

(напишите мне).

Что происходит в методе TCustomGrid.Paint?

В следующем списке показан внутренний порядок вызовов методов для отрисовки TCustomGrid (или потомков). Каждый элемент в списках представляет вызовы методов во время операции отрисовки. Это должно помочь найти правильный точку для изменения поведения, когда дело доходит до [написания] наследников TCustomGrid.

  • DrawEdges: Рисует внешнюю границу сетки.
  • DrawAllRows (virtual): Рисует все строки в видимой части сетки. Это единственный метод, вызываемый в процессе отрисовки, который объявлен virtual.
    • DrawRow (virtual): Вызывается для каждой строки внутри текущего окна просмотра.
      • DrawCell (virtual): Сначала вызывается для каждой "нормальной" (т.е. не фиксированной) ячейки в строке.
        • PrepareCanvas (virtual): Устанавливает стили рисования холста в соответствии с визуальными свойствами текущей ячейки.
        • DrawFillRect: Рисует фон ячейки со стилями, установленными в PrepareCanvas.
        • DrawCellGrid (virtual): Рисует линии границы ячейки.
      • DrawFocusRect (virtual): В TCustomGrid этот метод ничего не делает. В наследниках сетки этот метод используется для рисования прямоугольника фокуса внутри активной ячейки.
      • DrawCell (virtual): (см. выше) Вызывается для каждой фиксированной ячейки в видимом окне просмотра строки.
  • DrawColRowMoving: активен только при перемещении столбца или строки. Этот метод рисует линию, которая указывает новую позицию строки/столбца.
  • DrawBorder: Если необходимо (Flat=TRUE и BorderStyle=bsSingle), это отрисовывает внутреннюю линию границы.

Дополнительные методы для рисования TCustomGrid

Эти методы объявлены и (частично) реализованы в TCustomGrid, но они не вызываются непосредственно отсюда. Они используются классами потомков для отрисовки содержимого ячейки.

  • DrawCellText (virtual): Записывает/отрисовывает текст, который передается в качестве параметра в ячейку. Текст отформатирован с использованием стилей, которые активны в Canvas.TextStyle (см. также PrepareCanvas).
  • DrawThemedCell (virtual): Используется только для фиксированных ячеек и если TitleStyle=tsNative. [Метод] отрисовывает фон ячейки, используя ThemeServices.
  • DrawColumnText (virtual): Используется только для ячеек заголовков столбцов.
    • DrawColumnTitleImage: Если сетке сопоставлен TitleImageList и Columns.Title[x].Title.ImageIndex содержит допустимое значение, [указанное в ImageIndex] изображение отрисовывается внутри ячейки.
    • DrawCellText : (см. выше)
  • DrawTextInCell (virtual): Используется для "нормальных" (т.е. не фиксированных) ячеек. В TCustomGrid этот метод ничего не делает. В наследуемых сетках этот метод используется для отрисовки содержимого ячеек. В TStringGrid метод вызывает DrawCellText (см. выше).

Методы рисования, введенные TCustomDrawGrid

Как вы можете видеть, базовый класс TCustomGrid не выводит никакого содержимого в ячейки. Это делается в TCustomDrawGrid. TCustomDrawGrid переопределяет метод DrawCell следующим образом:

  • DrawCell (override):
    • PrepareCanvas: вызывает унаследованный метод из TCustomGrid.
    • DefaultDrawCell (virtual): Этот метод вызывается только в том случае, если обработчик события для OnDrawCell НЕ назначен.
      • DrawFillRect / DrawThemedCell (von TCustomGrid): Рисует фон ячейки. Если TitleStyle=tsNative, фон фиксированных ячеек рисуется с помощью DrawThemedCell.
      • DrawCellAutonumbering (virtual): Если [свойство] goFixedAutonumbering [включено] в [набор] Options, номера строк отображаются в первом фиксированном столбце [с помощью метода] TCustomGrid.DrawCellText.
      • DrawColumnText (из TCustomGrid): Вызывается только для ячеек заголовков столбцов (см. «Дополнительные методы отрисовки в TCustomGrid»).
      • DrawTextInCell (из TCustomGrid): Вызывается только для "нормальных" (т.е. не фиксированных) ячеек (см. «Дополнительные методы отрисовки в TCustomGrid»).
    • DrawCellGrid (virtual): Рисует границы ячейки.

Сохранение и получение содержимого сетки

Методы SaveToFile и LoadFromFile позволяют сетке сохранять и извлекать ее макет и данные в/из файла формата XML. TStringGrid, как унаследованный от TCustomStringGrid, также имеет возможность "экспортировать" и "импортировать" свой контент в файл формата Comma Separated Values, наиболее известный как CSV-файлы. Это описано в ссылке методов SaveToCSVFile и LoadFromCSVFile.

Тип информации, которая может быть сохранена, а затем извлечена при использовании SaveToFile и LoadFromFile, определяется свойством SaveOptions (тип TSaveOptions), которое представляет собой набор параметров, описанных ниже:

soDesign:     Сохраняет и загружает ColCount,RowCount,FixedCols,FixedRows,
              ColWidths, RowHeights и Options (TCustomGrid)
soPosition:   Сохраняет и загружает Scroll position, Row, Col и Selection (TCustomGrid)
soAttributes:  Сохраняет и загружает Colors, Text Alignment и Layout, и т.д. (TCustomDrawGrid)
soContent:    Сохраняет и загружает текст (TCustomStringGrid)
soAll:        набор всех параметров (soAll:=[soDesign,soPosition,soAttributes,soContent];)

Не все параметры применимы ко всем типам сеток, например, soContent не применяется к наследникам сеток TCustomGrid, таким как TDrawGrid, поскольку такая сетка не имеет понятия "контент". TStringGrid - это специальный вид сетки, который "знает", как обрабатывать строки, и может использовать параметр soContent, если он указан.

Опция soAttributes не используется в стандартных сетках Lazarus, [она предназначена] для наследников сеток.

При использовании LoadFromFile сетка также использует свойство SaveOptions, чтобы знать, какая информация должна извлекаться из файла, поэтому вполне возможно указать SaveOptions:=[soDesign,soContent] при сохранении и только SaveOptions:=[soContent] при загрузке.

Для TStringGrid значение SaveOptions по умолчанию - [soContent], для других видов сеток SaveOptions - это пустой набор.

На заметку: Одна из распространенных проблем при сохранении и извлечении данных сетки возникает [тогда], когда пользователь определяет свойство SaveOptions перед [методом] SaveToFile, но не раньше [вызова метода] LoadFromFile. При использовании LoadFromFile через некоторое время после SaveToFile, свойство SaveOptions устанавливается должным образом, но если LoadFromFile выполняется при следующем запуске программы, свойство SaveOptions может настроиться неправильно, по этой причине рекомендуется всегда указывать свойство SaveOptions непосредственно перед [вызовом метода] LoadFromFile или делать это глобально при запуске программы, как в следующем примере:


Пример:

  1. Сначала перейдите в меню "File -> New -> Application";
  2. Бросьте пустой TStringGrid в форму;
  3. Бросьте TButton и TOpenDialog на форму;
  4. Добавьте событие OnCreate для формы;
  5. Добавьте событие OnClick для кнопки.


unit Unit1; 

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, Grids,
  Buttons, StdCtrls, XMLCfg;

type

  { TForm1 }
  TForm1 = class(TForm)
    StringGrid1: TStringGrid;
    Button1: TButton;
    OpenDialog1: TOpenDialog;
    procedure Button1Click(Sender: TObject);
    procedure Form1Create(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end; 

var
  Form1: TForm1; 

implementation

{ TForm1 }

procedure TForm1.Form1Create(Sender: TObject);
begin
 //устанавливаем SaveOptions во время создания формы
 stringgrid1.SaveOptions := [soDesign,soPosition,soAttributes,soContent];
end;


procedure TForm1.Button1Click(Sender: TObject);
begin
 //Спрашиваем, был ли запущен метод Execute [компонента] OpenDialog 
 //когда это происходит, пользователь выбирает файл XML для загрузки 
 //имя, которое было сохранено в объявлении FileName.

 if opendialog1.Execute then
 begin
   //Очищаем сетку 
   StringGrid1.Clear;
   //Загружаем XML
   StringGrid1.LoadFromFile(OpenDialog1.FileName);
   //Обновляем сетку
   StringGrid1.Refresh;
 end;
end;

initialization
  {$I unit1.lrs}

end.

Образец xml-файла: (Скопируйте текст ниже в текстовый файл. Не забудьте поставить заголовок xml :-))

<?xml version="1.0"?>
<CONFIG>
  <grid version="3">
    <saveoptions create="True" position="True" content="True"/>
    <design columncount="2" rowcount="5" fixedcols="1" fixedrows="1" defaultcolwidth="64" defaultRowHeight="20">
      <options>
        <goFixedVertLine value="True"/>
        <goFixedHorzLine value="True"/>
        <goVertLine value="True"/>
        <goHorzLine value="True"/>
        <goRangeSelect value="True"/>
        <goDrawFocusSelected value="False"/>
        <goRowSizing value="False"/>
        <goColSizing value="False"/>
        <goRowMoving value="False"/>
        <goColMoving value="False"/>
        <goEditing value="False"/>
        <goTabs value="False"/>
        <goRowSelect value="False"/>
        <goAlwaysShowEditor value="False"/>
        <goThumbTracking value="False"/>
        <goColSpanning value="False"/>
        <goRelaxedRowSelect value="False"/>
        <goDblClickAutoSize value="False"/>
        <goSmoothScroll value="True"/>
      </options>
    </design>
    <position topleftcol="1" topleftrow="1" col="1" row="1">
      <selection left="1" top="1" right="1" bottom="1"/>
    </position>
    <content>
      <cells cellcount="10">
        <cell1 column="0" row="0" text="Title Col1"/>
        <cell2 column="0" row="1" text="value(1.1)"/>
        <cell3 column="0" row="2" text="value(2.1)"/>
        <cell4 column="0" row="3" text="value(3.1)"/>
        <cell5 column="0" row="4" text="value(4.1)"/>
        <cell6 column="1" row="0" text="Title Col2"/>
        <cell7 column="1" row="1" text="value(1.2)"/>
        <cell8 column="1" row="2" text="value(2.2)"/>
        <cell9 column="1" row="3" text="value(3.2)"/>
        <cell10 column="1" row="4" text="value(4.2)"/>
      </cells>
    </content>
  </grid>
</CONFIG>

Редактор ячеек сетки

В сетке используются редакторы ячеек для изменения содержимого ячеек.

Для специализированной сетки, такой как TStringGrid, редактор является обычным однострочным текстовым контролом, но иногда желательно иметь другие средства для ввода информации. Например:

  1. показать диалоговое окно открытия файла, чтобы найти местоположение файла, чтобы пользователь не вводил полный путь вручную
  2. если текст в ячейке представляет дату, [вызвать] всплывающий календарь, чтобы мы могли легко выбрать конкретную дату.

Иногда информация, которую пользователь должен ввести в ячейку, ограничена определенным списком слов; в этом случае ввод информации напрямую может привести к ошибкам и, возможно, потребуется выполнить процедуру проверки. Мы можем избежать этого, используя редактор ячейки, который представляет пользователю список, содержащий только легальные значения.

Это также относится к общим сеткам типа TDrawGrid, где пользователю нужна какая-то структура для хранения данных, которые будут отображаться в сетке. В этой ситуации информация, введенная в редакторе ячейки, обновляет внутреннюю структуру, чтобы отображать изменения в сетке.

Встроенный редактор ячейки

Модуль grids.pas уже включает некоторые из наиболее используемых редакторов ячеек, готовых для использования в сетках. Также возможно создавать новые редакторы ячеек (настраиваемые редакторы ячеек), если встроенные редакторы не подходят для конкретной задачи.

Встроенные редакторы ячеек - это Button, Edit и Picklist.

Использование редакторов ячейки

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

  1. Using a custom column and selecting the ButtonStyle property of the column (использование настраиваемого столбца и выбор свойства ButtonStyle для столбца). В этом методе пользователь может выбрать стиль редактора, который будет показан. Доступными значениями являются: cbsAuto, cbsEllipsis, cbsNone, cbsPickList, cbsCheckboxColumn, cbsButtonColumn.
  2. Using OnSelectEditor grid event (Использование события сетки OnSelectEditor). Здесь пользователь указывает в параметре Editor, какой редактор использовать для ячейки, обозначаемой столбцом aCol и строкой ARow наследуемой сетки TCustomDrawGrid или TColumn в TCustomDBGrid. Для этой цели существует полезная public-функция сетки EditorByStyle(), которая принимает в качестве параметра одно из следующих значений: cbsAuto, cbsEllipsis, cbsNone, cbsPickList, cbsCheckboxColumn, cbsButtonColumn. Этот метод имеет приоритет над первым при использовании пользовательских столбцов. Редактор пользовательских ячеек можно указать здесь (см. главу 5 этого документа (автор Michaël Van Canneyt), в которой объясняется, как правильно реализовывать пользовательский редактор). Это событие также позволяет настраивать редактор со значениями, специфичными для ячейки, строки или столбца (например, пользовательское всплывающее меню для редактора ячейки).

Установка свойства ButtonStyle работает только в том случае, если столбец создан с помощью инструкции StringGrid1.Columns.Add. Использование выражения типа StringGrid1.ColCount:=X; вызовет исключение. Установка свойства ButtonStyle может быть выполнена с помощью подобного кода:

if ColCB< StringGrid1.Columns.Count
   then StringGrid1.Columns.Items[ColCB].ButtonStyle:=cbsCheckboxColumn;

Описание стилей редактора

Ниже приводится описание стилей редактора. Они являются значениями перечисляемого типа TColumnButtonStyle и поэтому имеют префикс 'cbs'. Этот тип использовался, чтобы оставаться совместимым с DBGrid Delphi.

  • cbsAuto
Это стиль редактора по умолчанию для сеток-наследников TCustomGrid. Фактический класс редактора, который будет использоваться для редактирования содержимого ячейки, зависит от нескольких факторов. Для TCustomGrids он использует класс TStringCellEditor, полученный из TCustomMaskEdit. Этот редактор специализирован для редактирования строк одной строки. Затем он используется по умолчанию в TStringGrid и TDrawGrid. При использовании пользовательских столбцов, если программист заполнил свойство PickList столбца, [редактор] ведет себя так, как если бы был установлен стиль редактора cbsPickList. Для TCustomDBGrid, который имеет поле типа boolean, он ведет себя так, как если бы был указан стиль редактора cbsCheckBoxColumn. Это рекомендуемое значение для Custom Cell Editors. TODO: сослаться на OnEditingDone.
  • cbsEllipsis
Этот стиль редактора является наиболее общим. При [его] использовании в ячейке редактирования появляется кнопка, и программисты могут использовать событие сетки OnEditButtonClick, чтобы определить, когда пользователь нажал кнопку, и выполнить любое действие, запрограммированное для такой ячейки. Например, программист может использовать этот стиль редактора, чтобы открыть диалог календаря, чтобы пользователь мог легко выбрать конкретную дату. Другие возможности могут заключаться в том, чтобы показать диалог открытия файла для поиска файлов, калькулятор, чтобы пользователь мог ввести числовой результат вычислений и т.д.
OnEditButtonClick - это просто уведомление, чтобы узнать, в какой ячейке нажата кнопка, взглянув на свойства grid.Row и grid.Col.
DBGrid имеет определенные свойства для извлечения активного столбца или поля, и, поскольку это событие происходит в активной записи, оно может обновлять информацию в активном поле.
Этот стиль редактора реализован с использованием TButtonCellEditor, прямого потомка TButton.
  • cbsNone
Этот стиль редактора указывает, что сетка не должна использовать какой-либо редактор для конкретной ячейки или столбца; [это поведение аналогично тому], как если бы такая ячейка или столбец сетки были только для чтения.
  • cbsPickList
Используется для представления пользователю списка значений, которые могут быть введены. Этот стиль редактора реализован с использованием TPickListCellEditor, компонента, полученного из TCustomComboBox. Список отображаемых значений заполняется одним из двух способов в зависимости от метода, используемого для выбора стиля редактора.
  1. При использовании настраиваемых столбцов программисты могут вводить список значений, используя свойство PickList столбца. [ДЛЯ НОВИЧКОВ: TODO: точная процедура для редактирования списка]
  2. В OnSelectEditor программисты получают экземпляр TPickListCellEditor, используя функцию EditorByStyle(cbsPickList). См. пример здесь
Значение в сетке TStringGrid будет автоматически отображать выбранное значение. При необходимости программист может определить момент, когда значение выбрано, путем написания обработчика для события OnPickListSelect сетки, поэтому могут быть предприняты дополнительные шаги (например, для обработки нового значения). TODO: связать с OnEditingDone.
  • cbsCheckboxColumn
Это может быть полезно, когда содержимое данных, связанное с столбцом, ограничено парой значений, например да-нет, true-false, вкл-выкл, 1-0 и т.д. Вместо того, чтобы принуждать пользователя вводить значения для данных такого типа в StringCellEditor или выбирать один из списка, cbsCheckboxColumn используется для изменения данных столбца с помощью представления флажка, которое пользователь может переключать, используя щелчок мыши или нажимая клавишу SPACE (если столбец, содержащий этот флажок, а также StringGrid доступны для редактирования).
Получение или установка значения флажка в ячейке осуществляется следующим образом:
StringGrid1.Cell[x,y]:='Z';
где Z заменяется на 0 для Unchecked, 1 для Checked и пустую строку для Grayed. Обратите внимание, что любое значение (строка), отличное от 0 и 1, будет отображаться как серый сheckbox.
Если для свойства столбцов ButtonStyle установлено значение cbsAuto, и DBGrid обнаруживает, что поле, связанное с столбцом, является логическим полем, то сетка автоматически использует этот стиль редактора. Этот автоматический выбор может быть отключен или активирован с использованием свойства OptionGxtra DBGrid; установка элемента dgeCheckboxColumn в false отключает эту функцию.
Значения, используемые для распознавания checked или unchecked состояний, задаются в свойствах столбца ValueChecked и ValueUnchecked [соответственно].
В любой момент значение поля может находиться в одном-трех состояниях: Unchecked, Checked или Grayed. Внутренне эти состояния идентифицируются следующими значениями типа TDBGridCheckBoxState: gcbpUnChecked, gcbpChecked и gcbpGrayed.
Этот стиль редактора не использует реальные компоненты TCheckbox для обработки взаимодействия с пользователем: визуальное представление задается тремя встроенными растровыми изображениями, которые соответствуют возможным состояниям флажка. Использованные растровые изображения можно настроить, написав обработчик для события OnUserCheckboxBitmap [компонента] DBGrid; обработчик этого события получает состояние флажка в параметре CheckedState типа TDBGridCheckboxState и параметр растрового изображения, который программист может использовать для указания пользовательских растровых изображений.
  • cbsButtonColumn
Этот стиль редактора используется для отображения кнопки в каждой ячейке в столбце. Как и в случае cbsCheckboxColumn, этот редактор не использует реальные кнопки, внешний вид определяется текущей темой widgetset.
Пользователь знает, какая конкретная кнопка была нажата благодаря обработчику OnEditButtonClick сетки и проверке столбца и строки. Обратите внимание, что в этом конкретном случае столбец и строка сетки идентифицируют не текущую выбранную ячейку, а ячейку нажатой кнопки. Как только событие OnEditButtonClick будет обработано, и если пользователь не изменил столбец или строку в этом обработчике, сетка автоматически сбрасывает столбец и строку, чтобы отобразить выбранную ячейку. Пока обработчик OnEditButtonClick текущего выделения сетки доступен в grid.Selection, который является свойством TRect, левое и правое [значения] представляют [собой] индексы столбцов, а верхний и нижний - индексы строк.
Заголовок кнопки - соответствующая строка ячейки.

Редактирование сеток

Эффект редактирования отличается в зависимости от того, какая сетка используется, например, TStringGrid сохраняет отредактированный текст внутри, а TDBGrid влияет на записи в наборе данных. TDrawGrid "не знает", что делать с отредактированным текстом: если программист не контролирует его, [отредактированный текст] просто отбрасывается.
Для TDrawGrid (хотя это должно работать для всех классов сетки), чтобы получить доступ к отредактированному тексту, программист может использовать событие OnSetEditText, которое запускается каждый раз, когда пользователь что-то изменяет в редакторе, параметры aCol и Row этого события определяют редактируемую ячейку, параметр value содержит [редактируемый] текст - это рекомендуемый метод. Другой способ доступа к отредактированному тексту - прием его непосредственно из редактора после завершения процесса редактирования с использованием события OnEditingDone. Это можно сделать, обратившись к внутреннему редактору, используемому для редактирования, в данном случае, по умолчанию "String Cell Editor" [("Редактору строк ячеек")]. Для этого существует два метода: первый - с использованием события OnSelectEditor, где параметр Editor - это экземпляр, который будет использоваться для редактирования ячейки; вы должны сохранить этот экземпляр в переменной, например TheEditor (типа TStringCellEditor), для последующего использования в OnEditingDone. Второй вариант - использование метода EditorByStyle сетки, этот метод принимает "стиль редактора" в качестве параметра и возвращает экземпляр редактора, соответствующего этому стилю. В нашем случае, зная, что стиль cbsAuto возвращает редактор ячейки по умолчанию, в обработчике события OnEditingDone можно использовать непосредственно TheEditor:=Grid.EditorByStyle(cbsAuto). Затем вы можете получить текст с помощью TheEditor.Text. Если вы [будете] использовать этот метод, обратите внимание на то, что "великая сила несет большую ответственность".

Параметры и свойства, которые влияют на редактирование

Поведение редактирования контролируется рядом свойств и параметров, фактически любая настройка или параметры, которые влияют на редактирование, требуют редактирования сетки или такая настройка может быть проигнорирована.
При запуске редактируемое состояние сетки различно [в разных видах сетках], поскольку редактирование TDrawGrid и TStringGrid отключено, для TDbGrid включено по умолчанию.
Свойство Options имеет несколько элементов, которые касаются редактирования, они описаны ниже:

  • goEditing, dgEditing (в DbGrid). Эта опция изменяет редактируемое состояние сетки, ее можно изменить во время выполнения, поскольку она проверяет, когда начнется редактирование.
  • goAlwaysShowEditor. Обычно редактор скрыт и становится видимым только тогда, когда это необходимо. С помощью этой опции редактор будет отображаться все время, если сетка не редактируется, этот параметр игнорируется, и редактор всегда скрыт.

Хинты (подсказки) ячейки

В дополнение к стандартным хинтам большинства элементов управления потомки TCustomGrid могут отображать специальную подсказку для ячейки, над которой курсирует мышь. Эта функция управляется параметрами сетки goCellHints и goTruncCellHints (или dgCellHints и gdTruncCellHints для DBGrid соответственно):

  • goCellHints оценивает событие OnGetCellHint для извлечения отображаемого текста для ячейки, указанной переданными параметрами. В случае TDrawGrid и TStringGrid задаются индексы столбцов и строк, тогда как в случае TDBGrid текст ячейки может быть извлечен из поля, назначенного указанному объекту столбца.
  • goTruncCellHints отображает подсказку только в том случае, если текст не помещается [полностью] в ячейку и [поэтому] усечен; текст подсказки - это не обрезанный ячейкой текст. В Lazarus 1.9+ этот текст передается событию OnGetCellHint для дальнейшего уточнения (например, отображает весь текст memo в подсказке). В случае обрезания ячейкой текстов рекомендуется также активировать опцию goCellEllipsis (или dgCellEllipsis для DBGrid), которая добавляет «...» к обрезанному ячейкой тексту.

Если сетка имеет значение в своем стандартном свойстве Hint, тогда свойство HintPriority можно использовать для управления тем, как все эти тексты подсказок могут быть объединены в одно всплывающее окно:

  • chpAll помещает стандартную подсказку в первую строку, подсказку, определяемую событием OnGetCellHint во вторую строку, и подсказку для усеченных ячеек в третью строку
  • chpAllNoDefault отображает подсказки только ячейки, заданные событием OnGetCellHint, и усеченными ячейками.
  • chpTruncOnly отображает подсказки только для усеченных ячеек; это настройки по умолчанию.

В дополнение, стандартное свойство ShowHint сетки должно быть установлено в true, чтобы отображать подсказки на ячейки.

"Как сделать..." и Примеры

Фокусировка ячейки

Фокусировка ячейки в TStringGrid очень проста. Обратите внимание, что подсчет начинается с нуля, а не 1. Таким образом, чтобы перевести фокус на строку 10, столбец 9, выполните:

StringGrid1.row := 9;
StringGrid1.col := 8;

Пример: Как настроить собственный редактор ячеек.

см. lazarus/examples/gridexamples/gridcelleditor/gridcelleditor.lpi (начиная с laz 1.2)

Пример: Как установить memo-редактор для dbgrids

Разумеется, вы можете использовать другой контрол вместо TMemo. Откорректируйте [на свой] вкус. Адаптирован из [1] (упрощенное использование свойства SelectedFieldRect, см. [2])

  • Поместите элемент управления memo (или любой другой контрол, который вы хотите) на свою форму, установите любые свойства, которые вы хотите, и установите значение visible:= false. Этот [контрол] будет использоваться при редактировании ячейки сетки. В этом примере мы будем использовать GridCellMemo.
  • В событии OnSelectEditor поместите следующий код - адаптируйте, если вы не хотите редактировать логический столбец 3, физический столбец 4:
if (Column.DesignIndex = 3) then
  begin
      GridCellMemo.BoundsRect := DbGrid.SelectedFieldRect;
      GridCellMemo.Text:=Column.Field.AsString;
      Editor := GridCellMemo;
  end;
  • Предположим, что ваш источник данных называется Datasource1, а DBGrid называется ResultsGrid. Тогда в событии OnEditingDone для GridCellMemo введите следующее:

(В исходном сообщении форума используется событие OnChange, которое будет срабатывать при каждом изменении содержимого. Использование OnEditingDone запускается только после того, как пользователь закончил свои правки.)

  if not(Datasource1.State in [dsEdit, dsInsert]) then Datasource1.Edit;
    
  Datasource1.DataSet.FieldByName(ResultsGrid.SelectedField.FieldName).AsString:=GridCellMemo.Text;

Осторожно Использование обычного элемента управления в качестве настраиваемого редактора имеет множество недостатков: при изменении размеров столбцов или скроллинге сетки он не будет изменять размер или положение, вам придется позаботиться о том, чтобы изменить данные в сетке (как объяснялось выше), он не работает должным образом с DbGrids и препятствует нормальной навигации по сетке с помощью клавиатуры. Сетка взаимодействует с редактором, используя некоторые специальные сообщения сетки, поэтому лучше [выделить этот контрол в] подкласс для упорядочивания управления этими сообщениями, как описано в пятой главе этого документа.

Осторожно Если у вас более одной сетки в одной и той же форме, используйте разные настраиваемые редакторы для каждой из них: если вы [будете] использовать один и тот же элемент управления, начнут происходить плохие вещи с того момента, когда обе сетки попытаются использовать его одновременно.

Пример: как добавить кнопку редактора

// Условно показываем кнопку редактора в столбцах индекс 1 или 2, если
// ячейка в столбце index 1 пуста
procedure TForm1.StringGrid1SelectEditor(Sender: TObject; aCol, aRow: Integer; 
  var Editor: TWinControl);
begin
  if (aCol = 1) or (aCol = 2) then
    if StringGrid1.Cells[1,aRow] = '' then
    begin
      Editor := StringGrid1.EditorByStyle(cbsEllipsis);
    end;
  end;

// Запуск действия ...
procedure TForm1.StringGrid1EditButtonClick(Sender: TObject);
begin
  if StringGrid1.Col = 1 then Showmessage('щелкнут редактор столбца 1');
  if StringGrid1.Col = 2 then Showmessage('щелкнут редактор столбца 2');
end;

Пример: Запрет отображения текстовых полей как "(Memo)" для DBGrids

Когда вы используете SQL-выражения, такие как "Select * from A", вы можете увидеть "(Memo)" вместо своего содержимого в ячейке сетки. Есть много способов исправить это, но, может быть, самым простым является изменение вашего выражения "Select * from A" на "Select Cast(Column as TEXT) as Column from A". Это обычная проблема при использовании компонента DBGrid.

 var
    sql : UTF8String;
    Query : TSQLQuery;
.....
    sql := 'SELECT cast(Name as TEXT) as Name FROM Client';
    Query.SQL.Text:=sql;

Пример: Работа с Picklist, Как сделать его только для чтения и Как заполнить его в runtime.

Используя событие сетки OnSelectEditor, вы можете настроить, как поведет себя редактор PickList (см. стиль кнопки cbsPickList). В следующем примере редактор списка выбора из столбца 1 изменяется на нечетных строках таким образом, что пользователь может вводить значения, набирая [их с клавиатуры], а на четных строках [вводимые] значения ограничиваются теми, которые содержатся в его списке. Кроме того, в этом примере показано, как заполнить список разными значениями в зависимости от обрабатываемой строки.

procedure TForm1.gridSelectEditor(Sender: TObject; aCol, aRow: Integer;
  var Editor: TWinControl);
begin
  if aCol=1 then begin
    if (Editor is TCustomComboBox) then
      with Editor as TCustomComboBox do begin
        if (aRow mod 2=0) then
          Style := csDropDown
        else
          Style := csDropDownList;
        case aRow of
          1:
            Items.CommaText := 'ONE,TWO,THREE,FOUR';
          2:
            Items.CommaText := 'A,B,C,D,E';
          3:
            Items.CommaText := 'MX,ES,NL,UK';
          4:
            Items.CommaText := 'RED,GREEN,BLUE,YELLOW';
        end;
      end;
  end;
end;

Выравнивание текста в StringGrids

Этот код показывает, как использовать разное выравнивание текста в столбцах 2 и 3.

procedure TForm1.StringGrid1PrepareCanvas(sender: TObject; aCol, aRow: Integer;
  aState: TGridDrawState);
var
  MyTextStyle: TTextStyle;
begin
  if (aCol=2) or (aCol=3) then
  begin
    MyTextStyle := StringGrid1.Canvas.TextStyle;
    if aCol=2 then
      MyTextStyle.Alignment := taRightJustify 
    else 
    if aCol=3 then
      MyTextStyle.Alignment := taCenter;
    StringGrid1.Canvas.TextStyle := MyTextStyle;
  end;
end;

Многострочность в Grids, DBGrid

В этом примере показано, как сделать многострочный текст в ячейке [3,2]. Он работает так же для DBGrid, где [событие] OnPrepareCanvas имеет параметры для работы с столбцами, а оттуда и с TFields.

procedure TForm1.StringGrid1PrepareCanvas(sender: TObject; aCol, aRow: Integer;
  aState: TGridDrawState);
var
  MyTextStyle: TTextStyle;
begin
  if (aRow=2) or (aCol=3) then
  begin
    MyTextStyle := StringGrid1.Canvas.TextStyle;
    MyTextStyle.SingleLine := false;
    StringGrid1.Canvas.TextStyle := MyTextStyle;
  end;
end;

Проверка введенных значений

Lazarus версии 0.9.29 для StringGrid ввел событие OnValidateEntry типа TValidateEntryEvent, которое имеет следующее объявление:

TValidateEntryEvent =
  procedure(sender: TObject; aCol, aRow: Integer;
            const OldValue: string; var NewValue: string) of object;

где

aCol,aRow являются координатами проверяемой ячейки.
OldValue это значение, которое было в ячейках [aCol, aRow] до начала редактирования.
NewValue это значение, которое будет окончательно вставлено в ячейки [aCol, aRow].

Из-за того, что StringGrid работает, устанавливая значение ячейки во время редактирования пользователем (см. событие OnSetEditText и метод SetEditText сетки), то когда событие OnValidateEntry запускается, ячейка уже содержит введенное значение (корректное или нет); используя аргументы события OldValue и NewValue, значение ячейки может быть проверено и изменено, если необходимо.

Обычно проверка выполняется, когда пользователь перемещается [с редактируемой ячейки] в другую. Если проверка закончилась неудачей, желательно сохранить редактор ячейки видимым/сфокусированным, чтобы введенное значение могло быть исправлено пользователем. Чтобы сетка "узнала", что проверка не прошла, необходимо создать исключение. Сетка будет обрабатывать исключение из Application.HandleException, и любое перемещение отменится.

Например, предположим, что ячейка [1,1] должна содержать только значения 'A' или 'B', [тогда] валидация может быть выполнена с помощью:

procedure TForm1.GridValidateEntry(sender: TObject; aCol,
  aRow: Integer; const OldValue: string; var NewValue: String);
begin
  if (aCol=1) and (aRow=1) then begin
    if grid.Cells[aCol,aRow]<>'A') and grid.Cells[aCol,aRow]<>'B') then begin
      // устанавливаем новое допустимое значение, чтобы пользователь мог просто нажать RETURN для продолжения, например
      NewValue := 'A';
      // другой вариант - вернуться предыдущему значению ячейки (которое считается допустимым)
      // NewValue := OldValue;
      // Используйте EAbort, а не другой тип исключения, чтобы избежать [возникновения] ложных
      // диалоговых окон ошибок и [диалоговых окон] обратной отладки
      raise EAbort.Create('Разрешены только A или B');
    end else begin
      // если исключение не создано, предполагается, что значение действительно, но при необходимости
      // окончательное значение все равно можно изменить, заполнив NewValue другим значением
       
      // компьютер знает лучше :)
      if grid.Cells[1,1]='A' then 
        NewValue := 'B' 
      else 
        NewValue := 'A';
    end;
  end;
end;

Сортировка столбцов или строк

Свойство ColumnClickSorts позволяет сортировать сетку автоматически, когда пользователь щелкает по заголовку столбца. Многократный щелчок по тому же столбцу переключает порядок сортировки. По умолчанию отображаются значки сортировки, показывающие, какой столбец был нажат.

В коде вы можете использовать метод SortColRow().

Первый параметр IsColumn - это логическое значение, которое указывает:

  • True если будет отсортирован столбец
  • False если будет отсортирована строка

Второй параметр, index, является целым значением:

  • Index указывает колонку или строку для сортировки.

Последние два параметра являются необязательными, [они] определяют поддиапазон строк или столбцов для сортировки.

  • FromIndex (От столбца или строки)
  • ToIndex (До столбца или строки)

Если последние два параметра не указаны, сортируется весь столбец или строка.

Сортировка использует алгоритм QuickSort, его можно изменить, если сетка-наследник переопределяет метод sort() и вызывает doCompareCells для сравнения с ячейкой.

По умолчанию [метод SortColRow()] сортирует содержимое ячейки как строки в порядке возрастания или убывания, которое можно выбрать с помощью свойства SortOrder. По умолчанию он использует порядок [сортировки] по возрастанию.

// Сортировка столбца 3 в порядке возрастания
grid.SortColRow(true, 3);

// Сортировка столбца 3 в порядке убывания, пропустить фиксированные строки сверху
grid.SortOrder := soDescending; // или soAscending
grid.SortColRow(true, 3, grid.FixedRows, grid.RowCount-1);

Для пользовательской сортировки чисел, дат, состояний и т.д.: у StringGrid есть событие OnCompareCells, с которым пользователи могут обращаться, например, следующим образом:

procedure TForm1.GridCompareCells(Sender: TObject; ACol, ARow, BCol, BRow: Integer; var Result: integer);
begin
  // Результат будет либо <0, = 0, либо >0 для нормального порядка.
  result := StrToIntDef(Grid.Cells[ACol,ARow],0)-StrToIntDef(Grid.Cells[BCol,BRow],0);
  // Для обратного порядка просто [присвойте] негативный результат (напр., на основе SortOrder сетки).
  if StringGrid1.SortOrder = soDescending then
    result := -result;
end;

Вы также можете использовать OnCompareCells, когда автоматическая сортировка столбцов включена через свойство ColumnClickSorts.

Переопределить автоматическую сортировку (ColumnClickSorts установлен в False). Исключить сортировку некоторых столбцов. Показать стрелки сортировки в заголовке столбца

(ColumnClickSorts, установленный в False, НЕ будет показывать стрелки сортировки в заголовке столбца, поэтому мы должны установить стрелки.)

procedure TForm1.StringGrid1HeaderClick(Sender: TObject; IsColumn: Boolean;
 Index: Integer);
begin
 if isColumn then with StringGrid1 do
  begin
    col:=Index;                                                  // Задаем столбец для сортировки
    if Tag  = 0 then Tag :=-1                                    // Предположим, что данные в столбце возрастают
                else Tag := Tag  * -1;                           // Используем StringGrid1.Tag для хранения порядка сортировки  
                                                                 // Первый щелчок заголовка сортирует по убыванию 
   
     // Активируем CompareCells (активируем сортировку)                                                                            
    case Index of
      0: col:=-1;                                                // Не сортируем столбец 0 и не показываем стрелку сортировки
      1: SortColRow(True, Index);                                // Сортировка будет происходить в CompareCells, как только будет выполнен оператор
      2: SortColRow(True, Index);
      3: SortColRow(True, Index);
      4: SortColRow(True, Index);
      5: col:=-1;                                                // Не сортируем столбец 5 и не показываем стрелку сортировки
    end;
  end;
end;

// Compare возвращает одно из следующих значений:
// if A<B  return: -1            ( Восходящий )
// if A=B  return:  0
// if A>B  return:  1            ( Нисходящий )

// Как сортировать: Text, Integer, Float, DateTime:

procedure TForm1.StringGrid1CompareCells(Sender: TObject; ACol, ARow, BCol,
 BRow: Integer; var Result: integer);

  function CompareTextAsText: Integer;                         // Text
  begin
    CompareText(Cells[ACol,ARow], Cells[BCol,BRow]);
  end;

  function CompareTextAsInteger: Integer;
  var i1,i2:Integer;
  begin
    if TryStrToInt(StringGrid1.Cells[ACol,ARow],i1) and       // Integer
       TryStrToInt(StringGrid1.Cells[BCol,BRow],i2) then
    Result := CompareValue(i1, i2)
    else Exit(0);
  end;  

  function CompareTextAsFloat: Integer;                        // Float
  var f1,f2:Extended;
  begin
    if TryStrToFloat(StringGrid1.Cells[ACol,ARow],f1) and
       TryStrToFloat(StringGrid1.Cells[BCol,BRow],f2) then
    Result := CompareValue(f1, f2)
    else Exit(0);
  end;

  function CompareTextAsDateTime: Integer;                  // DateTime
  var
  D1, D2 : TDateTime;
  begin
    if Trim(StringGrid1.Cells[ACol,ARow]) <> Trim(StringGrid1.Cells[BCol,BRow]) then
    begin
      if trim(StringGrid1.Cells[ACol,ARow]) = '' then Exit(1);
      if Trim(StringGrid1.Cells[BCol,BRow]) = '' then Exit(-1);
    end
    else Exit(0);

    if TryStrToDate(StringGrid1.Cells[ACol,ARow], D1, 'yy/mm/dd') and
       TryStrToDate(StringGrid1.Cells[BCol,BRow], D2, 'yy/mm/dd') then
    Result := CompareDateTime(D1, D2)
    else Exit(0);
 end;

begin                                        // Не делайте здесь ничего другого, сортировка может быть выполнена некорректно
  With    StringGrid1 do
  begin
    case ACol of
      0 : ;                                                                     // Не сортируйте столбец 0
      1 :  Result := CompareTextAsText;                                         // Text
      2 :  Result := CompareTextAsInteger;                                      // Integer
      3 :  Result := CompareTextAsFloat;                                        // Float
      4 :  Result := CompareTextAsDateTime;                                     // DateTime
    end; {Case}
  end;
  if  StringGrid1.Tag  = -1 then   Result := -Result;                           // Переключаем порядок сортировки
end;  

// DrawCell сделает трюк, предыдущие изображения стрелки будут удалены: 
procedure TForm1.StringGrid1DrawCell(Sender: TObject; aCol, aRow: Integer;
  aRect: TRect; aState: TGridDrawState);
   
   procedure DrawArrow(ImgIndex:Integer);                  // Нужны стрелки вверх/вниз в ImageList
   begin
     with StringGrid1 do
       TitleImageList.Draw(Canvas,                         // TitleImageList из StringGrid
       ARect.Right - TitleImageList.Width,
      (ARect.Bottom - ARect.Top - TitleImageList.Height) div 2 + 2,  // Положение стрелки, может быть отрегулировано
       ImgIndex );
   end;
begin
  if Col > 0 then                                    // В этом демо нет стрелок в col 0  
    if (ARow = 0) and                                // Предположим, строка 0 в качестве заголовка строк
       Assigned(TitleImageList) then                 // Нужны стрелки вверх/вниз в ImageList
         case Columns.Items[aCol].Tag of             // Использовать тег столбца как индекс Восходящей/Нисходящей стрелки
           -1: DrawArrow(1);                         // Заменяем Index указателем стрелки вниз
            0: DrawArrow(-1);                        // Удаляем предыдущее изображение
            1: DrawArrow(2);                         // Заменяем Index указателем стрелки вверх
         end;
end;

Сортировка столбцов или строк в DBGrid со стрелками сортировки в заголовке столбца

Вот пример, который будет сортировать DBgrid, используя событие OnTitleClick и TSQLQuery и индексы. Это также должно работать для любого совместимого набора данных, такого как TbufDataset. Функция использует свойство column.tag для хранения состояния сортировки для каждого столбца, на который вы нажимаете, поэтому, когда вы вернетесь к тому [столбцу], который вы уже отсортировали, [заголовок столбца] отобразит правильную стрелку сортировки для отображения.

Предпосылки:

  • Для хранения [изображений] стрелок вверх/вниз вам понадобится imagelist. Присвойте свой imagelist свойству TitleImageList dbgrid'а.
  • Убедитесь, что TSQLQuery.MaxIndexesCount достаточно велик, чтобы хранить новые индексы, которые вы будете создавать. Для этого примера я установил значение 100.
  • Вам также понадобится переменная-поле [(в разделе интерфейса private)] для хранения последнего столбца, который используется для сортировки, для этого примера я назвал его FLastColumn.

В этом примере используемый TSQLQuery называется OpenQuery.

Note-icon.png

Примечание: По состоянию на 21 марта 2013 года TSQLQuery не имеет возможности очищать индексы, но в качестве обходного пути вы можете установить [значение его свойства] unidirectional в true, а затем в false: это очистит индексы.

Чтобы повторно использовать компонент TSQLQuery с другим оператором SQL, индексы должны быть очищены после сортировки, [иначе] при открытии TSQLQuery с помощью другого оператора SQL будет вызвано исключение.

FLastColumn: TColumn; //сохраняем последний столбец сетки, который мы отсортировали

procedure TSQLForm.ResultsGridTitleClick(Column: TColumn);
const
  ImageArrowUp=0; //должен соответствовать изображению в imagelist
  ImageArrowDown=1; //должен соответствовать изображению в imagelist
var
  ASC_IndexName, DESC_IndexName:string;
  procedure UpdateIndexes;
  begin
    // Обеспечиваем обновление индексов
    OpenQuery.IndexDefs.Updated:=false; {<<<--Эта строка - решающая. IndexDefs.Update не будет
    обновляться, если уже true, что произойдет в первом отсортированном столбце.}
    Openquery.IndexDefs.Update;
  end;
begin
  ASC_IndexName:='ASC_'+Column.FieldName;
  DESC_IndexName:='DESC_'+Column.FieldName;
  // индексы не могут сортировать двоичные типы, такие как ftMemo, ftBLOB
  if (Column.Field.DataType in [ftBLOB,ftMemo,ftWideMemo]) then
    exit;
  // проверяем, существует ли восходящий индекс для этого столбца
  // если нет, создадим [его]
  if OpenQuery.IndexDefs.IndexOf(ASC_IndexName) = -1 then
  begin
    OpenQuery.AddIndex(ASC_IndexName,column.FieldName,[]);
    UpdateIndexes; //обеспечиваем обновление индексов
  end;
  // проверяем, существует ли нисходящий индекс для этого столбца
  // если нет, создадим [его]
  if OpenQuery.IndexDefs.IndexOf(DESC_IndexName) = -1 then
  begin
    OpenQuery.AddIndex(DESC_IndexName,column.FieldName,[ixDescending]);
    UpdateIndexes; //обеспечиваем обновление индексов
  end;

  // Используем тег столбца для переключения ASC/DESC
  column.tag := not column.tag;
  if boolean(column.tag) then
  begin
    Column.Title.ImageIndex:=ImageArrowUp;
    Openquery.IndexName:=ASC_IndexName;
  end 
  else
  begin
    Column.Title.ImageIndex:=ImageArrowDown;
    OpenQuery.IndexName:=DESC_IndexName;
  end;
  // Удаляем стрелку сортировки из предыдущего столбца, который мы отсортировали
  if (FLastColumn <> nil) and (FlastColumn <> Column) then
    FLastColumn.Title.ImageIndex:=-1;
  FLastColumn:=column;
end;

Выбор записей в DBGrid с использованием checkbox'ов

Цель состоит в том, чтобы иметь возможность выбирать произвольные записи в dbgrid с помощью checkbox'ов, сетка имеет возможность показывать checkbox'ы автоматически, когда обнаруживает наличие логических полей, а для других типов полей пользователь может вручную [в свойстве] ButtonStyle столбца выбрать [значение] cbsCheckboxColumn. Для таких столбцов пользователь просто помечает checkbox, и содержимое поля изменяется соответствующим образом.

Но что произойдет, если в нашем наборе данных нет такого доступного поля? или мы не хотим, чтобы сетка входила в состояние редактирования при изменении состояния checkbox'а?. Добавление столбца без полей с помощью ButtonStyle=cbsCheckboxColumn покажет все checkbox'ы с неопределенным значением и отключенными, потому что нет поля, связанного с этим столбцом, и поэтому ничего не нужно изменять. Поскольку мы хотим самостоятельно обрабатывать состояние checkbox'а, нам нужно где-то сохранить состояние для каждой записи. Для этого мы можем использовать класс TBookmarklist (определенный в модуле dbgrids.pas), где свойство CurrentRowSelected может определить, выбрана ли текущая запись или нет. Используя события dbgrid'а OnCellClick и OnUserCheckboxState, мы можем отслеживать состояние checkbox'а.

Обратите внимание, что этой методике нужен Lazarus r31148 или более поздней версии, которая реализует событие OnUserCheckboxState.

...
uses ..., dbgrids, stdctrls, ...

type

  { TForm1 }

  TForm1 = class(TForm)
  ...
    procedure DBGrid1CellClick(Column: TColumn);
    procedure DBGrid1UserCheckboxState(sender: TObject; column: TColumn; var AState: TCheckboxState);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  ...
  private
    RecList: TBookmarklist;
  ...
  end;

procedure TForm1.DBGrid1CellClick(Column: TColumn);
begin
  if Column.Index=1 then
    RecList.CurrentRowSelected := not RecList.CurrentRowSelected;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  RecList := TBookmarkList.Create(DbGrid1);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  RecList.Free;
end;

procedure TForm1.DBGrid1UserCheckboxState(sender: TObject; column: TColumn; var AState: TCheckboxState);
begin
  if RecList.CurrentRowSelected then
    AState := cbChecked
  else
    AState := cbUnchecked;
end;

Подсветка выделенного столбца и строки ячейки

В версии Lazarus 40276 добавлена опция (gdRowHighlight), она работает аналогично goRowSelect, но использует более светлый тон для выделения, а прямоугольник фокуса - только выбранная ячейка, а не в целая строка. Это отлично работает для строк, но как насчет столбцов?

В этом разделе показан способ выделения столбцов, строк или и того, и другого (пример, который выделяет только столбцы и заголовки строк, можно найти в lazarus/examples/gridexamples/spreadsheet, этот способ действительно является расширением этого примера). Он использует два события сетки: OnBeforeSelection и OnPrepareCanvas, рисунок не нужен.

Событие OnBeforeSelection запускается, когда начинается смена выделения, в этом случае мы можем узнать, какая ячейка выбрана в данный момент и какая ячейка будет выбрана следующей. Мы используем эту информацию для перерисовки всей строки или столбца как старой, так и новой ячеек. Когда начнется следующий цикл отрисовки, сетке будет предложено перерисовать ячейки, принадлежащие недействительным областям. Одним из первых шагов для перерисовки является вызов события OnPrepareCanvas (если он существует) для настройки свойств холста по умолчанию. Мы используем это событие для настройки выделенной строки или столбца:

procedure TForm1.gridBeforeSelection(Sender: TObject; aCol, aRow: Integer);
begin
  // мы можем здесь принять решение, если хотим выделить столбцы, строки или и те, и другие
  // в этом примере мы выделяем и те, и другие
  if Grid.Col<>aCol then
  begin
    // обнаружено изменение текущего столбца
    grid.InvalidateCol(aCol);      // перерисовываем новый выбранный столбец ячейки
    grid.InvalidateCol(grid.Col);  // перерисовываем текущий (это будет 'старый') выбранный столбец
  end;
  if Grid.Row<>aRow then
  begin
    grid.InvalidateRow(aRow);      // перерисовываем новую выбранную строку ячейки
    grid.InvalidateRow(grid.Row);  // перерисовываем текущую (это будет 'старая') выбранную строку
  end;
end; 

procedure TForm1.gridPrepareCanvas(sender: TObject; aCol, aRow: Integer;
  aState: TGridDrawState);
begin
  if gdFixed in aState then
  begin
    if (aCol=grid.Col) or (aRow=grid.Row) then
      grid.Canvas.Brush.Color := clInactiveCaption; // это подсветило бы также заголовки столбцов или строк
  end else
  if gdFocused in aState then begin
    // мы оставляем в покое текущую выделенную/сфокусированную ячейку
  end else
  if (aCol=Grid.Col) or (aRow=grid.Row) then
    grid.Canvas.Brush.Color := clSkyBlue; // подсветка строк и столбцов цветом clSkyBlue
end;

DBGrids "Как сделать..." и Примеры

Пример: как установить редактор memo для dbgrids

Конечно, вы можете использовать другой элемент управления вместо TMemo. Отрегулируйте по вкусу. Адаптировано из [3] (упрощено использование свойства SelectedFieldRect, см. [4])

  • Поместите элемент управления memo (или любой другой элемент управления) на форму, установите любые свойства, которые вы хотите, и установите видимым значение false. Он будет использоваться при редактировании ячейки сетки. Мы будем использовать GridCellMemo в этом примере.
  • В событии OnSelectEditor вставьте следующий код - адаптируйте, если вы не хотите редактировать логический столбец 3, физический столбец 4:
  if (Column.DesignIndex = 3) then
  begin
      GridCellMemo.BoundsRect := DbGrid.SelectedFieldRect;
      GridCellMemo.Text:=Column.Field.AsString;
      Editor := GridCellMemo;
  end;
  • Предположим, ваш источник данных называется Datasource1, а DBGrid называется ResultsGrid. Затем в событие OnEditingDone для GridCellMemo поместите следующее:

(В исходном сообщении на форуме использовалось событие OnChange, которое будет запускаться каждый раз при изменении содержимого. Использование OnEditingDone срабатывает только после того, как пользователь завершит редактирование.)

  if not(Datasource1.State in [dsEdit, dsInsert]) then Datasource1.Edit;
    
  Datasource1.DataSet.FieldByName(ResultsGrid.SelectedField.FieldName).AsString:=GridCellMemo.Text;

Осторожно Использование обычного элемента управления в качестве пользовательского редактора имеет много недостатков: он не будет изменять размеры или перемещать себя при изменении размера столбцов или прокрутке сетки, вы должны позаботиться о модификации данных в сетке. (как объяснено выше), он не работает должным образом с DbGrids и мешает нормальной навигации по сетке с помощью клавиатуры. Сетка взаимодействует с редактором с помощью некоторых специальных сообщений сетки, поэтому для управления этими сообщениями лучше выделить подкласс элемента управления, как объясняется в пятой главе этого документа.

Осторожно Если у вас несколько сеток в одной форме, используйте для каждой свой собственный редактор: если вы используете один и тот же элемент управления, могут произойти плохие вещи, поскольку обе сетки попытаются использовать его одновременно.

Пример: исключение отображения текстовых полей как «(Memo)» для DBGrids

Когда вы используете операторы SQL, такие как "Select * from A", вы можете увидеть «(Memo)» вместо текстового содержимого в ячейке сетки. Есть много способов исправить это, но, возможно, самый простой - изменить выражение "Select * from A" на Select Cast(Column as TEXT) as Column from A. Это распространенная проблема при использовании компонента DBGrid.

 var
    sql : UTF8String;
    Query : TSQLQuery;
.....
    sql := 'SELECT cast(Name as TEXT) as Name FROM Client';
    Query.SQL.Text:=sql;

Сортировка столбцов или строк в DBGrid со стрелками сортировки в заголовке столбца

Вот пример, который будет сортировать DBgrid, используя событие OnTitleClick и TSQLQuery и индексы. Это также должно работать для любого совместимого набора данных, такого как TbufDataset. Функция использует свойство column.tag для хранения состояния сортировки для каждого столбца, по которому вы щелкнули, поэтому, когда вы вернетесь к тому, который уже отсортирован, она выберет правильную стрелку сортировки для отображения.

Предварительные условия:

  • Вам понадобится список изображений для хранения стрелок вверх/вниз. Присвойте свой список изображений свойству dbgrid TitleImageList.
  • Убедитесь, что свойство TSQLQuery.MaxIndexesCount достаточно велико для хранения новых индексов, которые вы будете создавать. Для этого примера у меня было установлено значение 100.
  • Вам также понадобится поле-переменная в разделе private класса для хранения последнего столбца, использованного для сортировки, для этого примера я назвал ее FLastColumn.

В этом примере используемый TSQLQuery назван OpenQuery.

Note-icon.png

Примечание: по состоянию на 21 марта 2013 года TSQLQuery не имеет возможности очищать индексы, но в качестве обходного пути вы можете установить свойство unidirectional в значение true, а затем установить его в значение false, и это очистит индексы.

Чтобы повторно использовать компонент TSQLQuery с другим оператором SQL, индексы должны быть очищены после сортировки, иначе будет возбуждено исключение при открытии TSQLQuery с другим оператором SQL.

FLastColumn: TColumn; //сохраняем последний столбец сетки, по которому мы делали сортировку

procedure TSQLForm.ResultsGridTitleClick(Column: TColumn);
const
  ImageArrowUp=0; //должно соответствовать изображению в imagelist
  ImageArrowDown=1; //должно соответствовать изображению в imagelist
var
  ASC_IndexName, DESC_IndexName:string;
  procedure UpdateIndexes;
  begin
    // Убедитесь, что индексы обновлены
    OpenQuery.IndexDefs.Updated:=false; {<<<--Эта строчка имеет решающее значение. IndexDefs.Update не будет обновляться, если уже имеет значение true, что произойдет в первом отсортированном столбце.}
    Openquery.IndexDefs.Update;
  end;
begin
  ASC_IndexName:='ASC_'+Column.FieldName;
  DESC_IndexName:='DESC_'+Column.FieldName;
  // индексы не могут сортировать двоичные типы, такие как ftMemo, ftBLOB
  if (Column.Field.DataType in [ftBLOB,ftMemo,ftWideMemo]) then
    exit;
  // проверяем, существует ли восходящий индекс для этого столбца.
  // если нет, создаем
  if OpenQuery.IndexDefs.IndexOf(ASC_IndexName) = -1 then
  begin
    OpenQuery.AddIndex(ASC_IndexName,column.FieldName,[]);
    UpdateIndexes; //ensure index defs are up to date
  end;
  // проверяем, существует ли нисходящий индекс для этого столбца.
  // если нет, создаем
  if OpenQuery.IndexDefs.IndexOf(DESC_IndexName) = -1 then
  begin
    OpenQuery.AddIndex(DESC_IndexName,column.FieldName,[ixDescending]);
    UpdateIndexes; //ensure index defs are up to date
  end;

  // Используем тег столбца для переключения направления сортировки ASC/DESC
  column.tag := not column.tag;
  if boolean(column.tag) then
  begin
    Column.Title.ImageIndex:=ImageArrowUp;
    Openquery.IndexName:=ASC_IndexName;
  end 
  else
  begin
    Column.Title.ImageIndex:=ImageArrowDown;
    OpenQuery.IndexName:=DESC_IndexName;
  end;
  // Удаляем стрелку сортировки из предыдущего столбца, который мы отсортировали
  if (FLastColumn <> nil) and (FlastColumn <> Column) then
    FLastColumn.Title.ImageIndex:=-1;
  FLastColumn:=column;
end;

Выбор записей в DBGrid с помощью флажков(чекбоксов)

Цель состоит в том, чтобы иметь возможность выбирать произвольные записи в dbgrid с помощью флажков, сетка имеет возможность автоматически отображать флажки, когда обнаруживает наличие логических полей, для других типов полей пользователь может вручную выбрать значение cbsCheckboxColumn свойства ButtonStyle для столбца. Для этого типа столбцов пользователь просто устанавливает флажок, и содержимое поля изменяется соответствующим образом.

Но что произойдет, если в нашем наборе данных нет такого доступного поля? Или мы не хотим, чтобы сетка входила в состояние редактирования при установке флажка? При добавлении столбца без полей с помощью ButtonStyle = cbsCheckboxColumn все флажки будут выделены серым цветом и отключены, поскольку поле, связанное с этим столбцом, отсутствует и поэтому изменять нечего. Поскольку мы хотим обрабатывать состояние флажка самостоятельно, нам нужно хранить состояние где-то для каждой записи. Для этого мы можем использовать класс TBookmarklist (определенный в модуле dbgrids.pas), где свойство CurrentRowSelected может сказать, выбрана текущая запись или нет. Используя события dbgrid OnCellClick и OnUserCheckboxState, мы можем отслеживать состояние флажка.

Note-icon.png

Примечание: эта хитрость требует наличие Lazarus r31148 или новее, который реализует событие OnUserCheckboxState.

...
uses ..., dbgrids, stdctrls, ...

type

  { TForm1 }

  TForm1 = class(TForm)
  ...
    procedure DBGrid1CellClick(Column: TColumn);
    procedure DBGrid1UserCheckboxState(sender: TObject; column: TColumn; var AState: TCheckboxState);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  ...
  private
    RecList: TBookmarklist;
  ...
  end;

procedure TForm1.DBGrid1CellClick(Column: TColumn);
begin
  if Column.Index=1 then
    RecList.CurrentRowSelected := not RecList.CurrentRowSelected;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  RecList := TBookmarkList.Create(DbGrid1);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  RecList.Free;
end;

procedure TForm1.DBGrid1UserCheckboxState(sender: TObject; column: TColumn; var AState: TCheckboxState);
begin
  if RecList.CurrentRowSelected then
    AState := cbChecked
  else
    AState := cbUnchecked;
end;

Пример: Как экспортировать данные из DBGrid в текстовый файл

Есть несколько способов создать текстовый файл из содержимого DBGrid. В большинстве случаев люди будут говорить вам, что вместо того, чтобы думать о DBGrid, как о контейнере данных, лучше поискать связанный набор данных.

Но что произойдет, когда вам понадобится текстовое представление, которое должно соответствовать внешнему виду сетки: видимость колонок, ширина или порядок?

Одним из решений является использование пакета LazReport, в котором есть компонент, специально предназначенный для создания отчета из DbGrid. Затем этот отчет можно экспортировать в несколько других форматов, включая текстовый формат. Здесь представлено простое решение, которое также может быть использовано в качестве основы для других экспортеров текстового формата, например, простого экспортера HTML.

// aGrid: Сетка для экспорта
// lpp: строк на странице. Не включает текстовый заголовок
// pageBreak: вставляет разрыв страницы, который некоторые принтеры могут использовать для запуска новой страницы
// require: uses LazUTF8
function GridTextExporter(aGrid: TDbGrid; lpp: Integer; pageBreak:boolean): TStringList;
const
  PX_PER_CHAR = 7;

var
  Ds: TDataset;
  book: TBookMark;
  recCount: Integer;
  line: string;
  column: TColumn;

  function WidthToChar(aWidth: Integer): integer;
  begin
    result := trunc(aWidth/PX_PER_CHAR);
  end;

  procedure AddNext(theText: string; alignment: TAlignment);
  var
    width: Integer;
  begin
    if (line<>'') and (line<>#12) then
      line := line + ' ';

    width := WidthToChar(Column.Width);

    case Alignment of
      taRightJustify: line := line + UTF8PadLeft(theText, width);
      taCenter:       line := line + UTF8PadCenter(theText, width);
      else            line := line + UTF8PadRight(theText, width);
    end;
  end;

  procedure CollectHeader;
  begin
    AddNext(Column.Title.Caption, Column.Title.Alignment);
  end;

  procedure CollectField;
  var
    field: TField;
  begin
    field := Column.Field;

    if (field.DataType=ftMemo) and (dgDisplayMemoText in aGrid.Options) then
      AddNext(field.AsString, Column.Alignment)
    else if Field.DataType<>ftBlob then
      AddNext(field.DisplayText, Column.Alignment)
    else
      AddNext('(blob)', Column.Alignment);
  end;

  procedure LoopColumns(printingRecord: boolean);
  var
    c: TCollectionItem;
  begin
    if (not printingRecord) and pageBreak and (result.count>0) then
      line := #12
    else
      line := '';
    for c in aGrid.Columns do begin
      Column := TColumn(c);
      if Column.Visible and (Column.Width>=PX_PER_CHAR) then begin
        if printingRecord then CollectField
        else                   CollectHeader;
      end;
    end;
    result.add(line);
  end;

begin
  result := TStringList.create;
  Ds := aGrid.DataSource.DataSet;
  Ds.DisableControls;
  book := Ds.GetBookmark;
  try
    Ds.First;
    recCount := 0;
    while not DS.EOF do begin
      if RecCount mod lpp = 0 then
        LoopColumns(false);
      LoopColumns(true);
      inc(recCount);
      Ds.Next;
    end;
  finally
    Ds.GotoBookmark(book);
    Ds.FreeBookmark(book);
    Ds.EnableControls;
  end;
end;

Чтобы использовать это просто сделайте:

var
  L: TStringList;
begin
  L := GridTextExporter(grid, 80, true);
  L.SaveToFile('grid.txt');
  L.Free;
end;