SQLite и FireMonkey. Учимся заполнять таблицы.

Vlad

С появлением в 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 секунды.

Исходник приложения (без DLL и базы данных) можно скачать по ссылке ниже:


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=27282