SQLite и FireMonkey. Учимся заполнять таблицы.
С появлением в 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:
А чтобы указать значение для ячейки, воспользоваться свойством Cells:
При этом помните, что заголовок таблицы и содержимое таблицы - разные элементы (см. структуру компонента выше). То есть значение индексов строк в 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 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 секунды.
Исходник приложения (без DLL и базы данных) можно скачать по ссылке ниже: