ZHJ0125

使用 LinuxC+QT+MySQL 编写机票网络售票模拟系统

第一部分:嵌入式系统开发及应用
第二部分:QT编程实训
嵌入式系统开发及应用实训报告PDF文件
QT编程实训报告PDF文件
源代码:https://github.com/ZHJ0125/TicketingSystem

1 实训目的

本次实训需要设计并实现一个飞机票的网络售票模拟系统,主要包括服务端设计、售票端设计以及购票端设计。在QT for Linux环境中移植Linux C的底层逻辑代码,在UI编辑区添加前端界面,可以实现Linux后台与QT前端的结合,为以后的项目开发奠定基础。

2 实训原理

2.1 程序功能分析

首先分析一下整个航班系统的功能需求。航班模拟系统由三部分组成,分别为服务端售票端购票端。为方便表述,以下将售票端和购票端统称为客户端。服务端通常是处理性能较好的计算机,适合进行数据处理操作,因此服务端承担了读取航班信息,接收客户端的数据请求以及对航班数据进行相应处理等功能。售票端面向系统管理员,具有较高的管理权限,可以实现对机票的查询、增加、更新和删除等操作。购票端面向普通用户,只有查询航班和购买机票的功能。
因为底层的控制代码在Linux实训中已经编写完成了,所以本次QT实训只需要为服务端、售票端和购票端分别编写前端UI界面即可。另外需要注意的是,购票端是直接面向了普通用户,所以可以为它添加一个简易的欢迎页面。售票端因为具有较高的权限,所以需要增加一个登录界面,只有管理员输入正确的密码之后才能进行接下来的操作。

2.2 界面框架分析

前端界面可以使用基于QMainwindow的窗口类,这样可以非常方便地添加菜单选项,添加工具栏等各种可视化的图形操作。服务端、售票端和购票端采用统一的UI风格,添加菜单栏和工具栏。通过查找开源图库,可以获取相关图标资源,为每个工具栏选项添加图标,方便用户操作。
登录界面和欢迎页面通过新建QT设计类来实现。其中登录界面要实现密码判断和界面跳转的逻辑功能。

3 实训过程

我们团队设计的网络购票模拟系统,主要由三部分组成,分别是服务端、售票端和购票端。我负责的是售票端部分,下面对售票端的制作过程进行简单介绍。售票端的工程代码结构如下图所示。
在售票端代码中,大部分底层控制逻辑的文件都是直接由Linux移植过来的。另外我们还为售票端添加了登录界面等诸多功能。
图3.1 售票端工程结构
售票端的功能有“查询所有航班”、“查询特定航班”、“增加航班信息”、“更新航班信息”、“删除航班信息”等。因为售票端涉及对航班信息的增删改功能,权限较高,因此我们为它添加了一个登录界面,只有管理员输入正确的密码时才能进入售票界面。

3.1 登录界面设计

登录界面的制作首先需要增加一个QT设计类。相较于直接在代码中使用new QDialog语句创建窗口,这种直接添加文件的方式更加灵活,操作也更加简便。QT设计类可以很方便地生成制作登录界面所需的源文件、头文件和界面文件,选择添加新建文件中的Qt Designer Form Class,即可创建QT模板。如下图3.2所示。
图3.2 增加设计类
将类名改为Login,生成对应名称的头文件、源文件和UI界面文件。
图3.3 设置类名
首先设计一下UI界面,为其添加Label标题、LineEdit输入框、登录按钮等组件。使用水平布局和垂直布局限制界面组件的布局格式,将各个组件有秩序地摆放。设置Label标签的alignment属性,使之水平垂直居中;设置font属性设置字体的大小和字体格式。最终设计的登录界面样式如下图3.4所示。
图3.4 登录界面样式
在login的头文件中,需要声明按钮的槽函数,如下所示。

  public:
      explicit Login(QWidget *parent = nullptr);
      ~Login();
  private slots:
      void on_pushButton_clicked();

接下来设计登录界面的代码逻辑。当点击“登录”按钮后,需要进入按钮的槽函数进行事件处理。在槽函数中,通过判断管理员输入的密码是否正确,可以做进一步处理。具体代码如下所示。

  void Login::on_pushButton_clicked()
  {
      if((ui->lineEdit->text()=="qxk"&&ui->lineEdit_2->text()=="qixinkai")||\
          (ui->lineEdit->text()=="ss"&&ui->lineEdit_2->text()=="sunshuo")||\
          (ui->lineEdit->text()=="zhj"&&ui->lineEdit_2->text()=="zhanghoujin")){
          accept();
      }
      else{
          QMessageBox::warning(this,tr("warning"),tr("user name or password error!"),\
    QMessageBox::Yes);
      }
  }

在槽函数中,利用“逻辑或”判断如果用户输入的用户名和密码是否正确。代码中设置的三个用户名和密码分别是我们团队三个队员的姓名英文缩写。如果用户输入正确,会执行accept函数,否则的话会显示错误信息的提示框,警告用户名或密码输入错误。
接下来需要修改main.cpp文件中的内容,设置登录框的标题以及代码逻辑。如下面代码所示,只有当判断到QDialog::Accepted时,才显示会显示主界面。

MainWindow w;
Login d1;
d1.setWindowTitle("登录界面");
if(d1.exec()==QDialog::Accepted)
{
      w.show();
}

设置完成后,登录界面的逻辑代码就完成了。
接下来进行登录功能测试。运行售票端的QT程序,其效果如下图3.5所示。
图3.5登录界面效果
当输入错误的密码时,会有错误的提示性语句,如下图3.6所示。
图3.6 密码输入错误
只有当用户名和密码输入正确后,才能登录进售票界面。售票界面的初始化窗口如下图3.7所示。
图3.7 售票界面

3.2 售票界面UI设计

售票界面的功能函数需要引入C语言的代码,我们是直接将C语言的代码包含到了工程文件中。如下图3.8所示。
图3.8 源代码导入
其中的global.h文件、ticket.h文件是C语言的逻辑代码,login.hlogin.cpplogin.ui是登录界面的代码。因为它们与QT界面内容无关,所以在此部分就不再详细介绍了。
接下来介绍UI界面的设计过程。UI界面的设计是在mainwindow.ui文件中进行的,因为使用了QMainwindow类,所以可以很方便地在界面中添加菜单栏选项。首先根据售票端的功能,添加相应的菜单栏选项。售票端主要是有四个大的功能分类,分别为“客户端操作”、“机票查询”、“管理员操作”、“帮助”这四项。具体的功能见下表。

菜单列表 功能说明
连接服务器 售票端连接服务器
断开连接 售票端断开与服务器的连接
购买机票 售票端购买机票
退出程序 退出售票端程序
查询特定航班 查询指定的某个航班信息
查询所有航班 查询所有航班信息
增加航班信息 增加某个航班的信息
更新航班信息 更新某个航班的信息
删除航班信息 删除某个航班的信息
显示操作提示 显示功能说明窗口
团队信息 显示团队信息窗口

根据上表的功能分类,设计UI界面的菜单布局,如下图3.9所示。
图3.9 菜单选项
在每个菜单下,分别设置下拉菜单选项,如下图3.10至3.13所示。
图3.10 客户端操作菜单
图3.11 机票查询菜单
图3.12 管理员操作菜单
图3.13 帮助菜单
下拉菜单设计完成后,可以为其添加图片资源。在工程文件中添加QT资源类,如下图3.14所示。
图3.14添加QT资源
添加完成的图片资源如下图3.15所示。
图3.15 添加图片资源
首先需要在网上下载相关的图片资源,我是在阿里巴巴矢量图库里面找到的图片资源,将其下载并拷贝到VMware虚拟机中。如下图3.16所示。
图3.16 图片资源
接下来在编辑面板中添加图片资源段前缀和图片文件,如下图3.17所示。
图3.17 图片导入至工程
资源添加完毕后,工程代码中的结构如下图3.18所示。
图3.18 工程结构
接下来可以为每个下拉菜单的选项添加图片资源。在UI编辑界面的Action Editor窗口中,可以设置菜单选项的属性,如下图3.19所示。
图3.19 Action Editor窗口
以“连接服务器”选项为例,右键单击“连接服务器”选项,选择“Edit”设置其属性。点击Icon选项,设置其图片文件。
图3.20 设置动作
在弹出的资源菜单中,选择相应的图片文件并点击OK即可。
图3.21 选择图片资源
按照这样的流程,设置所有选项的图片样式,最终的效果如下图3.22所示。
图3.22 图片样式
其中的aciton_showaction_about选项,因为没有展示到工具栏上,所以暂时没有设置图片资源。
设置完图片资源后,可以将常用到的选项添加到工具栏中,便于用户操作。直接将Action Editor窗口中的常用部件拖动到工具栏中即可。因为大部分菜单选项已经设置了图片图标,所以当把选项拖动到工具栏后,相应的选项就变成了图标的形式,效果如下图3.23所示。
图3.23 工具栏效果
如果直接这样显示界面的工具栏,因为没有文字提示,可能用户不清楚每个工具选项的具体功能,所以需要为其添加文字说明。在构造函数中添加代码,设置工具栏的图片样式,如下代码所示。

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow){
    ui->setupUi(this);
    enable_button(isconnected);
    // 设置工具栏图标样式
    ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
}

最后一行设置了工具栏的图表样式,将样式设为Qt::ToolButtonTextUnderIcon属性,其效果就是在图标的下方添加相应的文字提示。
运行代码测试一下目前的效果,首先在登录界面输入正确的用户名和密码,弹出的售票界面如下图3.24所示。
图 3.24售票界面

3.3 工具栏按键使能

可以注意到,工具栏图标文件下方已经有了相应的文字说明。另外,连接服务器后,“连接服务器”按钮就变成了灰色。接下来介绍与此相关的按钮使能操作。
售票端拥有很多功能,但是这些功能都必须要与服务器进行通信才可以实现。也就是说,当售票端没有连接服务器时,很多功能是不可用的。为了明显地提示用户,说明该功能不可用,可以将按钮使能关闭,使按键暂时不可用。为实现此功能,可以编写一个按钮使能函数,控制按钮的使能。具体代码如下代码所示。

// 客户端操作
ui->action_connect->setEnabled(!boolean);
ui->action_disconnect->setEnabled(boolean);
ui->action_buyticket->setEnabled(boolean);
// 机票查询
ui->action_inquireone->setEnabled(boolean);
ui->action_inquireall->setEnabled(boolean);
// 管理员操作
ui->action_add->setEnabled(boolean);
ui->action_update->setEnabled(boolean);
ui->action_delete->setEnabled(boolean);

上述代码中,使用布尔类型的变量boolean配合serEnabled方法,实现了按钮的使能控制。需要注意到的是,“退出”按钮应该是一直保持使能状态,在任意时刻都是可用的,所以在代码中没有涉及“退出”按钮。“退出”按钮保持默认使能状态即可。
如果要改变按钮的使能状态,无非就只有两种情况,也就是在“连接”按钮和“断开连接”按钮被按下的时刻。因为只有这两个按钮可以改变售票端与服务器之间的连接状态。我们可以通过设置一个全局变量isconnected,在任意时刻获取与服务器之间的连接状态。
首先在启动售票端伊始,可以先将其余按钮失能,只让“连接”按钮使能。第二是在“连接”按钮点击并成功连接服务器后,让“连接”按钮失能,其余按钮使能。第三就是在“断开连接”按钮被按下后,让“连接”按钮使能,其余按钮失能。通过调用enable_button函数,就可以很方便地实现该功能。
下面进行按钮使能功能的测试。
当刚刚开启售票端时,按钮的使能状态如下图3.25所示。
图3.25 按键失能效果
点击“连接服务器”按钮后,按钮使能状态如下图3.26所示。
图3.26 按键使能效果
可以看到,按钮的使能功能是正常的。
至此,QT界面的基本功能就实现了,包括添加工具栏、设置工具栏图表样式、设置按钮使能功能等等。接下来需要实现具体的通信功能逻辑,实现售票端与服务器之间的通信过程。

3.4 数据输出函数

在介绍具体的槽函数编写之前,需要考虑文本显示的问题。C语言底层的逻辑代码输出信息时使用了printf语句,将文本信息直接输出到了标准输出流,也就是终端显示屏中。而QT中的输出是要输出到文本框组件中,这就需要进行数据的输出转换。编写相应的函数,如下代码所示。

  void MainWindow::display_info(QString msg){
      ui->textBrowser->append(msg);
  }

该函数比较简单,是直接调用append方法,将形参里的msg变量内容追加到了textBrowser文本框中。以后每当底层逻辑代码要输出信息时,直接调用该函数即可实现信息输出。

3.5 函数命名空间

通信逻辑代码的实现,也无非就是对QT界面按钮的槽函数进行编写。因为我们已经有了Linux C的底层通信逻辑代码,所以实现底层通信只需要将原有的C代码移植到QT对应的槽函数中即可。我们需要为所有的按钮都设置槽函数,有些按钮的槽函数内容涉及C语言的底层逻辑,其中的某些函数可能会与QT中的函数冲突,下面以“断开连接”按钮的槽函数为例,介绍函数冲突时的解决方法。其余的涉及C语言的内容就不再赘述了。
首先在UI编辑界面,找到Action Editor窗口,在该窗口中右键单击“断开连接”选项,选择Go to slot转到它的槽函数。
图3.27 转到槽函数

ui->textBrowser->append("Disonnect Server....\n");
char msg[512];
if(isconnected) {
	::close(socket_fd);
	sprintf(msg,"断开连接成功!\n");
	display_info(msg);
	isconnected=false;
}
enable_button(isconnected);

如上代码所示,在槽函数中要实现断开与服务器连接的逻辑功能,需要用到close函数。因为QT本身也有close函数,那是用来关闭当前窗口的,而我们使用的C语言中的close函数是用来关闭与服务器之间的套接字连接的,两个函数名有冲突时,可以在close函数前面加上两个冒号,改变当前函数的命名空间,声明要使用C语言中的close函数。这样,函数名冲突的问题就可以被解决。

3.6 信息交互界面

售票端的各种按钮,包括“连接服务器”、“断开连接”、“购买机票”、“退出”、“查询特定航班”、“查询所有航班”、“增加航班信息”、“更新航班信息”和“删除航班信息”这几个按钮,它们的槽函数编写都是移植的C语言代码,需要注意的是,像“购买机票”、“查询航班信息”这类的功能,都需要与用户进行交互,需要用户输入具体的航班信息等内容。在C语言中,我们直接在终端中输入即可,但在QT中,需要弹出相应的对话框,提示用户输入。下面以“增加航班信息”功能为例,介绍如何与用户实现信息交互。
管理员在点击“增加航班信息”按钮后,需要在弹窗中输入具体的航班信息。为此,我们需要为其新建一个QDialog对话框。关键代码如下代码所示。

QDialog dialog(this);
QFormLayout form(&dialog);
dialog.setWindowTitle("增加航班信息(管理员)");
QList<QLineEdit *> fields;
QLineEdit *id = new QLineEdit(&dialog);
form.addRow(new QLabel("请输入该航班的航班号:"));
form.addRow(id);
QLineEdit *number = new QLineEdit(&dialog);
form.addRow(new QLabel("请输入该航班的票数:"));
form.addRow(number);
QLineEdit *price = new QLineEdit(&dialog);
form.addRow(new QLabel("请输入该航班的票价:"));

这里使用了QDialog和QFormLayout的对象,分别创建了对话框和消息提示列表。使用connect连接信号与槽函数,当用户点击确定按钮时,获取用户在对话框中所输入的文本信息内容。
下面介绍一下“显示帮助信息”和“关于”按钮的槽函数编写过程。这两个按钮被按下后,都需要显示提示信息框,以“显示帮助信息”按钮为例,其代码如下代码所示。

QDialog dialog(this);
QFormLayout form(&dialog);
dialog.setWindowTitle("帮助信息");
form.addRow(new QLabel("<h1><center>功能说明</center></h1>"));
form.addRow(new QLabel("连接服务器: 与远程服务器建立连接"));
form.addRow(&buttonBox);
QObject::connect(&buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
if (dialog.exec() == QDialog::Accepted) {
	display_info("查询信息成功\n");
}

这里使用了QDialog对话框来显示弹出的提示信息,使用QFormLayout类的对象form,控制每条文本的格式。每条文本以QLabel的形式被创建,QLabel支持HTML格式,所以我这里使用了<h1>标签将文本放大,<center>标签将文本居中。每一条文本使用addRow方法添加到form对象中,最后使用buttonBox与form绑定,为界面添加按钮组件。使用connect连接信号与槽,用dialog.exec()的值判断用户是否点击确认按钮。用户点击确认后,在前端界面显示提示信息。
“关于”按钮也是相同的处理方式,其具体代码如下图所示。当用户点击“关于”按钮时,同样会显示提示信息框。其中包括了程序的名称、版本号以及团队信息和更新日期。用户点击确认按钮后,会在前端界面显示提示信息。

QDialog dialog(this);
QFormLayout form(&dialog);
dialog.setWindowTitle("关于");
form.addRow(new QLabel("<h1>网络售票模拟系统管理端</h1>"));
form.addRow(new QLabel("<center>版本 V0.3</center>"));
form.addRow(new QLabel("本程序仅用于测试,请勿用于商业目的"));
form.addRow(new QLabel("作者信息: 孙硕、戚莘凯、张厚今"));
form.addRow(new QLabel("更新日期: 2020年06月17日"));
QDialogButtonBox buttonBox(QDialogButtonBox::Ok, &dialog);
form.addRow(&buttonBox);
QObject::connect(&buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
if (dialog.exec() == QDialog::Accepted) {
	display_info("查询信息成功\n");
}

4 实现的效果

代码编写完成后,运行售票端进行测试。下面仅介绍售票端实现的效果,服务端和购票端效果请查看第七章附录中的测试视频链接。

4.1 登录界面及初始化

启动售票端后,首先会出现管理员的登录界面,如下图4.1所示。
图4.1 登录界面
如果输入错误的用户名或密码,则登录失败,售票端弹出提示信息。
图4.2 提示信息
如果用户名和密码输入正确,则窗口跳转到售票界面。售票界面初始化时只能点击“连接服务器”和“退出”按钮。其界面如下图4.3所示。
图4.3 售票界面初始化
此时需要启动服务端程序,开启服务器才能在售票端成功连接服务器。
图4.4 售票界面连接服务器

4.2 查询特定航班

用户点击“查询特定航班”按钮,会弹出提示窗口,要求用户输入待查寻的航班号信息,如下图4.5所示。用户在弹出的提示窗口中输入航班号,点击OK按钮即可进行航班查询。如果用户点击Cancel取消按钮,则取消当前的查询功能,返回到程序主界面。
图4.5 输入航班号
当该航班存在,即用户输入的航班信息无误时,售票端会显示航班信息。
图4.6 显示航班信息
当用户输入的航班号有误时,售票端显示警告信息。
图4.7 显示警告信息

4.3 查询所有航班

点击“查询所有航班”按钮可以查询所有存在的航班信息。如下图4.8所示。当航班信息较多时,窗口会自动显示滚动条。可以通过拖动滚动条查看所有航班的具体信息。
图4.8 查询航班结果

4.4 增加航班信息

点击“增加航班信息”按钮,会弹出提示窗口,要求管理员输入待增加的航班信息。当管理员输入有效的航班信息时,点击OK即可确认提交。当管理员点击取消按钮,可以取消当前的操作。
图4.9 增加航班信息
现测试增加第21号航班,票数为300,票价为450,点击OK确认提交信息。此时售票端会显示增加航班信息成功的提示信息。效果如下图4.10所示。
图4.10 增加航班提示
再次查询所有航班信息,可以看到第21号航班的信息已经被添加进来了。
图4.11 查询航班信息

4.5 更新航班信息

管理员点击“更新航班信息”按钮后,同样会弹出提示窗口,要求管理员输入待更新的航班信息,如下图4.12所示。当管理员输入有效的航班信息时,点击OK即可确认提交。当管理员点击取消按钮,可以取消当前的操作。
图4.12 更新航班信息
测试更新一下第20号航班,将票数改为123,票价改为8888,点击OK提交。再次查询所有航班信息,可以看到第20号航班的信息已经被更新,如下图4.13所示。
图4.13 查询航班信息

4.6 删除航班信息

管理员点击“删除航班信息”按钮,会弹出提示框,要求管理员填写待删除的航班号。如下图4.14所示。当管理员输入有效的航班号时,点击OK即可确认提交,删除对应的航班信息。当管理员点击取消按钮,可以取消当前的操作。
图4.14 删除航班信息
我们测试输入第21号航班,再次查询所有航班,航班信息如下图4.15 所示。
图4.15 查询航班信息
可以看到,现在第21号航班的信息已经被删除了。

4.7 帮助信息和退出程序

点击菜单栏的帮助信息按钮,在下拉菜单中找到“显示内容”选项,可以看到程序功能说明,如下图4.16所示。
图4.16 显示功能说明
在“帮助”的下拉菜单中,找到“关于”选项,可以找到程序的版本信息和我们团队的信息,如下图4.17所示。
图4.17 显示团队信息
点击“断开连接”按钮,可以断开售票端与服务器直接的连接。此时售票端界面显示提示信息,同时按钮使能发挥作用,只有“连接服务器”和“退出”按钮使能,其余按钮变为失能状态。
图4.18 断开连接
点击工具栏最右侧的“退出”按钮,可以退出当前窗口。

5 实训中遇到的问题及解决方案

实训中遇到了很多问题,大部分都是细枝末节的小问题。通过团队之间的讨论,都已经被解决了。其中我印象较深的是有关数据库的一个问题。我们团队制作的航班售票系统,其航班数据是被存储在了MySQL数据库中。在纯Linux C的环境下编译代码时,直接使用MySQL的链接库编译代码即可,如下图5.1所示。
图5.1 终端代码编译
但是将Linux代码移植到QT环境中之后,编译QT工程时总是报错,说是MySQL数据库的头文件<mysql/mysql.h>不存在。后来经过查阅资料和团队讨论才明白,需要在QT的配置文件中添加上MySQL的链接库才行,如下图5.2所示。
图5.2 添加QT依赖库
添加上链接库后,再编译代码就不会报错了。在QT for Linux中编译程序,就没有问题了,如下图5.3所示。
图5.3 编译成功

6 实训总结

本次实训制作了一个简易的航班购票模拟系统,使用Linux C编写后台程序,由QT for Linux软件编写前端界面,实现了后台功能与前端界面的完美结合。在QT编程方面,由我负责制作的售票端模块涉及到了登录界面设计、图片资源引入、菜单及工具栏管理、按键使能、函数命名空间转换、交互界面设计等多方面的工作,涉及到很多日常学习中经常遇到的知识点。
通过本次实训学习,不仅使我对QT的基础操作有了更深入的理解,也为我在编写图形界面的技术思路上积累了宝贵的经验。日后的项目开发必定离不开图形界面编程,本次实训使我真正感受到了图形界面编程的强大。
在团队合作方面,本次实训也使我受益匪浅。我们团队三人分工合作,项目进行地有条不紊,整个项目实施过程也非常顺利。实训前期时我们的项目整体进展比较缓慢,后期加快了项目进度,在规定时间内顺利完成了实训。遇到问题时我们会及时沟通、积极讨论,从而产生了很多有价值的新思路和新创意,像“客户端的欢迎页面”、“售票端的注册页面”、“航班信息采用数据库存储”等等技术思路,我觉得这些都是非常有价值的团队成果。感谢团队成员孙硕和戚莘凯为项目做出的宝贵贡献,相信在以后的学习和生活中,本次实训一定会对我们产生积极的影响。

7 附录

7.1 测试视频

测试视频是对整个航班购票模拟系统的测试,包括服务端、售票端和购票端的测试过程。视频已上传至哔哩哔哩弹幕网。
视频链接:https://www.bilibili.com/video/BV14T4y1J7bt/

7.2 实验代码

实验代码已经上传到了Gitee仓库,包含了服务端、售票端和购票端的QT工程代码以及Linux C底层代码。

Gitee 仓库地址(推荐): https://gitee.com/zhj0125/TicketingSystem
GitHub 仓库地址: https://github.com/ZHJ0125/TicketingSystem
该代码是在《LINUX C编程从入门到精通》(刘学勇编著 ISBN:978-7-121-17415-5)书中例题的基础上修改的。如果您对代码有任何疑问或修改意见,欢迎提出issue 或者直接私信我们,谢谢。

分类:

技术点:

相关文章: