С появлением в Delphi XE2 FireMonkey иногда ощущаю себя прям каким-то первоклассником =) Не сказать, чтобы прям уж так совсем все непонятно и сложно…скорее немного не привычно использовать новые FMX-контролы. Вот и сегодня от темы статьи так и напоминает что-то из разряда «Delphi для начинающих». Но, как говориться, из песни слов не выкинешь — раз решил поработать с FireMonkey, то начинать надо с простых вещей. Ну, а так как в статье про SQLite для Delphi XE2 было предложение расписать работу с Grid’ами в FMX, то попробуем написать небольшое приложение, которое будет заполнять табличку на основании запроса к базе SQLite.
И в начале несколько слов про SQLite в Delphi, а точнее про ту обертку с которой я буду работать. Дело в том, что это, наверное, самая простая обертка из всех, что мне встречались (собственно этим она мне и понравилась). Здесь нет компонентов, все объекты в модуле SQLiteTable3 — это классы-наследники от TObject, а вся работа напрямую связана с запросами к библиотеке SQLite (в Win-приложениях — это будет sqlite3.dll). Естественно, такая организация работы с SQLite накладывает некоторые ограничения на работу с БД SQLite. Например, я не могу взять и без лишних заморочек использовать TDataSet или использовать также просто как и при работе, например, с MSSQL DBGrid — придется искать свое решение, использовать TClientDataSet’ы и т.д. Но, мне такие решения в принципе не нужны были. Единственный раз когда мне приходилось использовать TDataSet, TDBGrid и т.д. был лет эдак пять-семь назад и то всё это «творчество» тогда использовало BDE. В общем, если у кого-то возникнет желание/потребность связать эту обертку с TDataSet — знайте, что сделать это просто и быстро врядли получится. Ну, а я рассмотрю пример того, как, используя SQLiteTable2.pas можно формировать таблицы в приложении Firemonkey.
В качестве примера, воспользуемся базой данных, в которой будет содержаться таблица, рассмотренная в предыдущей статье про SQLite:
CREATE TABLE [TestTable] ( [id] INTEGER PRIMARY KEY AUTOINCREMENT, [StringRow] TEXT, [NumberRow] INTEGER, [DateTimeRow] DATETIME);
Теперь посмотрим, что нам необходимо знать для того, чтобы заполнить таблицу.
Для того, чтобы заполнить таблицу на форме (не важно какую — из FMX или VCL) нам необходимо знать помимо значений полей набора данных ещё как минимум два значения: количество полей (столбцов) и количество записей (строк) в таблице. Иначе мы просто не сможем правильно установить количество строк и столбцов у нашего контрола на форме.
Для того, чтобы работать с самой БД мы должны знать какие таблицы в ней присутствуют.
Для примера создадим новое приложение «Firemonkey HD Application» и разместим на форме следующие элементы:
- TMemo — здесь мы будем просматривать/редактировать SQL-запросы
- TStringGrid (со вкладки Grids) — сюда будем выводить результат выполнения запроса
- 2 TButton для выполнения операции подключения к БД и выполнения SQL-запроса
- TComboBox — для хранения списка таблиц БД
- TOpenDialog — для выбора файла БД.
При нажатии на кнопку «Connect» мы будем подключаться к базе данных и считывать из неё названия всех таблиц. Я предложил использовать БД из предыдущей статьи, но фактически приложение сможет работать с любой указанной вами БД, поэтому списочек получать мы будем так:- Подключаем в uses модули
uses [..], SQLite3, SQLiteTable3;
- создаем в секции private класса формы следующие переменные:
type TForm1 = class(TForm) [...] private FBase: TSQLiteDatabase; FStmt: TSQLitePreparedStatement; FTable: TSQLiteUniTable; public end;
- На OnClick кнопки «Connect» пишем
if dlgOpenBase.Execute then begin try FBase:=TSQLiteDatabase.Create(dlgOpenBase.FileName); FBase.GetTableStrings('SELECT name FROM sqlite_master WHERE type="table" ORDER BY name',cbTables.Items); ShowMessage('Подключились. Названия таблиц прочитали'); except ShowMessage('Не смогли подключиться к базе данных'); end; end;
Здесь стоит отметить следующие моменты.
Во-первых, что за таблица sqlite_master к которой я так смело обратился? Это специальная таблица, которая содержится в любой БД SQLite и содержит сведения по базе данных. В моем случае я выбрал из sqlite_master только те записи, в которых содержатся сведения по таблицам БД, т.е. проверил значение поля type на присутствие в нем значения «table«. Можно было бы запросить и всю таблицу целиком. sqlite_master выглядит следующим образом:
CREATE TABLE sqlite_master ( TYPE TEXT, name TEXT, tbl_name TEXT, rootpage INTEGER, SQL TEXT );
Более подробно про эту таблицу можно почитать в официальном FAQ.
Во-вторых, стоит немного рассказать про метод GetTableStrings у TSQLiteDatabase. Дело в том, что не важно какого содержания SELECT вы отправите к базе данных, этот метод всегда вернет строковые значения из первого столбца (с индексом 0). Ну, а так как у меня результат запроса и содержит всего одно поле, то я без лишних проверок записал все значения в ComboBox.
Следующий момент — это отправка запроса к произвольной таблице БД. Здесь все довольно просто. Например, на OnChange ComboBox’а можно написать такой код:
TableName:=; FStmt:=TSQLitePreparedStatement.Create(FBase,'SELECT * FROM '+ComboBox1.Items[cbTables.ItemIndex]); FTable:=FStmt.ExecQuery; Memo1.Lines.Clear; Memo1.Lines.Add(FStmt.SQL);
То есть после выбора названия таблички в ComboBox’е просто считываем все значения.
Ну, а теперь подходим к главному. Получили мы все, что хотели из БД, а как теперь эти значения «вытянуть» на наш StringGrid? Вот здесь мы сделаем небольшое отступление от работы над приложением и проясним некоторые моменты по поводу FMX.
StringGrid в FireMonkey
Помните в начале статьи я сказал, что работать с контролами в FMX мне несколько непривычно? Про это я немного говорил в «Знакомстве с FireMonkey«, теперь же постараюсь рассказать более подробно.
Итак, раз вы (и я с вами) решили использовать в проекте FireMonkey, то первое, что стоит для себя уяснить — это то, что несмотря на схожесть названий контрол в FMX — это вообще не тоже самое, что контрол в VCL. У них общего столько же сколько у мопеда и мотоцикла Harley Davidson — оба на двух колесах.
Например, все, что общего у двух TButton — это родитель TComponent.
В FMX любой компонент — это контейнер. Контейнер может содержать в себе ещё один контейнер, а в том ещё 5 контейнеров и т.д. и при этом чем сложнее контрол — тем сложнее иерархия объектов. Все компоненты FMX собираются из базовых элементов, таких как TLayout, TRectangle и т.д. Вот она та самая «необычность» к которой надо привыкать в случае работы с FireMonkey.
Теперь, что касается StringGrid в FMX. Чтобы разобраться из чего состоит таблица обратимся к StyleBook. Бросаем на форму компонент TStyleBook, дважды кликаем по нему мышкой и в дереве ищем запись gridstyle:
Как видите в таблице FMX заголовок таблицы (header) и основное содержимое (content) — это разные элементы. То есть мы можем изменять стиль заголовков, не затрагивая при этом стиль строк таблиц. Но до этого нам пока ещё рано — надо разобраться как вообще управляться с таблицей.
Для того, чтобы добавить в таблицу новый столбец, достаточно дважды кликнуть по таблице на форме и в редакторе нажать кнопку AddItem. Например, на рисунке ниже представлен редактор в котором я добавил в таблицу пять столбцов и вид окна Structure, чтобы дать вам небольшое предстовление о том, что в итоге получится, так сказать «концепция контейнеров в действии»:

Можно через тот же редактор сделать и так, что каждый столбец таблицы будет содержать в себе ещё с десяток столбцов, но в визуальном плане это никак не отразиться — таблица в приложении так и будет двухмерная. Для каждого столбца таблицы мы можем задать свои особые свойства, например, для нас сегодня будет важно свойство Header:string — заголовок столбца.
Что касается ячеек таблицы, то каждый столбец (TStringColumn) содержит поле FCells, которое представляет собой массив строк. Свойства для этого поля нет, а вся работа с ячейками и строками осуществляется в родителе, т.е. с TStringGrid. И здесь отличий никаких в названиях свойств и методов от VCL нет. Например, чтобы указать количество строк, надо указать значение свойства RowCount:
MyStringGrid.RowCount:=10;
А чтобы указать значение для ячейки, воспользоваться свойством Cells:
MyStringGrid.Cells[0,0]:='Ячейка 0,0'
При этом помните, что заголовок таблицы и содержимое таблицы — разные элементы (см. структуру компонента выше). То есть значение индексов строк в VCL и FMX.StringGrid’ах будут различаться на 1 (см. рисунок ниже):
Двигаемся далее. Раз решал рассмотреть свойства, то стоит отметить, что у FMX.StringGrid нет свойства ColCount для изменения количества столбцов в runtime. Вместо него есть свойство только для чтения под названием ColumnCount. Но это отнюдь не означает, что мы не в состоянии поменять количество столбцов у FXM.StringGrid в runtime. Сделать это можно, например так:
//удаляем все столбцы из таблицы for I := StringGrid1.ColumnCount-1 downto 0 do StringGrid1.Columns[i].Release; //добавляем 4 новых столбца for i:=1 to 4 do StringGrid1.AddObject(TStringColumn.Create(nil));
В целом это все особенности, которые нам необходимо уяснить для дальнейшей работы с FMX.StringGrid. Резюмируем и двигаемся обратно в наше приложение:
- Количество строк меняется также как и в VCL — через свойство RowCount
- Строка с индексом 0 в FMX.StringGrid — это то же самое, что строка с индексом 1 в VCL, т.к. заголовок таблицы и набор строк — разные элементы компонента.
- У FMX.StringGrid нет свойства ColCount — изменение количества столбцов необходимо проводить через работу со свойством Columns и добавление новых столбцов как дочерних элементов, используя метод AddObject.
Краткий экскурс в FMX.StringGrid закончили — продолжим работу над приложением. Нам осталось только правильно заполнять табличку и научить программу выполнять произвольные запросы. Начнем с таблицы и напишем метод, с помощью которого будет строится таблица с данными из запроса:
procedure TForm1.DrawTable(ATable: TSQLiteUniTable); var i:integer; delta: integer; begin //смотрим сколько надо удалить/добавить столбуов delta:=StringGrid1.ColumnCount-ATable.FieldCount; if delta>0 then //в таблице столбцов больше чем надо - удаляем begin for I := 1 to delta do StringGrid1.Columns[StringGrid1.ColumnCount-1].Release; end else //не хватает столбуов - добавляем begin for I := 1 to abs(delta) do StringGrid1.AddObject(TStringColumn.Create(nil)); end; StringGrid1.RowCount:=0;//обнуляем количество строк try StringGrid1.BeginUpdate; //заполняем табличку данными while not ATable.EOF do begin {увеличиваем количество строк на 1} StringGrid1.RowCount:=StringGrid1.RowCount+1; for I := 0 to ATable.FieldCount-1 do begin {задаем заголовок столбца} StringGrid1.Columns[i].Header:=ATable.Fields[i].Name; {выписываем в ячейку последнее значение} StringGrid1.Cells[i,ATable.Row-1]:=ATable.FieldAsString(i); end; ATable.Next end; finally StringGrid1.EndUpdate; end; end;
Здесь мы вначале определяем сколько столбцов надо добавить/удалить из таблицы. Затем приводим таблицу в исходное состояние, т.е. устанавливаем необходимое количество столбцов и строк. И наконец, проходимя по каждой записи в TSQLiteUniTable выводим значения полей в соответствующие ячейки FMX.StringGrid.
Теперь остается только «навесить» необходимое событие на кнопку «Выполнить SQL» и вызвать только что созданный метод. Обработчик OnClick кнопки будет следующим:
if not Assigned(FStmt) then FStmt:=TSQLitePreparedStatement.Create(FBase); FStmt.SQL:=Memo1.Text;//запомнили и подготовили запрос FTable:=FStmt.ExecQuery();//выполнили запрос и получили объект-таблицу DrawTable(FTable)//передали таблицу в метод и отрисовали StringGrid
Ну и в заключение вид приложения в котором показана таблица FMX.StringGrid, содержащая ту самую 1000 записей, которые мы добавляли в прошлый раз:
На отрисовку таблички ушло чуть больше 1 секунды.
Книжная полка
![]() |
Описание Книга основана на материалах лекций и практических занятий, разработанных автором, и объединяет теоретические основы и практические аспекты разработки реляционных баз данных.
|
Купить на ЛитРес |
![]() |
Автор: Анатолий Хомоненко, Владимир Гофман Название:Работа с базами данных в Delphi Описание: Рассматривается использование средств Delphi для разработки приложений баз данных. Даются понятия баз данных, характеризуются элементы и описываются этапы проектирования реляционных баз данных, изложена технология разработки информационных систем, освещаются приемы работы с данными, создание таблиц и приложений баз данных, подготовка отчетов. |
Купить на ЛитРес |




Спасибо за материал! WebDelphi — один из лучших блогов, по-моему, по Delphi-тематике!
Теперь по материалу поста: логичнее было бы, если бы была возможность, хотя бы через LiveBinding, «привязать» поле набора данных к столбцу грида. Или в данном конкретном случае это особенность TSQLiteDatabase (TSQLiteTable), не являющихся потомками TDataset?
angryvitum, спасибо за отзыв. LiveBinding можно использовать в любом случае, несмотря на то, что TSQLiteDatabase — это не потомок датасетов, а простой TObject. Связать можно как простой объект и VCL — об этом я рассказывал http://www.webdelphi.ru/2011/09/delphi-xe2-livebindings-dlya-obektov/. Правда придётся использовать не TBingExpression, а TBindList
По поводу мотоциклов. А то что выше TComponent включая TComponent это FMX или VCL ?
Георгий, TComponent и выше — из Classes. Собственно в официальной wiki это даже наглядно нарисовано:
Ок, спасибо за ответ
Спасибо за статью очень понравилась :) У меня стойкое ощущение дежавю, т.к. X-лет назад сам занимался заполнением TStringGrid данными из запросов, воспоминания прям нахлынули :)
А почему не захотели использовать LiveBindin, если не секрет?
elpik, по поводу LiveBindings — не секрет =) Я просто решил сделать два поста. В первом без LiveBindings, во втором — с LiveBindings. Но вот сейчас небольшая заминка по времени есть, поэтому второй пост задерживается
У меня вопрос, как можно узнать у какой ячейке сейчас фокус в StringGrid текущую колонку я получаю, а вот строку не могу. Заранее спасибо
При добавлении строки: {увеличиваем количество строк на 1} StringGrid1.RowCount := StringGrid1.RowCount + 1; у меня удаляются значения в ячейках. Как лечить?yura,
var
CurrCell:string;
begin
CurrCell:=StringGrid.Cell[Stringgrid.ColumnIndex, StringGrid1.Selected];
end;