Проект DataKeeper предполагает, что конечный пользователь будет создавать различные конфигурации объектной модели для решения своих задач, но само приложение при этом дорабатывать не потребуется. Штатный механизм переключения между базами SQLite не очень удобный и предполагает работу с модальной формой настройки параметров. В качестве альтернативного решения предлагаю набор процедур и функций, с помощью которых управление переносится в главное меню. В решении широко используются функции и процедуры из других модулей проекта.

Константы и переменные

const
  // параметры управления базой
  DBASE_CHANGE_DATABASE = 'Смена базы данных';
  DBASE_VACUUM_COMPLETE = 'Сжатие базы данных завершено';
  DBASE_DELETE_DATA_COMPLETE = 'Удаление данных завершено';
  DBASE_RECENT_LIMIT = 5; // Максимальное число в списке файлов БД
  // параметры статус-панели
  STATUS_BAR_NAME = 'stbMain';
var
  dbaseRecentList: array of string; // список отрытых ранее базCode language: PHP (php)

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

Принцип управления

Для того, чтобы произвести смену используемого файла базы данных SQLite, необходимо прописать новое название в файле конфигурации settings.ini и перезапустить программу.

procedure DBase_SetDBFileName( AFileName: string );
// установить новое имя файла базы данных; перезагрузить
begin
  if DB_GetDBType = DBT_SQLITE then
    IniFile_Write('Options', 'server', AFileName )
  else
    RaiseException('App_SetDBFileName - не поддерживается для типа базы DBT_MYSQL');
  // сохранить новое имя базы данных в список использованных баз
  DBase_AddToRecentList(AFileName);
  App_Restart(True, R('APP_CONFIRM_RESTART', APP_CONFIRM_RESTART), R('DBASE_CHANGE_DATABASE', DBASE_CHANGE_DATABASE))
end;Code language: Delphi (delphi)

Для работы с файлом конфигурации используются процедура IniFile_Write() из модуля system\iniFile.pas, а для перезапуска приложения – процедура App_Restart() из модуля system\App.pas, параметры которой передаются через систему управления ресурсами (модуль system\Resourses.pas), что обеспечивает локализацию сообщений. Процедура добавления имени файла в список использованных баз DBase_AddToRecentList() будет рассмотрена ниже.

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

Создание меню

procedure DBase_CreateMenu;
// создание меню для управления базой данных
//
//  База данных
//    Открыть...
//    Последние
//      <файл 1>
//      <файл 2>
//      ...
//      <файл n>
//    Сохранить как...
//    Сжать
//    ----
//    Удалить данные
var
  tmpItem: TMenuItem;
begin
  FindC(MainForm,'mniFile',tmpItem);
  tmpItem := Menu_Add('DBase','База данных',tmpItem,0,'-');
  Menu_Add('DBaseOpen','Открыть...',tmpItem,0,'DBase_OpenDBFile_OnClick');
  Menu_Add('DBaseRecent','Последние',tmpItem,1,'-');
  Menu_Add('DBaseSaveAs','Сохранить как...',tmpItem,2,'DBase_SaveDBFile_OnClick');
  Menu_Add('DBaseCompress','Сжать',tmpItem,3,'DBase_Compress_OnClick');
  Menu_Add('','-',tmpItem,4,'-');
  Menu_Add('DBaseDeleteData','Удалить данные',tmpItem,5,'DBase_DeleteData_OnClick');
  DBase_ReadRecentList; // прочитать список файлов, с которыми уже работали
  DBase_UpdateRecentList; // отобразить список в меню
end;Code language: Delphi (delphi)

Библиотечная процедура FindC() из модуля system\Utils.pas позволяет находить компоненты на заданной форме по имени, а функция Menu_Add() из модуля VClass\Menu.pas – добавлять пункты меню, используя параметры, последний из которых – имя процедуры обработки нажатия.

Список использованных ранее файлов

Список хранится в массиве dbaseRecentList, при запуске программы массив инициализируется и загружается из файла конфигурации процедурой DBase_ReadRecentList().

procedure DBase_ReadRecentList;
// прочитать список последних открываемых файлов в меню
var
  tmpIniFile: TIniFile;
  i: integer;
begin
  SetLength(dBaseRecentList,DBASE_RECENT_LIMIT);
  tmpIniFile := TIniFile.Create(Application.SettingsFile);
  for i:=0 to DBASE_RECENT_LIMIT-1 do
    dBaseRecentList[i] := tmpIniFile.ReadString('DBRecent', 'File'+IntToStr(i), '');
  tmpIniFile.Free;
end;
Code language: Delphi (delphi)

Затем в процедуре DBase_UpdateRecentList() производится отображение списка в главном меню – для каждого файла создается отдельный вложенный пункт:

procedure DBase_UpdateRecentList;
// обновить список последних открываемых файлов в меню
var
  tmpItem: TMenuItem;
  i: integer;
begin
  FindC(MainForm,T_MENU_ITEM + 'DBaseRecent',tmpItem);
  tmpItem.Clear;
  for i:=0 to DBASE_RECENT_LIMIT-1 do
    if dBaseRecentList[i] <> '' then
      Menu_Add('DBaseRecentFile'+IntToStr(i),dBaseRecentList[i],tmpItem,i,'DBase_OpenRecentFile_OnClick');
end;Code language: Delphi (delphi)

В качестве обработчика использована процедура DBase_OpenRecentFile_OnClick().

procedure DBase_OpenRecentFile_OnClick(Sender:TObject);
// открыть ранее использованный файл БД
var
  tmpItem: TMenuItem;
  tmpFileName:string;
begin
  tmpItem := TMenuItem(Sender);
  tmpFileName := ReplaceStr( tmpItem.Caption, '&','' ) ; // убрать спецсимволы, которые добавляются автоматически
  DBase_SetDBFileName(tmpFileName);
end;Code language: JavaScript (javascript)

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

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

procedure DBase_AddToRecentList( AFileName:string );
// добавить файл БД в список последних использованных
var
  tmpIniFile: TIniFile;
  i: integer;
  j: integer;
begin
  // если файл уже есть в списке, то удаляем его из списка
  for i := 0 to DBASE_RECENT_LIMIT- 2 do
  begin
    if dBaseRecentList[i] = AFileName then
    begin
      for j := i to DBASE_RECENT_LIMIT-2 do
        dBaseRecentList[j] := dBaseRecentList[j+1];
      break;
    end;
  end;
  // сдвигаем список, освобождая первую позицию
  for i := DBASE_RECENT_LIMIT-1 downto 1 do
    dBaseRecentList[i] := dBaseRecentList[i-1];
  // сохраняем название файла в первой позиции
  dBaseRecentList[0] := AFileName;
  // обновить меню
  DBase_UpdateRecentList; // это делать необязательно, так как при обычной работе пользователь перезагружает компьютер.
  // сохранить в файле настроек
  tmpIniFile := TIniFile.Create(Application.SettingsFile);
  for i:=0 to DBASE_RECENT_LIMIT-1 do
    if dBaseRecentList[i] <> '' then
      tmpIniFile.WriteString('DBRecent', 'File'+IntToStr(i), dBaseRecentList[i]);
  tmpIniFile.Free;
end;
Code language: Delphi (delphi)

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

Выбрать базу

В процедуре DBase_OpenDBFile_OnClick() выбор файла базы данных осуществляется с помощью стандартного диалога выбора файла TOpenDialog.

procedure DBase_OpenDBFile_OnClick(Sender:TObject);
// выбор файла и установка его в качестве файла БД
var
  tmpDlg: TOpenDialog;
begin
  tmpDlg := TOpenDialog.Create(nil);
  tmpDlg.Filter := 'Файлы БД (*.db)|*.db|Все файлы|*.*';
  if tmpDlg.Execute then
  begin
    DBase_SetDBFileName(tmpDlg.FileName);
  end;
  tmpDlg.Free;
end;Code language: Delphi (delphi)

Создать копию базы

Для этого используется пункт меню “Сохранить как…” и процедура DBase_SaveDBFile_OnClick(). После успешного копирования новый файл БД назначается для дальнейшего использования, которое возможно после перезагрузки программы.

procedure DBase_SaveDBFile_OnClick(Sender:TObject);
// создание копии файла БД и установка его в качестве файла БД
var
  tmpDlg : TSaveDialog;
begin
  tmpDlg := TSaveDialog.Create(frmMain);
  tmpDlg.FileName := 'sqlite.db';
  if tmpDlg.Execute then
  begin
    if CopyFile( DB_GetDBFileName, tmpDlg.FileName ) then
    begin
      DBase_SetDBFileName(tmpDlg.FileName);
    end;
  end;
  tmpDlg.Free;
end;Code language: Elixir (elixir)

Сжатие базы

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

procedure DBase_Compress_OnClick(Sender:TObject);
// сжатие пустого места в файле БД
begin
  SQLExecute('VACUUM'); // актуально только для SQLite
  ShowMessage(R('DBASE_VACUUM_COMPLETE',DBASE_VACUUM_COMPLETE));
end;Code language: Delphi (delphi)

Удаление данных

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

procedure DBase_DeleteData_OnClick(Sender:TObject);
// очистка БД от пользовательских данных
begin
  if MessageBox('Все пользовательские данные будут удалены. Продолжить?','Удалить данные?', MB_YESNO + MB_ICONWARNING) = mrYes then
  begin
    // TODO: удаление по специальному алгоритму
    // Сжатие
    SQLExecute('VACUUM'); // актуально только для SQLite
    ShowMessage(R('DBASE_DELETE_DATA_COMPLETE',DBASE_DELETE_DATA_COMPLETE));
  end;
end;Code language: Delphi (delphi)

Отображение названия файла

Название файла базы данных можно увидеть во всплывающей подсказке в окне “О программе”, но этого явно недостаточно для проекта DataKeeper, поэтому я добавил парочку процедур: DBase_UpdateStatus – для отображения названия на статус-панели и StatusBar_Init; для создания статус-панели, которая располагается на главной форме приложения в нижней её части.

procedure DBase_UpdateStatus;
// обновить название базы на панели статуса
var
  tmpSBar: TStatusBar;
begin
  FindC(MainForm,STATUS_BAR_NAME,tmpSBar,False);
  if tmpSBar <> nil then
  begin
    tmpSBar.Panels.Items[0].Text := ' '+ DB_GetDBFileName;
  end;
end;

procedure StatusBar_Init;
// создание статус-бара на главной форме
var
  tmpSBar: TStatusBar;
  tmpPanel:TStatusPanel;
begin
  tmpSBar := TStatusBar.Create(MainForm);
  with tmpSBar do
  begin
    Parent := MainForm;
    Top := Parent.Height;
    Name := STATUS_BAR_NAME;
    Align := alBottom;
    Font.Size := 11;
    Height := 26;
    tmpPanel := panels.add;
    tmpPanel.Text := '?';
  end;
end;
Code language: Delphi (delphi)

Результат

После того, как в процедуру UserApp_InitForm() были добавлены вызовы DBase_CreateMenu(), StatusBar_Init и DBase_UpdateStatus для инициализации меню и статус-панели, приложение получило новый удобный функционал управления базами данных.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *