第4章 感受(二)——4.7. Hello Database GUI版
2009-06-08 21:37
357 查看
[回到目录]
白话C++
4.7. Hello Database GUI版
类似“Hello GUI” 章中的4.1.3小节 “向导-wxWidgets project”,创建一个空白的wxWidgets 对话框界面的项目,并命名为“HelloDatabase_GUI”。
4.7.1. 配置构建选项
类似前一章的“Hello Database 控制台版”,请为新项目“HelloDatabase_GUI”配置构建选项,为其加上:
链接库:“mysqlpp”
搜索路径(编译器):${#mysqlpp.include}、${#mysql.include}
搜索路径(链接器):${mysqlpp.lib}
“向导-wxWidgets project”为项目预备配置了一些内容,因此在配置以上内容时请注意,不要删除向导所产生的各项配置。
点击Code::Blocks主菜单:“文件→保存项目”,保存以上配置。
4.7.2. 界面设计
请参看“Hello GUI 布局篇(2)”小节,完成窗口界面设计。然后再根据下面两点,进行修改:
第一、
编辑框“TextCtrlPassword”,其内容为“1234”,请将它替换为您在创建MySQL数据库时,root用户的实际密码。请参看第2章《准备》中的“安装与配置MySQL”小节,或前一小节。
第二、
在最后一步,请设置对话框的标题为“Hello Database GUI”。
第三、
同样在最后一步,请去除对话框“风格”属性中的“wxRESIZE_BORDER”子选项。(wxRESIZE_BORDER属性允许用户手工改变窗口大小,这有利于当时我们检查窗口布局设计是否正确。本例中,对话窗口将由程序直接改变大小)。
4.7.3. 编写代码
wxWidgets
项目向导在生成图形界面的同时,也为我们产生了该图形界面对应的源代码。其中包含了对话框的类定义与实现。
本项目名为“HelloDatabase_GUI”,则所生成的对话框类名为:“HelloDatabase_GUIDialog”;头文件名
为:“HelloDatabase_GUIMain.h”,实现文件为:“HelloDatabase_GUIMain.cpp”。
我们将代码分成三部分:
第一、
用于定义“HelloDatabase_GUIDialog”类的代码,位于“HelloDatabase_GUIMain.h”头文件。 为了方便,我们将冠军记录等数据定义,也定义在该文件中。
第二、
用于实现“HelloDatabase_GUIDialog”类的代码,位于“HelloDatabase_GUIMain.cpp”源文件中。
第三、
事件代码,可以通过wxSmith可视工具生成事件函数的声明(位于头文件中)及事件函数的基本框架(位于源文件中),还有部分代码将会保存在相关的资源文件中。
4.7.3.1. 头文件
在“Management”边栏内切换到“项目”页,然后双击打开“Headers”节点下的“HelloDatabase_GUIMain.h”文件。
图 4-67 打开GUIMain头文件
头文件包含
在该头文件插以下013、014两行代码:
font face='Courier New'>
定义获奖记录数据结构
在026~036行,插以入Record数据类型定义,Record用来表达一条金牌获奖记录。
添加类成员
接下来是对话框“HelloDatabase_GUIDialog”的类定义,我们首先添加一些私有的成员函数及成员数据,代码片段如下,我们要添加的代码,位于048~064行。
4.7.3.2. 源文件
在013行,插入对mysql++头文件的包含,随后加入wxWidgets中和内存流、图形相关的三个头文件的包含
编码转换函数
数据库里的数据采用GB2312编码,当它们要显示在wxWidgets写的窗口界面时,需要转换为Unicode。
请在源代码文件169行处,加入以下两个独立函数
std::string是C++标准库提供的字符串类;而wxString则是wxWidgets使用的字符串类。因为在《准备》章节时,我们编译的wxWidgets库统一配置为Unicode版本,所以wxString默认存储的是Unicode编码的字符。
〖小提示〗:wxString与std::string的互换
为了方便转换std::string和wxString,wxWidgets提供了中间类:wxCharBuffer。当std::string要转换成wxString时,过程是:
std::string → wxCharBuffer → wxString
反过来的方向就是:
std::string ← wxCharBuffer ← wxString
FromGB2312
函数将一个含有gb2312字符,非Unicode编码的wxCharBuffer,转换成wxString(Unicode
编码)。ToGB2312函数则将一个wxString(Unicode
编码)转换为一个含有gb2312字符,非Unicode编码的wxCharBuffer。
连接数据库函数
在“Hello Database 控件台版”,我们已经写过连接MySQL数据库的函数,这里的代码大同小异。我们写成一个独立函数,接在前面两个函数之后,约183行处:
函数如何连接数据库失败(比如密码错误),将通过wxMessageBox函数弹出一个提示框,并且返回的是一个失败的连接,调用者可以通过其connected()函数判断是已经连接。
成员函数(查询、显示等功能)
成员函数ConnectAndGetRecords()先是调用前面所写的ConnectDatabase()函数,入参(密码)来自编辑框TextCtrlPassword。如果连接失败,函数在209行直接返回。
可能用户会重复连接数据,所以212行清除list中的数据,就显得有必要了。
和控制台版查到数据后,直接显示到控制台不同,本例中我们需要将查询所得的数据,全部转存到list<Record>中(或许你终于想起“成绩管理系统”中的list<Score>)。
219
行的“res =
query.store()”,相当于查询所得的记录,全部存在res对象中,因此此时“res”就是一张“表”;而“res[i]”则是表中第i条的记
录;至于“res[i]["abs_index"]”,就是第i条记录中,字段名为“abs_index”的值。
221行开始循环res这张“表”的所有记录,然后将它转换成一个Record对象,最后再将这个Record对象,push_back到list中。
247行,我们将当前位置(一个迭代器),指向list的end(),表示当前记录是哪条,尚未决定。
〖小提示〗:为什么有那么多种“字符串”类型?
眼尖的你,可能在上述代码中,看到三种字符串类型:std::string、wxString、mysqlpp::String。或许这会让你产生一个疑惑:同样用来表达字符串,为什么标准库及各扩展库都要各自实现一遍呢?
这当中有历史原因:比如在c++标准库成熟之前,就有了wxWidgets这个GUI库。也有特定需求上原因。 mysql::String就希望提供一些额外的,方便处理数据表字段的操作,比如和日期的互换,和数值的算术操作等。
各
自为政、重复建设的现象,不仅在字符串类中存在,对于那些标准库暂未定义的类,比如“线程类/thread”,类似现象更为严重,不管如何,这确实带来了
C++使用者的不便;不过,C++下一代标准即将到来,越来越多的常用类进入了C++的标准库,随着时间的推展,我们有理由相信,会有越来的越多扩展库,
将更多地直接或间接(比如派生)地使用标准库的已经实现的类。
成员函数ShowCurrentRecord()用来显示当前记录的数据。当前记录是哪一条?这由current_成员数据决定。253行,程序判断当前记录是否为空,如果是,则直接返回.
259
行定义一个wxString临时变量:tmp。wxString有一个非常类似std::stringstream的功能:即通过 <<
操作符来拼接成一个字符串,比如260行便可以拼接出类似“第1天,第1金”这样的内容。262行,将拼接后的内容,显示到窗口界面上的
“StaticTextLine1”控件上。在每次要重新拼接内容时,tmp需要通过调用clear()函数来清除原有内容(否则内容会越拼越长)。
显示完记录的基本信息(共5个)之后,程序在282行调用了另一个成员函数:ShowCurrentMemoAndPhoto(),该函数用来查询、显示当前记录的备注及图片数据。
284行代码,用以让窗口重新调整大小(以保证可以显示当前记录的图片)。285行,则可以让窗口自动在屏幕上居中显示。
记录的基本数据是一次查回全部记录,然后保存在list中;与引不同,记录的备注和图片数据是在每次需要显示当前记录数据时,再从数据库中查询获得。
〖小提示〗:考虑数据压力
许多数据库应用,在数据表中往往存储了成千上万,甚至数十万、百万、千万条的记录。如果一句简单的 SELECT * FROM xxx,往往会让这些数据先是拥挤地在网络上穿行(假设数据库在另一台主机上),然后再挤爆了本机的内存。
2008年北京奥运会,我们当然是获得金牌最多的国家!从这个意义上讲,我们的金牌数很多很多。不过如果从数据库的存储量来看,51条记录那可一点不多。因此,本例如果一次将所有备注与图片数据都查回,并且同样保存在内存中,对于流行的个人电脑配置,并不是很大的压力。
本例特意示例一种常用的数据处理技巧:延迟大数量字段的查询。想像如果有1万条记录,那么查询一万条记录的基本信息可以接受的,但如果此时还带有备注和图片,就会让程序占用太多内存了。
ShowCurrentMemoAndPhoto()
函数在296行重新建立一个数据库连接。然后在305行建立一个SQL查询语句,专门用于查询备注和图片数据。注意SQL语句中通过 WHERE
子句限定了查询图片和备注属于哪一条记录(也就是说,每次肯定只会查回一条记录的备注和图片数据)。
既然只会有一条(或零条)记录被查询,所以本函数的后续代码,你也就没有看到for语句。相反,311行通过if语句判断记录数是否大于0(更直观的写法,或许是判断其是否等于1)。
后续还有一些代码用于显示图片,暂不在本节详解。
成员函数(跳转当前记录)
和控制台版只能按次序一一输出每条记录不同,GUI版允许用户来回切换记录显示。这就需要我们实现用于跳转当前记录的函数
4.7.3.3. 事件代码
现在,我们已经完成大多数功能的内部实现。但还没有将这些代码和窗口界面上的控件绑定起来。对于本例,需要处理控件都是按钮。按钮存在一个典型事件:那就是用户通过鼠标或键盘“按”下这个按钮,这称为按钮的点击事件(OnClick)。
请在“Management”边栏内切换到“资源文件”页,鼠标双击“HelloDatabase_GUIDialog”节点,用以打开“HelloDatabase_GUIdialog.wxs”设计界面。
绑定“获取”按钮事件
双击界面上的 “获取”按钮,IDE将打开“HelloDatabase_GUIMain.cpp”文件。然后滚动到该文件底部,可以发现,新增了一个成员函数的空架子
〖危险〗:事件响应函数自动生成
此过程中,wxSmith在头文件中添加了该事件函数的声明,通常这个函数不应被手工删除。另外,wxSmith在CPP源文件中生成该事件函数的空定义,且记这些代码不是手工录入的,读者如果粗心忽略这一点,就会碰到程序编译之后,事件响应似乎“不灵光”的问题。
我们将为该空函数添加代码,及函数注释
392和393是我们添加的代码。作用是连接数据库,取得数据,并且自动切找到第一条。
绑定“|<”、“<”、“>”、“>|”按钮事件
这四个按钮分别需要实现“跳转到第一条/First”、“跳转到前一条/Prior”、“跳转到下一条/Next”及“跳转到最后一条/Last”的功能。
双击其中一个按钮,即可在源文件“HelloDatabase_GUIMain.cpp”内找到生成的空成员函数。下面是四个函数的实现代码:
4.7.4. 编译、运行
按下Ctrl + F9编译,再按下F9运行本程序。下面运行期的一个程序截图:
图 4-68 Hello Database GUI 版 运行界面
[回到目录]
白话C++
4.7. Hello Database GUI版
类似“Hello GUI” 章中的4.1.3小节 “向导-wxWidgets project”,创建一个空白的wxWidgets 对话框界面的项目,并命名为“HelloDatabase_GUI”。
4.7.1. 配置构建选项
类似前一章的“Hello Database 控制台版”,请为新项目“HelloDatabase_GUI”配置构建选项,为其加上:
链接库:“mysqlpp”
搜索路径(编译器):${#mysqlpp.include}、${#mysql.include}
搜索路径(链接器):${mysqlpp.lib}
“向导-wxWidgets project”为项目预备配置了一些内容,因此在配置以上内容时请注意,不要删除向导所产生的各项配置。
点击Code::Blocks主菜单:“文件→保存项目”,保存以上配置。
4.7.2. 界面设计
请参看“Hello GUI 布局篇(2)”小节,完成窗口界面设计。然后再根据下面两点,进行修改:
第一、
编辑框“TextCtrlPassword”,其内容为“1234”,请将它替换为您在创建MySQL数据库时,root用户的实际密码。请参看第2章《准备》中的“安装与配置MySQL”小节,或前一小节。
第二、
在最后一步,请设置对话框的标题为“Hello Database GUI”。
第三、
同样在最后一步,请去除对话框“风格”属性中的“wxRESIZE_BORDER”子选项。(wxRESIZE_BORDER属性允许用户手工改变窗口大小,这有利于当时我们检查窗口布局设计是否正确。本例中,对话窗口将由程序直接改变大小)。
4.7.3. 编写代码
wxWidgets
项目向导在生成图形界面的同时,也为我们产生了该图形界面对应的源代码。其中包含了对话框的类定义与实现。
本项目名为“HelloDatabase_GUI”,则所生成的对话框类名为:“HelloDatabase_GUIDialog”;头文件名
为:“HelloDatabase_GUIMain.h”,实现文件为:“HelloDatabase_GUIMain.cpp”。
我们将代码分成三部分:
第一、
用于定义“HelloDatabase_GUIDialog”类的代码,位于“HelloDatabase_GUIMain.h”头文件。 为了方便,我们将冠军记录等数据定义,也定义在该文件中。
第二、
用于实现“HelloDatabase_GUIDialog”类的代码,位于“HelloDatabase_GUIMain.cpp”源文件中。
第三、
事件代码,可以通过wxSmith可视工具生成事件函数的声明(位于头文件中)及事件函数的基本框架(位于源文件中),还有部分代码将会保存在相关的资源文件中。
4.7.3.1. 头文件
在“Management”边栏内切换到“项目”页,然后双击打开“Headers”节点下的“HelloDatabase_GUIMain.h”文件。
图 4-67 打开GUIMain头文件
头文件包含
在该头文件插以下013、014两行代码:
font face='Courier New'>
013 #include < list> 014 #include < string> //(*Headers(HelloDatabase_GUIDialog)
定义获奖记录数据结构
在026~036行,插以入Record数据类型定义,Record用来表达一条金牌获奖记录。
026 //定义一个结构,用于存储 //一条冠军获得者记录 struct Record { int index; //第几块 int day_index; //第几天获得 std:: string name; //姓名 std:: string province; //省份 bool sex; //性别:false-女,true-男 std:: string item_name; //项目名称 std:: string score; //成绩 };
添加类成员
接下来是对话框“HelloDatabase_GUIDialog”的类定义,我们首先添加一些私有的成员函数及成员数据,代码片段如下,我们要添加的代码,位于048~064行。
040 class HelloDatabase_GUIDialog: public wxDialog { public : HelloDatabase_GUIDialog( wxWindow* parent, wxWindowID id = - 1 ); virtual ~ HelloDatabase_GUIDialog(); ////////////插入以下代码//////// private : void First(); //转到第一条记录 void Prior(); //转到上一条记录 void Next(); //转到下一条记录 void Last(); //转到最后一条记录 //Record 列表,存储全部记录 //但不保存每条记录的备注与图片数据 std:: list< Record> records_; //迭代器,指向当前正在显示的记录 std:: list< Record>:: const_iterator current_; //连接数据库,并且获取全部记录 void ConnectAndGetRecords(); //将当前记录显示到界面 void ShowCurrentRecord(); //将当前记录的备注与图片数据显示到界面 void ShowCurrentMemoAndPhoto(); ////////////插入代码到此结束//////////// private : //(*Handlers(HelloDatabase_GUIDialog)
4.7.3.2. 源文件
在013行,插入对mysql++头文件的包含,随后加入wxWidgets中和内存流、图形相关的三个头文件的包含
013 #include < mysql++. h> #include <wx/mstream.h> #include <wx/image.h> #include <wx/bitmap.h>
编码转换函数
数据库里的数据采用GB2312编码,当它们要显示在wxWidgets写的窗口界面时,需要转换为Unicode。
请在源代码文件169行处,加入以下两个独立函数
169 //gb2312 => UNICODE (wxString) wxString FromGB2312( wxCharBuffer const & buf) { return wxString( buf , wxCSConv( wxT( "gb2312" )) , strlen( buf)); } //UNICODE (wxString) => GB2312 wxCharBuffer ToGB2312( wxString const & str) { return str. mb_str( wxCSConv( wxT( "gb2312" ))); }
std::string是C++标准库提供的字符串类;而wxString则是wxWidgets使用的字符串类。因为在《准备》章节时,我们编译的wxWidgets库统一配置为Unicode版本,所以wxString默认存储的是Unicode编码的字符。
〖小提示〗:wxString与std::string的互换
为了方便转换std::string和wxString,wxWidgets提供了中间类:wxCharBuffer。当std::string要转换成wxString时,过程是:
std::string → wxCharBuffer → wxString
反过来的方向就是:
std::string ← wxCharBuffer ← wxString
FromGB2312
函数将一个含有gb2312字符,非Unicode编码的wxCharBuffer,转换成wxString(Unicode
编码)。ToGB2312函数则将一个wxString(Unicode
编码)转换为一个含有gb2312字符,非Unicode编码的wxCharBuffer。
连接数据库函数
在“Hello Database 控件台版”,我们已经写过连接MySQL数据库的函数,这里的代码大同小异。我们写成一个独立函数,接在前面两个函数之后,约183行处:
183 //连接MYSQL数据库的函数 //入参:密码 //出参:一个MySQL数据库连接 mysqlpp:: Connection ConnectDatabase( wxString const & pwd) { mysqlpp:: Connection con( false ); mysqlpp:: SetCharsetNameOption * chareset = new mysqlpp:: SetCharsetNameOption( "gbk" ); con. set_option( chareset); if (! con. connect( "d2school" , "localhost" , "root" , ToGB2312( pwd))) { wxMessageBox( _T( "无法连接上数据,请检查密码是否正确!" ), _T( "出错消息" )); return con; } return con; }
函数如何连接数据库失败(比如密码错误),将通过wxMessageBox函数弹出一个提示框,并且返回的是一个失败的连接,调用者可以通过其connected()函数判断是已经连接。
成员函数(查询、显示等功能)
202 //连接数据库,成功的话获得各条记录的基本数据 void HelloDatabase_GUIDialog:: ConnectAndGetRecords() { mysqlpp:: Connection con = ConnectDatabase( TextCtrlPassword-> GetValue()); if (! con. connected()) { 209 return ; } 212 records_. clear(); //清除原有记录 //构建查询全部记录的SQL语句,仅查询基本数据(不含备注和图片) mysqlpp:: Query query = con. query( "SELECT abs_index, day_index, name " ", province, sex, item, score" " FROM champions_2008" ); 219 if ( mysqlpp:: StoreQueryResult res = query. store()) { 221 for ( size_t i = 0 ; i < res. num_rows(); ++ i) { Record rec; //创建一条记录数据 //获取记录数据 226 rec. index = ( int ) res[ i][ "abs_index" ]; //次序 rec. day_index = ( int ) res[ i][ "day_index" ]; //第几天 rec. name = res[ i][ "name" ]. c_str(); //姓名 rec. province = res[ i][ "province" ]. c_str(); //省份 rec. sex = ( bool ) res[ i][ "sex" ]; //性别 rec. item_name = res[ i][ "item" ]. c_str(); //项目 mysqlpp:: String score = res[ i][ "score" ]; //成绩 if ( score. is_null()) { rec. score = "N/A" ; } else { rec. score = score. c_str(); //成绩 } records_. push_back( rec); } } 247 current_ = records_. end(); }
成员函数ConnectAndGetRecords()先是调用前面所写的ConnectDatabase()函数,入参(密码)来自编辑框TextCtrlPassword。如果连接失败,函数在209行直接返回。
可能用户会重复连接数据,所以212行清除list中的数据,就显得有必要了。
和控制台版查到数据后,直接显示到控制台不同,本例中我们需要将查询所得的数据,全部转存到list<Record>中(或许你终于想起“成绩管理系统”中的list<Score>)。
219
行的“res =
query.store()”,相当于查询所得的记录,全部存在res对象中,因此此时“res”就是一张“表”;而“res[i]”则是表中第i条的记
录;至于“res[i]["abs_index"]”,就是第i条记录中,字段名为“abs_index”的值。
221行开始循环res这张“表”的所有记录,然后将它转换成一个Record对象,最后再将这个Record对象,push_back到list中。
247行,我们将当前位置(一个迭代器),指向list的end(),表示当前记录是哪条,尚未决定。
〖小提示〗:为什么有那么多种“字符串”类型?
眼尖的你,可能在上述代码中,看到三种字符串类型:std::string、wxString、mysqlpp::String。或许这会让你产生一个疑惑:同样用来表达字符串,为什么标准库及各扩展库都要各自实现一遍呢?
这当中有历史原因:比如在c++标准库成熟之前,就有了wxWidgets这个GUI库。也有特定需求上原因。 mysql::String就希望提供一些额外的,方便处理数据表字段的操作,比如和日期的互换,和数值的算术操作等。
各
自为政、重复建设的现象,不仅在字符串类中存在,对于那些标准库暂未定义的类,比如“线程类/thread”,类似现象更为严重,不管如何,这确实带来了
C++使用者的不便;不过,C++下一代标准即将到来,越来越多的常用类进入了C++的标准库,随着时间的推展,我们有理由相信,会有越来的越多扩展库,
将更多地直接或间接(比如派生)地使用标准库的已经实现的类。
250 //显示当前记录(当前记录数据显示到窗口控件中) void HelloDatabase_GUIDialog:: ShowCurrentRecord() { 253 if ( current_ == records_. end()) { return ; } //显示基本信息: 259 wxString tmp; 260 tmp << _T( "第" ) << current_-> index << _T( "金,第" ) << current_-> day_index << _T( "天" ); 262 StaticTextLine1-> SetLabel( tmp); 264 tmp. clear(); tmp << _T( "冠军:" ) << FromGB2312( current_-> name. c_str()) << _T( "," ) << ( current_-> sex ? _T( "男" ) : _T( "女" )); StaticTextLine2-> SetLabel( tmp); tmp. clear(); tmp << _T( "省份:" ) << FromGB2312( current_-> province. c_str()); StaticTextLine3-> SetLabel( tmp); tmp. clear(); tmp << _T( "项目:" ) << FromGB2312( current_-> item_name. c_str()); StaticTextLine4-> SetLabel( tmp); tmp. clear(); tmp << _T( "成绩:" ) << FromGB2312( current_-> score. c_str()); StaticTextLine5-> SetLabel( tmp); //查询当前记录的备注和图片数据,并显示 282 ShowCurrentMemoAndPhoto(); 284 Fit(); //wxWindows的函数,用于让窗口的大小适应图片 285 Center(); //让窗口在屏幕上居中 }
成员函数ShowCurrentRecord()用来显示当前记录的数据。当前记录是哪一条?这由current_成员数据决定。253行,程序判断当前记录是否为空,如果是,则直接返回.
259
行定义一个wxString临时变量:tmp。wxString有一个非常类似std::stringstream的功能:即通过 <<
操作符来拼接成一个字符串,比如260行便可以拼接出类似“第1天,第1金”这样的内容。262行,将拼接后的内容,显示到窗口界面上的
“StaticTextLine1”控件上。在每次要重新拼接内容时,tmp需要通过调用clear()函数来清除原有内容(否则内容会越拼越长)。
显示完记录的基本信息(共5个)之后,程序在282行调用了另一个成员函数:ShowCurrentMemoAndPhoto(),该函数用来查询、显示当前记录的备注及图片数据。
284行代码,用以让窗口重新调整大小(以保证可以显示当前记录的图片)。285行,则可以让窗口自动在屏幕上居中显示。
288 //查询当前记录的备注和图片数据,并显示 void HelloDatabase_GUIDialog:: ShowCurrentMemoAndPhoto() { if ( current_ == records_. end()) { return ; } 296 mysqlpp:: Connection con = ConnectDatabase( TextCtrlPassword-> GetValue()); if (! con. connected()) { return ; } //构建专门查询指定记录的备注和图片数据的SQL语句 //通过金牌次序(abs_index)来指定是哪一条记录 305 mysqlpp:: Query query(& con, false ); query << "SELECT abs_index, memo, photo FROM champions_2008 WHERE abs_index = " << current_-> index; if ( mysqlpp:: StoreQueryResult res = query. store()) { 311 if ( res. num_rows() > 0 ) { wxString memo = FromGB2312( res[ 0 ][ "memo" ]. c_str()); TextCtrlMemo-> SetValue( memo); mysqlpp:: sql_blob jpg = res[ 0 ][ "photo" ]; if ( jpg. length()) { //构建一个wx的内存流,并将mysql++查得的图片数据, //保存到该内存流中 wxMemoryInputStream stream ( jpg. c_str(), jpg. length()); wxImage img; //构建一个wx图片对象 img. LoadFile( stream); //从内存流中读入图片到img对象 wxBitmap bmp ( img); //将wxImage对象转换为一个位图对象 //调整StaticBitmap1控件大小 StaticBitmap1-> SetSize( bmp. GetWidth(), bmp. GetHeight()); StaticBitmap1-> SetBitmap( bmp); //更新其位图 //通知窗口改变大小: StaticBitmap1-> SetSizeHints( bmp. GetWidth(), bmp. GetHeight()); } } } }
记录的基本数据是一次查回全部记录,然后保存在list中;与引不同,记录的备注和图片数据是在每次需要显示当前记录数据时,再从数据库中查询获得。
〖小提示〗:考虑数据压力
许多数据库应用,在数据表中往往存储了成千上万,甚至数十万、百万、千万条的记录。如果一句简单的 SELECT * FROM xxx,往往会让这些数据先是拥挤地在网络上穿行(假设数据库在另一台主机上),然后再挤爆了本机的内存。
2008年北京奥运会,我们当然是获得金牌最多的国家!从这个意义上讲,我们的金牌数很多很多。不过如果从数据库的存储量来看,51条记录那可一点不多。因此,本例如果一次将所有备注与图片数据都查回,并且同样保存在内存中,对于流行的个人电脑配置,并不是很大的压力。
本例特意示例一种常用的数据处理技巧:延迟大数量字段的查询。想像如果有1万条记录,那么查询一万条记录的基本信息可以接受的,但如果此时还带有备注和图片,就会让程序占用太多内存了。
ShowCurrentMemoAndPhoto()
函数在296行重新建立一个数据库连接。然后在305行建立一个SQL查询语句,专门用于查询备注和图片数据。注意SQL语句中通过 WHERE
子句限定了查询图片和备注属于哪一条记录(也就是说,每次肯定只会查回一条记录的备注和图片数据)。
既然只会有一条(或零条)记录被查询,所以本函数的后续代码,你也就没有看到for语句。相反,311行通过if语句判断记录数是否大于0(更直观的写法,或许是判断其是否等于1)。
后续还有一些代码用于显示图片,暂不在本节详解。
成员函数(跳转当前记录)
和控制台版只能按次序一一输出每条记录不同,GUI版允许用户来回切换记录显示。这就需要我们实现用于跳转当前记录的函数
336 //跳转到第一条记录: void HelloDatabase_GUIDialog:: First() { if ( records_. empty()) return ; if ( current_ != records_. begin()) { current_ = records_. begin(); ShowCurrentRecord(); } } //跳转到前一条记录: void HelloDatabase_GUIDialog:: Prior() { if ( records_. empty()) return ; if ( current_ != records_. begin()) { -- current_; ShowCurrentRecord(); } } //跳转到下一条记录: void HelloDatabase_GUIDialog:: Next() { if ( records_. empty()) return ; std:: list< Record>:: const_iterator next = current_; ++ next; if ( next != records_. end()) { ++ current_; ShowCurrentRecord(); } } //跳转到最后一条记录: void HelloDatabase_GUIDialog:: Last() { if ( records_. empty()) return ; current_ = records_. end(); -- current_; ShowCurrentRecord(); }
4.7.3.3. 事件代码
现在,我们已经完成大多数功能的内部实现。但还没有将这些代码和窗口界面上的控件绑定起来。对于本例,需要处理控件都是按钮。按钮存在一个典型事件:那就是用户通过鼠标或键盘“按”下这个按钮,这称为按钮的点击事件(OnClick)。
请在“Management”边栏内切换到“资源文件”页,鼠标双击“HelloDatabase_GUIDialog”节点,用以打开“HelloDatabase_GUIdialog.wxs”设计界面。
绑定“获取”按钮事件
双击界面上的 “获取”按钮,IDE将打开“HelloDatabase_GUIMain.cpp”文件。然后滚动到该文件底部,可以发现,新增了一个成员函数的空架子
void HelloDatabase_GUIDialog:: OnButtonGetClick ( wxCommandEvent& event) { }
〖危险〗:事件响应函数自动生成
此过程中,wxSmith在头文件中添加了该事件函数的声明,通常这个函数不应被手工删除。另外,wxSmith在CPP源文件中生成该事件函数的空定义,且记这些代码不是手工录入的,读者如果粗心忽略这一点,就会碰到程序编译之后,事件响应似乎“不灵光”的问题。
我们将为该空函数添加代码,及函数注释
389 //“获取”按钮点击事件函数 void HelloDatabase_GUIDialog:: OnButtonGetClick( wxCommandEvent& event) { 392 ConnectAndGetRecords(); 393 First(); }
392和393是我们添加的代码。作用是连接数据库,取得数据,并且自动切找到第一条。
绑定“|<”、“<”、“>”、“>|”按钮事件
这四个按钮分别需要实现“跳转到第一条/First”、“跳转到前一条/Prior”、“跳转到下一条/Next”及“跳转到最后一条/Last”的功能。
双击其中一个按钮,即可在源文件“HelloDatabase_GUIMain.cpp”内找到生成的空成员函数。下面是四个函数的实现代码:
// |< 按钮点击事件函数 void HelloDatabase_GUIDialog:: OnButtonFirstClick( wxCommandEvent& event) { First(); } // |< 按钮点击事件函数 void HelloDatabase_GUIDialog:: OnButtonPriorClick( wxCommandEvent& event) { Prior(); } // > 按钮点击事件函数 void HelloDatabase_GUIDialog:: OnButtonNextClick( wxCommandEvent& event) { Next(); } // >| 按钮点击事件函数 void HelloDatabase_GUIDialog:: OnButtonLastClick( wxCommandEvent& event) { Last(); }
4.7.4. 编译、运行
按下Ctrl + F9编译,再按下F9运行本程序。下面运行期的一个程序截图:
图 4-68 Hello Database GUI 版 运行界面
[回到目录]
相关文章推荐
- 第4章 感受(二)——4.5. Hello Database 准备
- 第4章 感受(二)——4.6. Hello Database 控制台版
- 第4章 感受(二)——4.1. Hello GUI 基础篇
- 第4章 感受(二)——4.2. Hello GUI 布局篇(1)
- 第4章 感受(二)——4.4. Hello Internet
- 第4章 感受(二)——4.3. Hello GUI 布局篇(2)
- 第4章 组织程序和数据
- sqliteDataBase 到底是不是线程安全的
- 使用Database Configuration Assistant(DBCA)创建数据库
- SQL Server中模式(schema)、数据库(database)、表(table)、用户(user)之间的关系
- 庶民感受盛世
- 初到深圳工作的一些感受
- 今天看了阿凡达,来说下感受
- 第3章 TCP/IP Socket网络通讯------第4章 实现Socket C/S应用程序
- [转] 一个出租司机给我上的一课,感受颇深
- AngularJS权威教程 第4章 作用域Scope
- 4.9. Routing Policy Database (RPDB)
- Local database deployment problems and fixtures
- Creating Database Control Administrative Users
- 老李推荐:第4章1节《MonkeyRunner源码剖析》ADB协议及服务: ADB协议概览 2