11.嵌入式数据库管理工具开发案例(1)
11.1开发背景
随着技术的发展,Web应用不断普及的今天,小程序、手机 APP 等应用也在不断繁荣,在大众面前呈现出各式各样的客户端应用。在客户端应用中,很多时候我们需要在客户端记录一些数据,这时就会想到使用嵌入式数据库来保存或者处理这些数据了,如微信、QQ等都使用了 SQLite 数据库来处理客户端的用户数据。而我们一般情况下的软件开发流程是首先按照产品经理设计的原型来进行数据库设计,然后在数据库上创建表、视图、存储过程等。这样就需要对嵌入式数据库进行初始化,如果没有专门的工具操作嵌入式数据库,我们就只能借助于关系型数据库管理工具来完成这些操作,为了方便程序员操作独立的嵌入式数据库,开发一款嵌入式数据库管理系统还是有必要的。具体需求如下:
- 支持 SQLite、FireBird 两种数据库
- 可以创建数据库
- 可以在数据库上执行 SQL 语句
- 可以将执行的 SQL 语句保存为文件
- 可以打开文件系统中的 SQL 文件
- 可以记录在数据库上执行过的操作
界面设计如下图所示:
11.2 Lazarus 资源文件
对于嵌入式数据库 SQLite 来说,连接一个不存在的数据库即可创建新的数据库;而对于 FireBird 数据库来说,无法通过连接一个不存在的数据库来创建新的数据库,一般情况下,我们可以通过 isql 工具执行 create database 语句来创建数据库。
鉴于此,在我们的嵌入式数据库管理工具中实现创建数据库的方法就不能采用数据库本身提供的方法来创建新的数据库。那么,还有什么方法呢?这时候,我们自然会想到可以复制一个空数据库作为新的数据库,也就是说,我们事先分别准备一个空数据库,根据用户的需要在创建数据库的时候,直接复制即可。但这样又存在一个问题就是系统必须在发布版本中带上空数据库,而这个空数据库很容易被用户连接上以后进行各种操作,也就是说,在用户复制的时候可能已经不是空数据库了,怎么办呢?显然还是有办法的,我们可以将空数据库文件打包到 Lazarus 资源文件中,需要的复制的时候从资源文件中加载到文件系统即可。
所以,本节内容我们主要介绍 Lazarus 的资源文件的使用,然后在此基础上实现嵌入式数据库管理工具的创建数据库。
资源文件包含应编译为可执行文件的数据。这些数据可以由图像、字符串表、版本信息等组成。甚至是Windows 清单和表单。这包括程序员可以在代码中检索的数据(作为文件访问)。如果您想分发自包含的可执行文件,那么使用资源会很方便。
11.3 Lazarus 资源文件的使用
11.3.1向程序中添加资源
如果我们需要在可执行文件中存储一些数据的情况,以及在程序运行期间要从中提取这些数据。那么我们可以向程序中添加资源。
首先,我们需要告诉编译器要在资源中包含哪些文件。我们可以在一个.rc(资源脚本)文件中说明资源所包含的文件。其格式为:
<资源名称> <资源类型> <数据文件名>
如:
MYDATA RCDATA "mydata.dat"
指示编译器将资源包含到项目中,格式:
{$R <rc文件名>}
如:
program mydata;
{$R mydata.rc}
begin
end.
11.3.2在程序中使用资源文件
现在,我们将存储的资源提取到一个文件中。如:
program mydata;
uses
SysUtils, Classes, LCLType;
{$R mydata.res}
var
S: TResourceStream;
F: TFileStream;
begin
// create a resource stream which points to our resource
S := TResourceStream.Create(HInstance, \'MYDATA\', RT_RCDATA);
// Please ensure you write the enclosing apostrophes around MYDATA,
// otherwise no data will be extracted.
try
// create a file mydata.dat in the application directory
F := TFileStream.Create(ExtractFilePath(ParamStr(0)) + \'mydata.dat\', fmCreate);
try
F.CopyFrom(S, S.Size); // copy data from the resource stream to file stream
finally
F.Free; // destroy the file stream
end;
finally
S.Free; // destroy the resource stream
end;
end.
也支持直接从FPC资源加载,例如:
procedure exampleproc;
var
Image: TImage
begin
Image := TImage.Create;
Image.Picture.LoadFromResourceName(HInstance,\'image\'); // note that there is no need for the extension
end;
11.4 Lazarus 资源文件使用示例
本例通过资源文件加载一张图像显示到窗体上,点击一个按钮后将数据文件提取到文件系统中。
创建一个应用,拖放一个 TImage 和 TButton
准备一种图像文件 980.jpeg 和一个可执行程序 hello.exe
新建一个文本文件并保存为 myres.rc,内容如下:
I980 RCDATA "980.jpeg"
HELLO RCDATA "hello.exe"
在应用程序中添加编译器指令:
{$R myres.rc}
在应用程序中引入如下单元:
uses LCLType;
编写窗体的 OnCreate 事件:
procedure TForm1.FormCreate(Sender: TObject);
begin
Image1.Picture.LoadFromResourceName(HInstance, \'I980\');
end;
编写按钮的单击事件:
procedure TForm1.Button1Click(Sender: TObject);
var
S: TResourceStream;
F: TFileStream;
begin
// 创建指向资源的资源流
S := TResourceStream.Create(HInstance, \'HELLO\', RT_RCDATA);
try
// 创建一个文件
F := TFileStream.Create(\'extract.exe\', fmCreate);
try
F.CopyFrom(S, S.Size); // 将资源流中的数据复制到文件
finally
F.Free; // 释放文件流
end;
finally
S.Free; // 释放资源流
end;
end;
完整的示例代码如下:
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls;
type
{ TForm1 }
TForm1 = class(TForm)
Button1: TButton;
Image1: TImage;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
public
end;
var
Form1: TForm1;
implementation
{$R *.lfm}
{$R myres.rc} // 编译资源文件指令
{ TForm1 }
uses LCLType; // RT_RCDATA 所在单元
procedure TForm1.FormCreate(Sender: TObject);
begin
Image1.Picture.LoadFromResourceName(HInstance, \'I980\');
end;
procedure TForm1.Button1Click(Sender: TObject);
var
S: TResourceStream;
F: TFileStream;
begin
// 创建指向资源的资源流
S := TResourceStream.Create(HInstance, \'HELLO\', RT_RCDATA);
try
// 创建一个文件
F := TFileStream.Create(\'extract.exe\', fmCreate);
try
F.CopyFrom(S, S.Size); // 将资源流中的数据复制到文件
finally
F.Free; // 释放文件流
end;
finally
S.Free; // 释放资源流
end;
end;
end.
11.5 创建数据库
在我们设计的主窗体中(11.1中),当单击“创建”按钮时,打开创建数据库对话框,该对话框窗体设计如下图:
窗体中组件的主要属性是数据库选项,包括:sqlite-3 和 firebird-2.5 两项。
窗体的声明代码:
TCreatedbForm = class(TForm)
GroupBox1: TGroupBox;
GroupBox2: TGroupBox;
GroupBox3: TGroupBox;
CancelButton: TButton;
CreateButton: TButton;
SelFileDirButton: TButton;
FileNameEdit: TEdit;
DBComboBox: TComboBox;
FileDirEdit: TEdit;
DBFileSelectDirectoryDialog: TSelectDirectoryDialog;
procedure CancelButtonClick(Sender: TObject);
procedure CreateButtonClick(Sender: TObject);
procedure FormActivate(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure SelFileDirButtonClick(Sender: TObject);
private
public
end;
编写窗体的 OnCreate 事件:
procedure TCreatedbForm.FormCreate(Sender: TObject);
begin
// 组件初始化
FileNameEdit.Text:=\'\';
DBComboBox.ItemIndex:=0;
FileDirEdit.Text:=\'\';
end;
选择文件位置按钮的单击事件:
procedure TCreatedbForm.SelFileDirButtonClick(Sender: TObject);
begin
// 选择数据库文件位置
DBFileSelectDirectoryDialog.FileName:=\'\';
if DBFileSelectDirectoryDialog.Execute then
FileDirEdit.Text:=DBFileSelectDirectoryDialog.FileName;
end;
我们事先准备好空数据库文件:emtdb.db(SQLite3数据库)和EMTDB.FDB(FireBird2.5数据库)
新建 db.rc 文件,内容如下:
SQLite3_EMTDB RCDATA "emtdb.db"
FireBird2.5_EMTDB RCDATA "EMTDB.FDB"
在代码中添加:
{$R db.rc}
在代码引入单元:
uses LCLType;
声明提取资源文件函数:
private
function ExtractResourceFile(ResName: String; FileName: String): Boolean;
编写提取资源文件函数代码:
function TCreatedbForm.ExtractResourceFile(ResName: String; FileName: String): Boolean;
var
S: TResourceStream;
F: TFileStream;
begin
Result:=False;
// 创建指向资源的资源流
S := TResourceStream.Create(HInstance, ResName, RT_RCDATA);
try
// 创建一个文件
F := TFileStream.Create(FileName, fmCreate);
try
if F.CopyFrom(S, S.Size) = S.Size then Result:=True; // 将资源流中的数据复制到文件
finally
F.Free; // 释放文件流
end;
finally
S.Free; // 释放资源流
end;
end;
编写创建按钮的单击事件:
procedure TCreatedbForm.CreateButtonClick(Sender: TObject);
var
ResourceName: String;
TargetFile: String;
begin
// 创建
if DBComboBox.ItemIndex < 0 then
begin
MessageDlg(\'提示\', \'请选择数据库!\', mtInformation, [mbOK], 0);
Exit;
end;
if FileDirEdit.Text = \'\' then
begin
MessageDlg(\'提示\', \'请设置数据库文件位置!\', mtInformation, [mbOK], 0);
Exit;
end;
if FileNameEdit.Text = \'\' then
begin
MessageDlg(\'提示\', \'请设置数据库文件名!\', mtInformation, [mbOK], 0);
Exit;
end;
if not DirectoryExists(FileDirEdit.Text) then
begin
MessageDlg(\'提示\', \'您设置的数据库文件位置不存在,请重新设置!\', mtInformation, [mbOK], 0);
FileDirEdit.Text := \'\';
Exit;
end;
TargetFile := FileDirEdit.Text + \'/\' + FileNameEdit.Text;
if DBComboBox.Text = \'sqlite-3\' then
begin
ResourceName:=\'SQLite3_EMTDB\';
if ExtractFileExt(TargetFile) = \'\' then TargetFile := TargetFile + \'.db\'
end;
if DBComboBox.Text = \'firebird-2.5\' then
begin
ResourceName:=\'FireBird2.5_EMTDB\';
if ExtractFileExt(TargetFile) = \'\' then TargetFile := TargetFile + \'.fdb\'
end;
if FileExists(TargetFile) then
if MessageDlg(\'文件已存在,覆盖吗?\', mtConfirmation, [mbYes, mbNo], 0) = mrNo then Exit;
if ExtractResourceFile(ResourceName, TargetFile) then
begin
MessageDlg(\'提示\', \'数据库创建成功!\', mtInformation, [mbOK], 0);
Close;
end
else
MessageDlg(\'提示\', \'数据库创建失败!\', mtInformation, [mbOK], 0);
end;
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请通知我们,一经查实,本站将立刻删除。