使用 MySQL 进行数据库编程






4.53/5 (7投票s)
运行 MySQL 并使用 KDevelop 编程。
引言
无论爱它还是恨它,有一件事你可以基本保证,如果你将花费任何时间进行计算机编程,那就是迟早你将不得不创建或显示来自数据库的数据。在本文中,我们将探讨如何在 KDE 中编程 MySQL 数据库。对于完全不熟悉的人来说,KDE 环境是基于 Trolltech 提供的 Qt C++ 库构建的,因此在本文中,以字母 Q 开头的类是 Qt 库的一部分,而以字母 K 开头的类是 KDE 库的一部分。我们将使用的主要类是内置的数据库小部件 `QDataTable`、`QDataBrowser` 和 `QDataView`,但首先,我们需要设置一些要在数据库中使用的信息。对于本演示,我们将使用 MySQL,它随 openSuSe 提供,并且鉴于其流行性,可能也随所有其他版本的 Linux 提供。
为了澄清,本文最初是作为 SuSe 10.0 上《KDevelop 编程入门》第五章编写的,并已针对 openSuse 10.3 的此版本进行了更新和测试。
设置数据库
设置 MySQL 似乎有两种方法。简单的方法和痛苦的方法,而且似乎只有微小的区别决定了你的设置将走向哪种方式。这种方法是经过几天测试才确定的。如果你想用其他方式或遇到麻烦,你需要这个链接:MySQL 文档。
Order
-
安装 MySQL
-
启动 MySQL
-
更改 root 密码(推荐)
-
(可选)卸载 MySQL
1. 安装 MySQL
MySQL 随 openSUSE 安装;它只是没有作为服务启动,但仍然有理由使用 Yast 检查安装。这主要是因为 MySQL 的图形用户界面工具默认没有安装,所以除非你设置系统时知道你需要它们,否则你可能没有它们。
要安装或仅检查你是否拥有所需的一切,请打开控制中心 (Yast) 并选择“软件管理”选项。它应该以搜索选项打开,输入 mysql,不区分大小写,然后按回车。你在这里感兴趣的部分是 mysql-administrator 和 mysql-query-browser;查询浏览器用于比我们这里使用的更高级的用途,但拥有它也没有坏处。
如果你自己设置过系统,Yast 设置应该很熟悉;如果不是,请点击你感兴趣的项目旁边的复选框。黑色勾表示安装,蓝色勾表示已安装,垃圾桶表示移除,闪电符号表示更新。
还应该注意的是,这些程序包含在主菜单的“系统/服务配置”下,但如果你想在桌面上设置链接,可以在 `/usr/share/applications` 中找到它们。在 10.3 版本中,它们将出现在主菜单“应用程序”部分的顶部“新应用程序”部分。如果你认为会经常需要它们,可以将它们复制到桌面。
从 openSUSE 10.3 的角度来看,MySQL Administrator 不在光盘上;你需要访问 openSUSE 网站并搜索“mysql-administrator”软件。如果你通过一键安装安装它,它还会下载所有必需的文件,以便你之后可以通过 Yast 正常安装 MySQL 查询浏览器。
2. 启动 MySQL
默认情况下,当你设置 Suse 时,MySQL 服务器是禁用的,因此你需要自己启动它。为此,你需要访问你所使用的 Linux 版本的运行级别服务。在 Suse 上,这通过“控制中心/YaST2 模块/系统/系统服务 (Runlevel)”完成。你需要管理员权限才能访问服务列表。
一旦你在此处启用了 MySQL 服务器,它将在你每次启动计算机时启动,所以一旦你完成,你就可以忘记它,随时使用 MySQL。
3. 更改 root 密码
此时,建议您更改 root 密码。语法是
mysqladmin -u root password " newpwd"
4. 卸载 MySQL
如果你已经到了毫无进展,并且从 MySQL 网站也无法获得答案的地步,最快的解决方案(只要你使用的机器上没有有效的生产数据库)就是直接删除 MySQL 并从头开始。你可以通过打开 Yast,搜索 mysql,并将 MySQL 选项设置为卸载来完成此操作,这应该会在复选框处显示一个垃圾桶。
Suse 上 MySQL 的数据库文件存储在 `var/lib/mysql` 中。如果你删除此文件夹,你将完全移除 MySQL 信息,包括所有数据库和用户,所以就像我说的,如果这台计算机曾用作 MySQL 生产机器,请勿触碰此文件夹,否则你将丢失所有内容。
Administration
要精确设置您想要的 MySQL,请启动 MySQL Administrator。
使用 root 作为用户,并输入您最近设置的密码。
启动后,它应该看起来像上面那样。这里重要的是用户和目录部分,您可以在其中看到系统上的数据库。
Knoda
使用 MySQL 的首选工具将是 KDE Knoda 应用程序,原因很简单,它允许我们设置数据库而无需自己编写所有 SQL。您可能需要通过 Yast 安装它。
Knoda 应用程序的链接可以在主菜单“办公/数据库”下找到。
启动时,Knoda 会询问您要使用哪个驱动程序,我们选择 mysql,然后点击“连接”,这将弹出连接对话框。
要设置和使用我们的数据库,我们只需使用 root 连接,并输入我们之前设置 MySQL 时输入的密码。一旦 Knoda 连接成功,假设一切按计划进行,您应该可以访问 MySQL 默认数据库 Bri。我们可以忽略这些,因为在本章的项目中,我们将设置一个简单的音乐数据库,然后可以通过 KDevelop 访问并在我们自己的程序中使用。
要在 Knoda 中创建一个全新的 MySQL 数据库,请转到“文件/新建/数据库”菜单。
选择“新建数据库”菜单项,我们将得到这个对话框
如您所见,要设置一个新数据库,我们只需键入名称并选择“确定”。此时,似乎什么都不会发生,因为 Knoda 不会自动更改到新创建的数据库,也不会询问您是否要更改。不过这不是一个大问题,因为更改到新数据库很容易。
选择“数据库”组合框,然后从任何可用数据库中选择。我们想要 KDevelopMusic,一旦选择,Knoda 将加载我们新的空数据库。首先,我们要创建三个表。在表中创建新项,无论是表、查询等,都像右键单击并选择“新建”一样简单。对于这个示例音乐数据库,我们将创建三个表,它们是 Artist 表、Album 表和 Songs 表。
我们在这里可以看到 Artist 表的初步设置,它包含两个字段:Artist_ID 和 Artist。要向表中添加字段,请单击“新建字段”按钮,然后在右上角的选项框中填写字段的选项。您可以在此处尝试字段和表,但您应该在开始之前对表将包含什么以及它们将如何协同工作有一个清晰的了解。
设计数据库最糟糕的方法之一就是试图将所有内容都塞进一个表中。这只会自找麻烦,因为您会使访问表的工作变得更加困难;例如,如果通过此表我们知道艺术家的 ID,我们可以使用类似于 `Select * from Artist where Artist_ID = knownvalue` 的伪 SQL 语句。但是,如果我们将专辑名称也添加到此表中,我们最终将不得不尝试从 `Select` 语句的结果中分离出各个专辑标题。
这里创建的表是一个简单的音乐数据库;基本上,我们将存储艺术家姓名、专辑标题以及每个专辑包含的歌曲。数据库将以这样的方式创建:我们可以从歌曲中获取艺术家姓名,或从歌曲中获取专辑名称,但我们不会包含歌曲的播放时长、发行日期或出版商等信息。如果你真的想要所有这些详细信息,你可以随时添加它们或在 amaroK 中录制专辑。
创建表格后,您可以开始向其中添加信息。这在 Knoda 中通过右键单击表格并选择“开始”来完成。
目前,我们将在 Knoda 中添加一些初始测试信息,以确保一切都按预期运行。我们还可以在这里对数据进行操作,这样当我们开始使用 KDevelop 开发程序时,我们可以专注于编程方面,并且如果需要,可以已经有一些 SQL 语句。
为了测试目的,我添加了三个乐队,有四张专辑,总共六十二首歌曲。测试表如下所示,首先是 Artist 表,接着是 Album 表,以及 Songs 表的一个片段。
为了将此作为正确数据库查询的结果进行查看,我们需要右键单击“查询”并选择“新建”来创建一个新查询。
这是 Knoda 用于 SQL 查询的图形开发界面,查询本身是该数据库的标准查询。它基本上根据专辑和艺术家获取所有歌曲,当您运行它时,如下所示
我称之为“标准查询”,因为任何其他使用的查询基本上都是对这个查询的修改。您可以通过两种方式使用 Knoda 开发 SQL 查询:最明显的是通过上面所示的图形界面,另一种是对于那些能够凭空编写 SQL 的人,您可以直接输入 SQL。
要进入 SQL 编辑器,请选择“视图”菜单,然后选择使用 QBE。这将关闭图形开发界面,并允许您直接在编辑器中键入 SQL。“标准查询”的 SQL 是
SELECT "Artist0"."Artist" , "Album1"."Album_Title" , "Songs2"."Song_Title"
FROM "Artist" "Artist0" , "Album" "Album1" , "Songs" "Songs2"
WHERE ("Album1"."Artist_ID"="Artist0"."Artist_ID")
AND ("Songs2"."Album_ID"="Album1"."Album_ID")
尽管图形界面存在并且默认打开,如果它能让生活更轻松,那么不用它似乎有点傻。
当我们第一次开始一个新查询时,上面图片中显示的表并不存在,所以我们需要添加它们。我们通过右键单击组合框上方的空白区域来完成此操作。这将为您提供一个带有一个选项的菜单:“添加数据源”。当您选择此选项时,将打开一个对话框,显示当前所选数据库中可用的表。
您可以随意添加任意数量的表,在这个例子中,我们在关闭对话框之前添加了所有表。
将表添加到查询中后,您首先要做的就是设置表之间的链接。这通过选择要链接的表中的项目并将其拖到另一个表中的相应项目来完成。
例如,在上图中,Artist 表中的 Artist_ID 应该映射到 Album 表中的 Artist_ID。一个好的做法是,表之间的任何链接在每个表中都使用相同的名称,这样任何查看表的人都可以轻松地看到哪些链接到哪些。说到名称,细心的人会注意到 Knoda 在添加表时,将表名称添加了一个从零开始的数字。这些只是表在 SQL 语句的 `From` 部分中解析的别名。
FROM "Artist" "Artist0" , "Album" "Album1" , "Songs" "Songs2"
这告诉数据库 "Artist0" 是 "Artist" 的别名,等等。
当您在要建立链接的表上释放鼠标时,会弹出一个对话框,显示 Knoda 认为您正在尝试建立的链接,并允许您更改它,如果您本意是链接到表中的另一个项目。
一旦您建立了链接,您只需设置您想要显示的内容。这通过屏幕底部的下拉框完成。如果您是完全的初学者,可能需要几次尝试才能使其完全按照您的意愿显示,但作为一条基本经验法则,您应该将显示信息最少的表放在左侧,并向右移动,随着您的操作显示更多信息。一旦将表添加到查询中,它们就可以从表的下拉框中选择。一旦我们选择了表,我们就可以从该表中选择任何我们想要显示的字段。
在本例中我们设置的“标准查询”中,我们只显示与主题相关的信息。所有以 ID 结尾的项目都旨在供我们在数据库内部使用,因此对于那些只是为了查看特定专辑中的曲目而查看数据库的人来说,它们并不重要。
KDevelop 数据库支持
KDevelop 有三个 GUI 小部件可用于显示数据库信息;它们是 Qt 类小部件 DataTable、DataBrowser 和 DataView。
使用 DataTable
不幸的是,目前的数据库编程并非如预期的那么直接,因此最好从我们必须手动完成大部分工作的想法开始。问题的主要来源在于,虽然 DataTable 会尝试通过向导设置数据库连接,但它不会保存您用于登录数据库的信息,这意味着如果您尝试仅使用向导并运行代码,您将被告知连接数据库时出现错误。
让 `QDataTable` 工作的方法是将其拖放到表单上,然后取消向导。这将向您的表单添加一个空白的 `QDataTable`。我们可以用它来工作。
首先,我们必须添加一个对话框,以便从用户那里获取用户名和密码。我们可以将它们硬编码,但这非常糟糕,因此为了不鼓励不良习惯,我们将从一开始就正确地做。
添加对话框
要向项目添加新对话框,请单击 KDevelop 左侧的“新建文件”选项卡,然后选择相应的对话框。对于我们简单的用户和密码对话框,我们将选择带有按钮的标准对话框(底部)。当我们选择对话框时,会弹出一个设置对话框,
这只是询问文件的名称,并提供了最后一次改变我们想要哪个对话框的机会。然后会弹出一个自动生成对话框,询问我们想将此添加到哪个当前项目。如果您只想将对话框添加到当前正在处理的项目中,那么点击“确定”接受默认值即可。
虽然我们现在有了新的 UI 文件,但我们仍然没有任何类文件来实现新的用户界面,在这个例子中是我们的密码对话框。然而,这里需要注意类名,所以不要急于创建名为 `PasswordDialog.h` 和 `PasswordDialog.cpp` 的文件,否则你可能会让事情变得非常复杂和烦人。请记住前面章节中关于元对象编译器如何工作以及它如何为一个 `.ui` 文件创建头文件和 `.cpp` 文件(即使你永远看不到它们)的说法,正是这个文件控制着表单小部件的类声明。任何表单的代码实现都需要从这些类继承,这确实有点棘手,因为它们目前尚不存在。
所以首先,我们需要让元对象编译器生成我们的类和头文件。这通过运行 Automake 及其相关工具,然后从构建菜单配置,接着构建项目来完成。项目构建完成后,你将在 `projectname/debug/src` 目录中看到新创建的文件。在本例中,它们是 `passworddialog.h`,它看起来像
#ifndef PASSWORDDIALOG_H
#define PASSWORDDIALOG_H
#include <qvariant.h>
#include <qdialog.h>
class QVBoxLayout;
class QHBoxLayout;
class QGridLayout;
class QSpacerItem;
class QPushButton;
class passwordDialog : public QDialog
{
Q_OBJECT
public:
passwordDialog( QWidget* parent = 0, const char* name = 0, bool modal = FALSE,
WFlags fl = 0 );
~passwordDialog();
QPushButton* buttonHelp;
QPushButton* buttonOk;
QPushButton* buttonCancel;
protected:
QHBoxLayout* Layout1;
QSpacerItem* Horizontal_Spacing2;
protected slots:
virtual void languageChange();
};
以及 `passworddialog.cpp` 文件
#include <kdialog.h>
#include <klocale.h>
/****************************************************************************
** Form implementation generated from reading ui file
**'/home/pseudonym67/Dev/KDE/BeginningKDEProgramming/chapterfivedatatable/src/
** PasswordDialog.ui'
**
** Created: Fri Mar 3 18:09:34 2006
** by: The User Interface Compiler ($Id: qt/main.cpp 3.3.4 edited Nov 24 2003 $)
**
** WARNING! All changes made in this file will be lost!
****************************************************************************/
#include "PasswordDialog.h"
#include <qvariant.h>
#include <qpushbutton.h>
#include <qlayout.h>
#include <qtooltip.h>
#include <qwhatsthis.h>
/*
* Constructs a passwordDialog as a child of 'parent', with the
* name 'name' and widget flags set to 'f'.
*
* The dialog will by default be modeless, unless you set 'modal' to
* TRUE to construct a modal dialog.
*/
passwordDialog::passwordDialog( QWidget* parent, const char* name, bool modal, WFlags fl )
: QDialog( parent, name, modal, fl )
{
if ( !name )
setName( "passwordDialog" );
setSizeGripEnabled( TRUE );
QWidget* privateLayoutWidget = new QWidget( this, "Layout1" );
privateLayoutWidget->setGeometry( QRect( 20, 240, 476, 33 ) );
Layout1 = new QHBoxLayout( privateLayoutWidget, 0, 6, "Layout1");
buttonHelp = new QPushButton( privateLayoutWidget, "buttonHelp" );
buttonHelp->setAutoDefault( TRUE );
Layout1->addWidget( buttonHelp );
Horizontal_Spacing2 = new QSpacerItem( 20, 20, QSizePolicy::Expanding,
QSizePolicy::Minimum );
Layout1->addItem( Horizontal_Spacing2 );
buttonOk = new QPushButton( privateLayoutWidget, "buttonOk" );
buttonOk->setAutoDefault( TRUE );
buttonOk->setDefault( TRUE );
Layout1->addWidget( buttonOk );
buttonCancel = new QPushButton( privateLayoutWidget, "buttonCancel" );
buttonCancel->setAutoDefault( TRUE );
Layout1->addWidget( buttonCancel );
languageChange();
resize( QSize(511, 282).expandedTo(minimumSizeHint()) );
clearWState( WState_Polished );
// signals and slots connections
connect( buttonOk, SIGNAL( clicked() ), this, SLOT( accept() ) );
connect( buttonCancel, SIGNAL( clicked() ), this, SLOT( reject() ) );
}
/*
* Destroys the object and frees any allocated resources
*/
passwordDialog::~passwordDialog()
{
// no need to delete child widgets, Qt does it all for us
}
/*
* Sets the strings of the subwidgets using the current
* language.
*/
void passwordDialog::languageChange()
{
setCaption( tr2i18n( "UserName And Password" ) );
buttonHelp->setText( tr2i18n( "&Help" ) );
buttonHelp->setAccel( QKeySequence( tr2i18n( "F1" ) ) );
buttonOk->setText( tr2i18n( "O&K" ) );
buttonOk->setAccel( QKeySequence( tr2i18n( "Alt+K" ) ) );
buttonCancel->setText( tr2i18n( "Ca&ncel" ) );
buttonCancel->setAccel( QKeySequence( tr2i18n( "Alt+N" ) ) );
}
#include "PasswordDialog.moc"
现在我们可以着手让对话框运行起来。右键单击 Automake Manager 中的 `.ui` 文件。
为了创建实现密码对话框的类,我们需要选择“子类化向导”或“创建或选择实现”,实际的文件创建在这两种方式中是相同的。
主要的区别在于,使用子类化向导,如您所见,您可以选择专门实现一些按钮。在这种情况下,我们需要实现 `accept` 函数,以便稍后保存添加的用户名和密码字符串,并使用它们来访问数据库。为了避免命名问题,我将该类命名为 `PasswordDialogImpl`,以明确这是密码对话框的实现。
我们得到的最终对话框是 Automake Manager 对话框。如果我们查看 `.h` 文件,我们可以看到
#ifndef PASSWORDDIALOGIMPL_H
#define PASSWORDDIALOGIMPL_H
#include "PasswordDialog.h"
class PasswordDialogImpl : public passwordDialog
{
Q_OBJECT
public:
PasswordDialogImpl(QWidget* parent = 0, const char* name = 0, bool modal = FALSE,
WFlags fl = 0 );
~PasswordDialogImpl();
/*$PUBLIC_FUNCTIONS$*/
public slots:
/*$PUBLIC_SLOTS$*/
protected:
/*$PROTECTED_FUNCTIONS$*/
protected slots:
/*$PROTECTED_SLOTS$*/
virtual void accept();
};
它不仅继承自 MOC 创建的 `passwordDialog` 类,还提供了 `accept()` 函数的实现。由于我们刚刚向项目添加了新文件,请从构建菜单运行 automake 和相关工具,然后配置,再运行构建以确保一切构建正确。现在我们可以继续实现对话框。
实现对话框
从对话框的角度来看,实现是直截了当的。我们声明了几个字符串,一个用于用户名,一个用于密码。
private:
/** Store the UserName
**/
QString strUserName;
/** Store the Password
**/
QString strPassword;
并添加 get 和 set 功能,然后将以下代码添加到重写的 `accept` 函数中
setPassword( passwordLineEdit->text() );
setUserName( userLineEdit->text() );
QDialog::accept();
技术难题出现在我们意识到我们希望对话框在应用程序启动时出现,但最好在它显示之前。这样,当应用程序启动时,它将按照我们希望的方式显示数据库表数据,而不会在应用程序启动后明显地绘制它。为此,我们需要重写 `polish` 函数。`polish` 函数在小部件显示之前执行初始化,但文档警告您应该在实现自己的代码之前调用基类的 `polish` 函数。这确保了小部件的默认代码在您进行更改之前完成,因为在基类调用 `polish` 之前进行更改可能会触发对 `polish` 的进一步调用,最终导致无限递归。
所以我们重写 `polish` 函数看起来是这样的
void ChapterFiveDataTableWidget::polish()
{
QWidget::polish();
PasswordDialogImpl *dlg = new PasswordDialogImpl();
if( dlg->exec() == QDialog::Accepted )
{
loadDatabase( dlg->userName(), dlg->password() );
}
}
在这里,我们调用基类 `polish`,然后创建一个新对话框,并使用 `exec` 函数以模态方式执行它。请注意,调用 `show` 将显示一个无模态对话框,该对话框将使主应用程序小部件在后台显示,而这正是我们不希望它做的事情。
查询数据库
现在我们已经在应用程序启动时输入了用户名和密码,还有三个参数我们需要连接到数据库。它们是主机计算机、数据库名称以及我们将用于连接数据库的驱动程序。这些参数都输入到 Knoda 连接对话框中,所以现在应该很熟悉了。我已经在 `ChapterFiveDataTableWidget` 类中设置了字符串来保存变量,并提供了适当的访问函数,尽管这个类没有从任何地方访问,所以它们也可以作为私有变量留在构造函数中进行初始化,例如
strName = "KDevelopMusic";
strDriver = "QMYSQL3";
strHost = "localhost";
这些变量都用于 `loadDatabase` 函数中,该函数在我们重写的 `polish` 函数的末尾调用。
驱动程序变量启动一切,并与以下行一起使用
dbConnection = QSqlDatabase::addDatabase( strDriver );
此调用初始化我们在 `ChapterFiveDataTableWidiget` 类声明中声明的数据库连接变量,如下所示
QSqlDatabase *dbConnection;
Qt 可用的数据库驱动程序的详细列表可在帮助的 SQL 模块驱动程序部分找到。
现在我们设置数据库连接参数
dbConnection->setDatabaseName( name() );
dbConnection->setUserName( user );
dbConnection->setPassword( password );
dbConnection->setHostName( host() );
if( dbConnection->open() == true )
如您所见,我们只是使用代码来设置与 Knoda 使用的完全相同的参数,因此只要我们能够进入 Knoda,`dbConnection->open` 调用就没有理由返回 `false`。
我们现在正处于可以实际查询数据库并显示它的位置,并且在使用数据表时,只要我们做好了充分的准备,实际查询数据库就异常简单。首先,浏览 `QSqlCursor` 类帮助文件及其派生类 `QSqlSelectCursor` 表明,我们可以将表名传递给 `QSqlCursor` 或将 SQL 语句传递给 `QSqlSelectCursor` 构造函数,以及我们的数据库连接,这将给出查询结果在我们新构造的游标对象中,然后我们可以将其传递给 `QDataTable` 对象,让它为我们完成所有绘制和显示工作。在代码中,它看起来像这样
///QSqlCursor *cursor = new QSqlCursor( "Artist", true, dbConnection );
QSqlSelectCursor *cursor = new QSqlSelectCursor( "SELECT Artist FROM Artist",
dbConnection );
/// QSqlSelectCursor *cursor = new QSqlSelectCursor( strSqlStatement, dbConnection );
dataTable1->setSqlCursor( cursor, true, false );
dataTable1->refresh();
for( int i=0; i<dataTable1->numCols(); i++ )
{
dataTable1->adjustColumn( i );
}
我们在上述代码中被遮盖的第一个调用是创建了一个 `QSqlCursor` 对象,它以 Artist 表作为参数,并带有一个值为 true 的自动填充选项以及数据库连接指针。
然后我们通过调用 `setSqlCursor` 函数将游标传递给 `QDataTable`,传入游标本身和自动填充选项的 `true` 值,这告诉表自动显示数据。最后一个参数是自动删除,因为我们不希望在这里编辑表,我们将其设置为 `false`,这是默认值。`QDataTable` `refresh` 的后续调用告诉表重新绘制自身,它将使用当前游标来完成此操作。
最后一段代码只是获取列数,然后告诉所有表格列将其宽度调整为该列中最大条目的宽度。
该表的最终视图是
这很好,也正是我们想要的,因为它显示了表中的所有数据。唯一的问题是,这并不是我们真正想要的,因为 Artist_ID 是一个键值,对于那些只是尝试查看数据库中记录的人来说,它没有任何兴趣,所以我们将切换到 `QSqlSelectCursor`,看看我们是否能得到一些更合适的东西。
如果我们使用 SQL 语句
"SELECT Artist FROM Artist"
在 `QSqlSelect` 构造函数中,我们得到
这更符合我们想要的效果。当然,现在我们必须再玩一下,看看我们在 Knoda 中做的 `StandardQuery` 会是什么样子
使用 DataBrowser
设置 DataBrowser 项目可能有点棘手,所以这里是痛苦最小的方法。像所有其他项目一样,使用基于 Simple Designer 的 KDE 应用程序创建它,并删除标签和按钮以及所有代码。然后打散表单上的布局并调整大小,以便将 `QDataBrowser` 对象放入其中。然后对项目进行完整构建,您需要运行 automake 和相关工具,然后进行配置,以确保一切都令人满意。然后关闭 KDevelop。这样做的目的是,当您加载 KDevelop 时,该项目会在启动时加载。重新启动 KDevelop,然后将 `QDataBrowser` 添加到表单中。如果数据库连接向导启动,那么一切正常,否则您将不得不重新启动 KDevelop 并重试。
对于这个项目,我们将需要通过向导运行,尽管它不保留数据库连接信息,我们必须手动编写代码,但它确实为我们提供了查询结果的 GUI,这为我们节省了自己完成这项任务的时间。
首先,我们点击“设置数据库连接”按钮并填写连接对话框。
如您所见,这里唯一的改变是我们为连接提供了名称,其余输入与本章中的一样。点击“连接”按钮,我们得到
音乐连接已建立,我们可以点击“关闭”按钮。上次我们使用了 Artist 表,所以这次我们选择 Album 表并点击“下一步”。我承认此时这个数据库可能不适合演示浏览器,如果表包含更多要显示的项目,效果会更好,但即使只显示一个字段,您也应该能理解其思想,并且无论数据库表中存在多少字段,编程原则都适用。
字段的选择由中间的按钮控制,这里不是很清楚,但从上到下,它们是
- 将字段移到显示字段列表
- 从显示字段列表中移除字段
- 向下移动字段
- 向上移动字段
一旦我们设置好了想要的字段,就点击“下一步”。向导的下一个屏幕允许我们向 SQL 添加一个 `Where` 子句。
并设置显示字段的排序顺序。然后我们设置显示选项
最后一个选项允许我们设置是否希望向导生成用于更新的 SQL。这给我们一个看起来像这样的 GUI
重用对话框
接下来要做的是将 `polish` 函数添加到继承自“`projectname`”基类的类中;我们通过常规方式添加
public slots:
/*$PUBLIC_SLOTS$*/
virtual void polish();
到头文件,然后将实现添加到 CPP 文件中。向导生成的文件中有一些代码我们可以使用。`ChapterFiveDataBrowserWidgetBase` 类中 `polish` 函数的实现是
if ( dataBrowser3 ) {
if ( !dataBrowser3->sqlCursor() ) {
QSqlCursor* cursor = new QSqlCursor( "Album", TRUE, MusicConnection );
dataBrowser3->setSqlCursor( cursor, TRUE );
dataBrowser3->refresh();
dataBrowser3->first();
}
}
这在我们以后会有用,但它现在不能工作,因为 `MusicConnection` 所需的参数,即向导赋予 `QSqlDatabase` 的名称,尚未设置。
首先,我们需要使用我们在 chapterfivedatatable 项目中使用的对话框来获取用户名和密码。我们通过右键单击 Automake Manager 中的项目来完成此操作。
并选择“添加现有文件...”,这将弹出以下对话框
选择 `.ui` 文件以及实现头文件和定义文件,然后点击“添加选定”。只要你不开始更改名称,实现应该能顺利进行,因为 MOC 将完全按照它对原始项目所做的那样生成“`projectname`”widgetbase 类。
然后系统会询问您是否要链接或复制文件。在正常情况下,您会将您重用的类放在标准目录中并链接它们,而不是复制它们;这意味着如果您确实需要对类文件进行任何更改,那么它们将自动传播到所有项目中,并且您无需开始在硬盘上搜索同一类的不同实现。
但在这种情况下,它们已被复制到项目中,因为在这种情况下,该项目是一个独立的,应以尽可能少的问题进行分发。
// Notice calling QWidget polish here as we
// are overriding the behaviour added by the wizard
// which initially overrode polish in the base class.
//
QWidget::polish();
PasswordDialogImpl *dlg = new PasswordDialogImpl();
if( dlg->exec() == QDialog::Accepted )
{
并构建项目,确保您已将所有必需的头文件放置到位。然后添加 `QSqlDatabase` 的设置代码
MusicConnection = QSqlDatabase::addDatabase( driver() );
if( MusicConnection == 0 )
{
KMessageBox::error( this, "The database connection is invalid", "Data Browser Demo" );
return;
}
MusicConnection->setDatabaseName( name() );
MusicConnection->setHostName( host() );
MusicConnection->setUserName( dlg->userName() );
MusicConnection->setPassword( dlg->password() );
if( MusicConnection->open() == true )
{
现在的问题是这段代码仍然不起作用,原因如下
就调试器而言,目前 GUI 对象尚未完全构建,这意味着对数据浏览器对象的任何测试都相当碰运气;事实上,在我的电脑上,这行代码
if( !dataBrowser3->sqlCursor() )
每次都失败,因为它没有给 `sqlCursor` 一个 `false` 值。所以我们所做的是删除这一行,并继续生成指向我们数据的新 `QSqlCursor`。最坏的情况下,在这里 `QSqlCursor` 继承自 `QObject`,我们应该只在函数结束前多一个 `QSqlCursor` 悬挂着。
当我们删除对 `QSqlCursor` 的测试时,我们得到
if( MusicConnection->open() == true )
{
if( dataBrowser3 )
{
QSqlCursor* cursor = new QSqlCursor( "Album", TRUE, MusicConnection );
dataBrowser3->setSqlCursor( cursor, TRUE );
dataBrowser3->refresh();
dataBrowser3->first();
}
}
以及一个给我们带来以下结果的程序
最后一点,您应该记住我们接受了自动编辑的默认设置,即开启。这意味着您在运行此程序时对标题所做的任何更改都将同步到数据库。
使用 DataView
`QDataView` 是 `QDataBrowser` 的精简版本,没有内置的记录遍历功能。它更多地用于关注数据库的特定部分,而不是浏览所有记录。例如,查看个人记录,其中数据库中的下一条记录不一定与您正在处理的此记录有任何关联。因此,音乐数据库不足以展示 `QDataView` 的工作原理,所以我使用 Knoda 按照上述方法快速制作了一个虚构的人物数据库。
Knoda 表格看起来像
如您所见,这是一个足够简单的数据库,只包含虚构人物的姓名和地址,嗯,地址是虚构的。然而,它确实为我们提供了足够的字段,使我们的数据视图项目看起来更体面一些。
要启动创建 chapterfivedataview 项目的向导,请像以前一样创建基于 Simple Designer 的 KDE 应用程序,并删除小部件和 button_clicked 功能;保存并构建项目,然后向表单添加一个 DataView。
该向导与 DataBrowser 向导几乎相同,只是稍微短一些。
与之前的项目相比,设置上的唯一区别是细微的,例如数据库名称的更改。
尽管当我们进入字段对话框时,有更多字段要显示。
当我们查看代码时,会发现情况与最近的项目基本相同。基本结构是设置一个 `QSqlForm` 并将所有显示小部件添加到表单中。作为项目的用户,您不会直接使用 `QSqlForm`,但如果您希望手动编写数据库表单,查看向导实现是方法,因为它将小部件的名称设置为数据库的表列,然后将您传递给表单的记录映射到相应的列。这也是之前的 QDataBrowser 项目的工作方式,尽管在该项目中,`QDataBrowser` 封装了 `QSqlForm` 并为我们控制事务。
如果您查看生成的代码,会注意到 `polish` 函数在这里没有自动重载。这是因为 `QDataView` 的设计并不真正适合在启动时立即访问数据库,这也是 chapterfivedataview 项目的开发方式。
我们从一个独立的应用程序的想法开始,它在需要时联系数据库并显示数据库中的一个条目。从技术上讲,我们可以在那里重写 `polish` 函数并连接到数据库,但我决定将所有内容分开。所以现在我们有一个独立的数据库函数,通过单击按钮激活,并且在启动时,是一个空的 `QComboBox`。该项目的想法是用户连接到数据库,并将可用人员列表放入 `QComboBox` 中。然后用户在 `QComboBox` 中选择他们希望查看详细信息的人员姓名,然后填充表单。
数据库连接在单击按钮时启动
PasswordDialogImpl *dlg = new PasswordDialogImpl();
if( dlg->exec() == QDialog::Accepted )
{
FictionalPeopleConnection = QSqlDatabase::addDatabase( databaseDriver() );
FictionalPeopleConnection->setDatabaseName( databaseName() );
FictionalPeopleConnection->setHostName( host() );
FictionalPeopleConnection->setUserName( dlg->userName() );
FictionalPeopleConnection->setPassword( dlg->password() );
if( FictionalPeopleConnection->open() == true )
{
QSqlSelectCursor *cursor = new QSqlSelectCursor(
"SELECT FictionalPeople.First_Name, FictionalPeople.Middle_Names,
FictionalPeople.Surname FROM FictionalPeople", FictionalPeopleConnection );
cursor->select();
QString strName;
while( cursor->next() == true )
{
strName = cursor->value( "First_Name" ).toString() + " ";
if( cursor->value( "Middle_Names" ).toString().isEmpty() == false )
{
strName += cursor->value( "Middle_Names" ).toString() + " ";
}
strName += cursor->value( "Surname" ).toString();
namesComboBox->insertItem( strName );
}
}
}
如您所见,此函数中没有什么会让您感到惊讶。密码和数据库访问与前两个项目完全相同;只是初始化方式不同。SQL 语句
SELECT FictionalPeople.First_Name, FictionalPeople.Middle_Names,
FictionalPeople.Surname FROM FictionalPeople
从数据库中的所有条目中获取名字、中间名和姓氏。然后我们使用 `QSqlRecord` 类中的 `value` 函数构建一个姓名字符串。我们可以这样做是因为如果你遵循帮助中的类层次结构,你会发现 `QSqlSelectCursor` 类继承自 `QSqlCursor` 类,而 `QSqlCursor` 类又派生自 `QSqlRecord` 和 `QSqlQuery` 类。你可以根据你的心情将它不会自动填充表单的事实称为 bug 或功能,但是当你仔细想想,如果有一个很长的姓名列表,你为什么要特别想看第一个而不是其他任何一个呢?即使它们已经排序。
这样做的方法是,当列表中选择了一个新项目并在可见区域中显示时,从 `QComboBox` 接收激活信号。
在这里添加 Signal Handler 将函数添加到我们的小部件类中,然后我们需要为 `QString` 添加一个变量名,以便我们可以使用它。
virtual void namesComboBox_activated( const QString& name );
函数是,
void chapterfivedataviewWidget::namesComboBox_activated( const QString& name )
{
int nSurname = name.findRev( ' ' );
// KMessageBox::information( this, "The space for the surname is at "
+ QString::number( nSurname ) + "\nin name " + name );
int nLength = name.length();
setSurname( name.mid( nSurname+1, nLength ) );
// KMessageBox::information( this, "The surname is " + surname()
+ " \nFrom the name " + name );
int nFirstName = name.find( ' ' );
setFirstName( name.left( nFirstName ) );
// KMessageBox::information( this, "The first name is " + firstName()
+ "\nWith the space at " + QString::number( nFirstName ) );
if( nFirstName != nSurname )
{
setMiddleName( name.mid( nFirstName+1, nSurname - ( nFirstName+1 ) ) );
// KMessageBox::information( this, "The middle names are " + middleName()
+ "\n Starting from " + QString::number( nFirstName+1 ) + "\nEnding at "
+ QString::number( nSurname ) + "\nFrom a length of "
+ QString::number( nLength ) );
}
QString strSqlString = "SELECT * FROM FictionalPeople
WHERE FictionalPeople.First_Name = '" + firstName() + "' ";
if( middleName() != 0 && middleName().isEmpty() == false )
strSqlString += "AND FictionalPeople.Middle_Names = '" + middleName() + "' ";
strSqlString += "AND FictionalPeople.Surname = '" + surname() + "'";
// KMessageBox::information( this, strSqlString );
QSqlSelectCursor *cursor = new QSqlSelectCursor( strSqlString,
FictionalPeopleConnection );
cursor->select();
cursor->next();
dataView2->refresh( cursor );
// clear the middle name
strMiddleName.truncate( 0 );
}
有点大,所以我们将其分解为三个部分。第一部分是处理从 `QComboBox` 传递给我们的字符串。这个字符串是我们要找的人的全名,所以我们必须再次将其分解。如果没有显示字符串处理进度的 `KMessageBox` 调用,它看起来像这样
int nSurname = name.findRev( ' ' );
int nLength = name.length();
setSurname( name.mid( nSurname+1, nLength ) );
int nFirstName = name.find( ' ' );
setFirstName( name.left( nFirstName ) );
if( nFirstName != nSurname )
{
setMiddleName( name.mid( nFirstName+1, nSurname - ( nFirstName+1 ) ) );
}
在这里,我们使用 `QString` 的几个函数来分解字符串,首先是 `findRev` 函数,它从字符串的末尾开始向开头移动,直到找到您在函数调用中指定的内容。然后我们存储字符串的长度以备将来使用,并在类中声明几个字符串变量来保存信息。这些变量的名称(希望)是显而易见的:`FirstName`、`MiddleName` 和 `Surname`。然后我们使用 `findRev` 返回的值加一来存储姓氏,因为我们不想要空格。接着我们使用 `find` 函数从字符串的开头查找第一个空格,然后使用 `left` 函数存储名字。一旦我们有了开头和结尾,其余的都属于中间名类别,所以我们使用 `mid` 函数获取它或它们。
QString strSqlString = "SELECT * FROM FictionalPeople
WHERE FictionalPeople.First_Name = '" + firstName() + "' ";
if( middleName() != 0 && middleName().isEmpty() == false )
strSqlString += "AND FictionalPeople.Middle_Names = '" + middleName() + "' ";
strSqlString += "AND FictionalPeople.Surname = '" + surname() + "'";
代码的下一节使用我们刚刚从 `QString` 名称中提取的名称值构建 SQL 语句。应该注意的是,我们添加的变量值用单引号 `'` 包裹。这是一个小细节但至关重要,因为没有它们查询将无法工作。
最后一部分是
QSqlSelectCursor *cursor = new QSqlSelectCursor( strSqlString,
FictionalPeopleConnection );
cursor->select();
cursor->next();
dataView2->refresh( cursor );
// clear the middle name
strMiddleName.truncate( 0 );
在这里我们运行查询,并确保我们处于结果的开头,并且它们已准备好读取。然后我们将游标直接传递给 `QDataview` 的 `refresh` 函数,该函数接受 `QSqlResult` 并自动填充表单。
数据库开发的一个最后提示是:永远不要假定 SQL 语句已成功执行。如果出现问题,通常是 SQL 语句本身的错误。当你确定它没问题后,再检查所有其他方面。
摘要
这是对 KDevelop 数据库开发的一次简要介绍,重点关注可用的小部件。它试图涵盖很多方面,但仍有很多内容留待读者自行探索。本章可以再写几百页关于事务和数据保存等内容,但希望这些内容足以让人们入门。