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