Копия викизеркало

Это зеркало вики с pascal.chat. Правка, история и вход — там.

Grids Reference Page/ru

Deutsch · English · Español · Polski · Русский

Цель

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

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

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

Введение

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

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

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

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

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

text
                     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 и запишите следующий код в обработчике щелчка кнопки:
    • pascal
      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, и это соблазнительно.

Различия

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

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

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

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

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

Информация

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

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

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

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

TCustomGrid

См. полную [[doc:lcl/grids/tcustomgrid.html|справку 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 мы:

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

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

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

pascal
  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, как описано [[Grids Reference Page/ru|выше]], либо установив свойство ColCount, и свойство сетки AutoFillColumns должно быть установлено в true. Каждый столбец имеет свойство SizePriority. Если оно имеет значение 0, ширина столбца берется из свойства Width столбца. Но когда оно имеет ненулевое значение, ширина столбца корректируется до среднего доступного размера, оставшегося для всех столбцов с ненулевым SizePriority.

TCustomDBGrid

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

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

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

процедура AutoSizeColumns

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

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

процедура InplaceEditor

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

pascal
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.

См. полную [[doc:lcl/grids/tcustomstringgrid.html|справку TCustomStringGrid]]

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

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

процедура AutoSizeColumns;

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

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

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

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

Очищает все ячейки в сетке в соответствии с данными CleanOptions. Дополнительную информацию см. в [[doc:lcl/grids/tgridzoneset.html|TGridZoneSet]]. Некоторые примеры:

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

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

pascal
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 вместо отдельных координат ячейки. Для очистки выделения полезно [сделать]:

pascal
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/ru|CsvDocument]] ... и, конечно, патч для LoadFromCSVFile всегда приветствуется.

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

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

Примеры
pascal
//задать содержимое третьего столбца в сетке из списка ListBox
Grid.Cols[2] := ListBox1.Items;
pascal
//задать содержимое 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 для извлечения содержимого строки. Пользователь должен освобождать этот объект после использования.

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

Примеры
pascal
//задать содержимое третьего столбца в сетке из списка ListBox
Grid.Rows[2] := ListBox1.Items;
pascal
//задать содержимое 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;
pascal
// это не сработает и вызовет утечку памяти
// потому что возвращаемый 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".

свойство 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 имеет определенные ограничения

свойство 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, изменяя свойства холста по умолчанию, такие как цвет кисти или шрифт. Ниже приведен список таких свойств:

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

Options.

Свойство Options - это набор некоторых элементов для обеспечения разнообразной функциональности, но некоторые из них напрямую связаны с внешним видом сетки. Эти параметры можно установить в режиме разработки(designtime) или времени выполнения(runtime).

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

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

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

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

Различия с Delphi

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

Расположение текущей (сфокусированной) ячейки (или строки) сетки может быть изменено с помощью клавиатуры, мыши или кода. Чтобы успешно сменить фокус ячейки на другую позицию, мы должны проверить целевую позицию, чтобы увидеть, разрешено ли ей получать фокус ячейки. При использовании клавиатуры свойство 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

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

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

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

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

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

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

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

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

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

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

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

pascal
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 для кнопки.
pascal
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
<?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 может быть выполнена с помощью подобного кода:

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

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

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

Это стиль редактора по умолчанию для сеток-наследников TCustomGrid. Фактический класс редактора, который будет использоваться для редактирования содержимого ячейки, зависит от нескольких факторов. Для TCustomGrids он использует класс TStringCellEditor, полученный из TCustomMaskEdit. Этот редактор специализирован для редактирования строк одной строки. Затем он используется по умолчанию в TStringGrid и TDrawGrid. При использовании пользовательских столбцов, если программист заполнил свойство PickList столбца, [редактор] ведет себя так, как если бы был установлен стиль редактора cbsPickList. Для TCustomDBGrid, который имеет поле типа boolean, он ведет себя так, как если бы был указан стиль редактора cbsCheckBoxColumn. Это рекомендуемое значение для Custom Cell Editors. TODO: сослаться на OnEditingDone.

Этот стиль редактора является наиболее общим. При [его] использовании в ячейке редактирования появляется кнопка, и программисты могут использовать событие сетки OnEditButtonClick, чтобы определить, когда пользователь нажал кнопку, и выполнить любое действие, запрограммированное для такой ячейки. Например, программист может использовать этот стиль редактора, чтобы открыть диалог календаря, чтобы пользователь мог легко выбрать конкретную дату. Другие возможности могут заключаться в том, чтобы показать диалог открытия файла для поиска файлов, калькулятор, чтобы пользователь мог ввести числовой результат вычислений и т.д.

OnEditButtonClick - это просто уведомление, чтобы узнать, в какой ячейке нажата кнопка, взглянув на свойства grid.Row и grid.Col.

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

Этот стиль редактора реализован с использованием TButtonCellEditor, прямого потомка TButton.

Этот стиль редактора указывает, что сетка не должна использовать какой-либо редактор для конкретной ячейки или столбца; [это поведение аналогично тому], как если бы такая ячейка или столбец сетки были только для чтения.

Используется для представления пользователю списка значений, которые могут быть введены. Этот стиль редактора реализован с использованием TPickListCellEditor, компонента, полученного из TCustomComboBox. Список отображаемых значений заполняется одним из двух способов в зависимости от метода, используемого для выбора стиля редактора.

  1. При использовании настраиваемых столбцов программисты могут вводить список значений, используя свойство PickList столбца. [ДЛЯ НОВИЧКОВ: TODO: точная процедура для редактирования списка]
  2. В OnSelectEditor программисты получают экземпляр TPickListCellEditor, используя функцию EditorByStyle(cbsPickList). [[Grids Reference Page/ru|См. пример здесь]]

Значение в сетке TStringGrid будет автоматически отображать выбранное значение. При необходимости программист может определить момент, когда значение выбрано, путем написания обработчика для события OnPickListSelect сетки, поэтому могут быть предприняты дополнительные шаги (например, для обработки нового значения). TODO: связать с OnEditingDone.

Это может быть полезно, когда содержимое данных, связанное с столбцом, ограничено парой значений, например да-нет, true-false, вкл-выкл, 1-0 и т.д. Вместо того, чтобы принуждать пользователя вводить значения для данных такого типа в StringCellEditor или выбирать один из списка, cbsCheckboxColumn используется для изменения данных столбца с помощью представления флажка, которое пользователь может переключать, используя щелчок мыши или нажимая клавишу SPACE (если столбец, содержащий этот флажок, а также StringGrid доступны для редактирования).

Получение или установка значения флажка в ячейке осуществляется следующим образом:

pascal
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 и параметр растрового изображения, который программист может использовать для указания пользовательских растровых изображений.

Этот стиль редактора используется для отображения кнопки в каждой ячейке в столбце. Как и в случае 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). Эта опция изменяет редактируемое состояние сетки, ее можно изменить во время выполнения, поскольку она проверяет, когда начнется редактирование.

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

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

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

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

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

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

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

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

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

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

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

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

pascal
if (Column.DesignIndex = 3) then
  begin
      GridCellMemo.BoundsRect := DbGrid.SelectedFieldRect;
      GridCellMemo.Text:=Column.Field.AsString;
      Editor := GridCellMemo;
  end;

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

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

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

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

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

pascal
// Условно показываем кнопку редактора в столбцах индекс 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.

pascal
 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 изменяется на нечетных строках таким образом, что пользователь может вводить значения, набирая [их с клавиатуры], а на четных строках [вводимые] значения ограничиваются теми, которые содержатся в его списке. Кроме того, в этом примере показано, как заполнить список разными значениями в зависимости от обрабатываемой строки.

pascal
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.

pascal
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.

pascal
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, которое имеет следующее объявление:

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

где

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

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

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

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

pascal
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 - это логическое значение, которое указывает:

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

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

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

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

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

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

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

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

pascal
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, НЕ будет показывать стрелки сортировки в заголовке столбца, поэтому мы должны установить стрелки.)

pascal
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 для хранения состояния сортировки для каждого столбца, на который вы нажимаете, поэтому, когда вы вернетесь к тому [столбцу], который вы уже отсортировали, [заголовок столбца] отобразит правильную стрелку сортировки для отображения.

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

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

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

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

pascal
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.

pascal
...
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 (если он существует) для настройки свойств холста по умолчанию. Мы используем это событие для настройки выделенной строки или столбца:

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

pascal
  if (Column.DesignIndex = 3) then
  begin
      GridCellMemo.BoundsRect := DbGrid.SelectedFieldRect;
      GridCellMemo.Text:=Column.Field.AsString;
      Editor := GridCellMemo;
  end;

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

pascal
  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.

pascal
 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 для хранения состояния сортировки для каждого столбца, по которому вы щелкнули, поэтому, когда вы вернетесь к тому, который уже отсортирован, она выберет правильную стрелку сортировки для отображения.

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

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

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

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

pascal
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: эта хитрость требует наличие Lazarus r31148 или новее, который реализует событие OnUserCheckboxState.

pascal
...
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.

pascal
// 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;

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

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

From the Free Pascal wiki, page "Grids Reference Page/ru", as it stood on 2022-01-21 after a change by Slawek, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, message the admin.

Версия 3, последнее изменение: WikiArchive, .