标签: it | 分类: 软件_Software |
这篇文章介绍我在使用Delphi7+SQLite3过程中遇到的问题,然后以一个完整的实例说明SQLite在Delphi中的使用方法。
起因
我http://blog.chinaunix.net/u/15586/showart_356412.html看到《承影随时学英语2.0》这个软件,觉得很不错,于是下载下来看了一下,发现使用了SQLite数据库,利用的是ODBC连接方式,于是我也有了自己写一个类似程序的想法,毕竟这是别人的东西,自己写的话,用起来感觉就不一样了,说干就干。
SQLite介绍
SQLite是一个老牌的轻量级别的本地文件数据库,完全免费且开源,不需要安装,无须任何配置,当然,这样管理功能就不是很强大了,但是它的主要应用也是在本地数据库,应该说是最简单好用的嵌入式本地数据库了吧。象FireBird虽然也提供了嵌入式数据库,但是它自身附带的DLL太多,看起来没那么清爽,而SQLite只要一个DLL就可以实现全部功能。SQLite不需要数据库引擎,只有一个数据文件,占用系统资源非常少,很适合做Demo或小型应用。同时,SQLite也是关系型数据库,支持大部分SQL语句,这是它比BDB(BerkelyDB)优秀的地方,当然性能跟它比还是有差距的。它支持事务机制和blob数据类型,支持大部分SQL92标准,最大支持数据库到2T。它还有Python、Tcl、PHP、Java的绑定,这些语言可以直接使用SQLite数据库,因为它们自身包含了支持;还有ODBC接口,非常方便使用。
SQLite的一些基本操作跟SQL很类似,基本上有SQL基础的都能看明白。SQLite的图像查看工具有很多,比如SQLiteSpy、SQLiteBrowser等。我这里使用的是SQLiteSpy。
SQLite默认是utf8编码,使用pragma encoding可以看出数据库的编码。建立数据库后,可以直接输入“pragmaencoding = UTF8/UTF16”来改变编码,但数据库有了数据以后,编码是不可以修改的。
SQLite的源码可以http://www.sqlite.org获得。关于SQLite的更进一步的语法和信息,请参http://www.sqlite.com.cn/http://www.sqlitecn.org。
Delphi的SQLite组件介绍
Delphi是一个很优秀的开发工具,虽然它自身没提供对SQLite的支持,但已经有人开发出了第三方控件,非常方便。不用直接使用SQLite提供的API,只要你拖下控件,设置下属性就行了。支持SQLite的控件有两个:ASQLite和ZEOSDBO,其中ASQLite是专门为SQLite编写的,ZEOSDBO是支持很多数据库的,包括Oracle、Firebird等,类似于dbexpress,但应该比它强大。
ASQLite是一个Delphi的开源封装库,发布包中有两个子压缩包,其中ASQLite(ASQLiteD4和ASQLiteD5)是开发时的调试组件,ASQLitePkg(D4/D5)是发布时的运行组件。ZEOSDBO是基于VCL标准的数据库接口实现,可以像BDE、ADO、DBX那样使用这一组控件,它不但可以访问免费的小型的数据库,而且MSSQL、DB2、Oracle、Sybase也同样支持。支持的VCL开发工具有Delphi5-10、BCB 5/6、Kylix2/3、Lazarus等。访问任何数据库都是统一的控件,只要选择不同的Protocol就可以了,确实是非常方便和强大的。
程序的设计
有了上面的基础知识,我们就可以进行程序的设计了,首先必须明确程序的功能。软件的功能需求有:1)基本功能:背诵单词/句子;2)控制功能:播放/暂停/设置播放时间;3)设置窗体属性:置顶/透明;4)杂项:历史/退出。
有了需求后,就是怎么设计的问题了。首先是数据库的设计,打开SQLiteSpy,点击“NewDataBase”,弹出对话框后,输入你想保存的数据库名字,点保存;接着在SQLiteSpy右上的空白处输入如下代码:
CREATE TABLE English900
(
[explain] varchar(256),
word
xuhao integer
);
CREATE TABLE EnglishWord
(
xuhao integer,
[explain] varchar(256),
word
);
explain是SQLite的关键字,所以需要加上括号!之后就可以看到建立好的表了,建立了表之后,我们就需要把数据导进来了。而要把已经存在的数据导入,需要把有数据的表也打开。SQLite只有一个Main数据库,所以需要用“AttachDatabase”命令把已经存有数据的数据库附加到现在的数据库。这时候的界面如图1所示。这样就打开了两个数据库,接着是导入命令:
insert into english900(fanyi,juzi,xuhao) select * from[englishdb].english900;
insert into englishword(xuhao,[explain],word) select * from[englishdb].englishword;
就可以把englishdb里面两个表的内容导入到现在的Main数据库表中。好了,数据库部分就完成了。
继续软件的设计,我的思路是这样的。先使用ASQLite或者ZEOSDBO把数据Select出来,接着放到内存表里面,准备使用ClientDataSet,然后把ASQLite或者ZEOSDBO关掉,这样就不会一直保存着连接了。而且把数据保存在内存里,不用每次拿一个单词或者句子出来,都要跑到数据库里面去取,速度应该相对来说快一点了。
接着是显示问题,我不想按照数据库的顺序取出来,而是随机取,这样随机的算法怎么设计呢?第一个笨办法是在ClientDataSet里面动态增加一个字段,用来判断该单词/句子是否已经被读取过了,读取过一次后,就对该字段置为True。但这个办法很不好,假如随机到的单词/句子都是读取过的,那不是要判断好久,太没效率了。还好这时候老张(一个同事)给了一个办法:分段随机。第一次从1-10开始随机取一个,然后从10-20,依此类推。我觉得这个方法挺不错,就采用了。这样把数据保存在内存表后,使用定时器固定一段时间就把单词/句子显示出来。确定了方案后,就开始编码了。
程序的编码
由于ASQLite是专门对SQLite进行定制的,所以我估计它在功能等方面应该会好点吧。于是我首先选择了ASQLite做为数据库连接组件。使用的控件有“DBConn:TASQLite3DB;”、“ASQLQuery:TASQLite3Query;”、“cdsCache:TclientDataSet;”、“dspCache:TdataSetProvider;”。
1)设置DBConn
var
Dir:String;
begin
//得到程序目录
Dir := ExtractFilePath(Application.ExeName);
//设置查找目录,否则会出现找不到db和dll错误
DBConn.DefaultDir := Dir;
//加入数据库不存在,则自己创建
DBConn.Database
DBConn.DriverDLL := 'DLL\SQLite3.dll';
DBConn.Connected := True;
end;
以上动态修改DBConn的属性,假如直接给它的Database赋值,那就定死在那个文件夹了。这里要注意,由于有DefaultDir属性,所以设置了之后,在Database等属性中,就不用加上Dir了,默认就是在这个路径寻找的。
2)设置ASQLQuery
Connection属性设置为 DBConn。
3)设置dspCache
DataSet属性设置为ASQLQuery;。
4)设置cdsCache
ProviderName设置为dspCache;。
这样,组件就设置完成了,接下来是按钮事件。背诵单词按钮事件处理如下。
if (Sender as TMenuItem).Checked then
Exit
else
begin
(Sender as TMenuItem).Checked := True;
pm_control_sentence.Checked := False;
with ASQLQuery do
begin
if Active then
Close;
SQL.Clear;
SQL.Text := 'select * from englishword';
Open;
end;
cdsCache.Data := dspCache.Data;
ASQLQuery.Close;
在把数据保存到ClientDataSet后,就把ASQLQuery关闭。背诵句子的事件和这个类似,就不贴出来了。
以上编译好后,在测试过程中出现问题了。问题出在这一句:“cdsCache.Data :=dspCache.Data”。当我来回切换背诵单词和背诵句子按钮时,会出现一个错误,而且错误是定位在ASQLite的GetFieldData里面,莫非控件自身有bug?后来我又测试了一些方法,不使用clientdataset和datasetprovider,直接从ASQLiteQuery里面把数据取出来,还是发现有问题,在尝试几次获取数据之后,就会出现这个错误。看来控件出问题的可能性很大了,不过里面的源码我也不是很看得明白,所以没办法修复,只好转向了ZEOSDBO,希望能找到转机吧。
于是去sourceforge把ZEOSDBO控件下载下来,把ASQLQuery和DBConn删除掉,把ZEOSDBO的Zconnection和Zquery放上去,cdsCahe无须修改,把dspCache的Dataset设置为Zquery就可以了。然后是设置Zconnection和Zquery,具体如下。
1)Zconnection
Dataabase属性设置为:DB\myEnglishDB.db,这个是数据库文件所在的地方。
Protocol属性设置为:sqlite-3,这样就支持SQLite3的数据库了。
2)Zquery
只需设置Connection的属性指向ZConnection控件就可以了。
背诵单词的代码如下。
if (Sender as TMenuItem).Checked then
Exit
else
begin
(Sender as TMenuItem).Checked := True;
pm_control_sentence.Checked := False;
with ZROQ do
begin
if Active then
Close;
SQL.Clear;
SQL.Text := 'select * from englishword';
Open;
end;
cdsCache.Data := dspCache.Data;
//本来想给它动态增加一个字段,然后根据这个字段判断是否已经显示过了,但感觉这样有个问题,假如随机产生的数一直都是显示过的,这样判断起来的话,就非常不爽,而且感觉这种方法太笨了,穷举……
//cdsCache.FieldDefs.Add('IsShow',ftInteger);
ZROQ.Close; //保存到内存表后,把ZQuery关闭,这样就不会占用一个连接了。
tmShow.Enabled := True; //设置定时器启动,每隔一段时间就可以把数据显示在界面上了。
执行select语句后,却发现读出来的数据是乱码,但不会出现和ASQLite类似的问题了。这至少说明了ASQLite应该是有问题的,看来接下来就是要解决编码问题了。在网上搜索了一些资料,没发现类似的解决方法。于是我开始怀疑是数据库编码问题,因为默认是utf8的,莫非ZEOSDBO没有解码,所以读取出来后变成了乱码?当我想改变数据库编码时,却发现改变不了。这时候发现了猛禽的一篇使用ZEOSDBO的文章,虽然他没说到这个问题,但他使用过这个控件,应该了解吧?于是我就发了封邮件给他,原来是Delphi的VCL不支持utf8编码,显示出来的时候要进行转换,真是笨,这都没想到。解决方法就是在显示的时候使用函数UTF8ToANSI转换一下。显示是写在Timer定时器里面的,代码如下。
//下面定时把数据读取出来,由于数据是保存在内存中,所以可以直接存取
procedure TfrmMain.tmShowTimer(Sender: TObject);
//将编译器设置$J+来支持形式常量,这就相当于C语言的static变量了
{$J+}
const i:integer = 0;
{$J-}
begin
Randomize;
cdsCache.Filtered := False;
cdsCache.Filter :=Format('xuhao=%s',[QuotedStr(IntToStr(Random(10)+10*i))]);
cdsCache.Filtered := True;
if cdsCache.RecordCount > 0 then
begin
Inc(i);
lblExplain.Caption :=UTF8ToANSI(cdsCache.FieldValues['explain']);
//就是这里了,把中文进行转换
lblWord.Caption := cdsCache.FieldValues['word'];
end
else
i := 0;
end;
主要问题解决了,其他就好办了。窗体置顶使用SetLayeredWindowAttribut
要使使窗体拥有透明效果,首先要有WS_EX_LAYERED扩展属性(旧的SDK没有定义这个属性,可以直接指定为0x80000),设置“WS_EX_LAYERED= 0x80000”,至于具体的实现,大家就看随文附带的代码吧。
由于窗体是无标题的,所以还需要设置WM_NCHitTest消息,这样才能拖动它。另外,还需要用ShellExecute打开连接,美化界面等,这些都很简单,大家可以自己看代码了。
这样,这个初步尝试SQLite就成功了。SQLite还是非常强大的,大家可以在此基础上自由发挥它的功能。
联系客服