实习期间由于公司需要,对QT进行了学习,写了一个简易版的学生管理系统,在这里简单的记录一下,项目的源代码可以查看https://github.com/Kaiko1/StudentManagementSystem
QT环境配置
1、首先安装QTCreator:https://www.qt.io/product/development-tools
2、QT下载:https://www.qt.io/download
3、一些配置
系统的功能
1、主页面的表格显示所有学生信息,包括学号、姓名、性别、专业、专业对应必修课等,学号唯一。
2、提供对学生进行增删改的功能,性别和专业采用下拉框选择。必修课不能被修改,随专业保持一致。
3、提供页面查询所有专业信息,包括专业名、必修课。可对专业增删改,但不能删除已被学生选中专业。
4、提供点击表头按列排序的功能。
5、实现Undo和Redo,可撤销或重做已有操作。
如何实现
建立数据库
这里选择了MySQL,为学生信息与专业信息分别建立表。
项目结构
由于功能3——显示专业信息并实现增删查改类似于一个独立的模块,所以我们可以使用QT中的pri文件来对major相关的代码进行管理,是QT项目管理上非常方便的一个功能。
Major.pri
SOURCES += \
$$PWD/majordialog.cpp \
$$PWD/majorinsertdialog.cpp \
$$PWD/majordeletedialog.cpp \
$$PWD/majorupdatedialog.cpp
HEADERS += \
$$PWD/majordialog.h \
$$PWD/majorinsertdialog.h \
$$PWD/majordeletedialog.h \
$$PWD/majorupdatedialog.h
FORMS += \
$$PWD/majordialog.ui \
$$PWD/majorinsertdialog.ui \
$$PWD/majordeletedialog.ui \
$$PWD/majorupdatedialog.ui
实现MainWindow
MainWindow提供了7个button,左边4个button的功能分别是增删查改(功能1),分别对应了代码里的on_insert_pushButton_clicked ...4个函数,其实现就是show出每个功能对应的窗口,下文会列举一个做介绍。右边3个button的功能分别为Undo,Redo(功能5)和打开专业信息的页面(功能3)。
在看代码之前我先对代码结构和关键函数做一些解释:
MainWindow构造函数:包含了初始化Ui、在列头显示排序的上下箭头、连接数据库、刷新表格(读取数据库的学生数据)、用connect函数接收来自其他表格的signal以及点击表头进行排序的signal。
refresh():刷新表格和undo/redo的Buttion状态。刷新表格将会清空表格并将当前数据库表的数据读出来,保证数据最新。undo/redo将在后面进行介绍。
connectDatabase():连接数据库。
mainwindow.h
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
static void addLogItem(QString redoSql, QString undoSql);
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void on_insert_pushButton_clicked();
... // 省略其他Button
void refresh();
void refreshTable();
void refreshDoButtonStatus(); //根据Log来刷新Undo与Redo按钮的状态
void sortColumn(int col);
void connectDatabase();
private:
Ui::MainWindow *ui;
QSqlDatabase db;
InsertDialog student_insert;
... //省略其他6个Dialog
static std::vector> log; //用于实现undo与redo功能的二维vector
static int logIndex;
static bool sortAscOrDesc; //记录当前顺序为升序还是降序
};
#endif // MAINWINDOW_H
mainwindow.cpp
std::vector> MainWindow::log = std::vector>();
int MainWindow::logIndex = 0;
bool MainWindow::sortAscOrDesc = false;
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->student_tableWidget->horizontalHeader()->setSortIndicatorShown(true);
connectDatabase();
refresh();
connect(&student_insert, SIGNAL(refreshMainWindow()), this, SLOT(refresh()));
... // 省略student_delete, student_update, student_major
connect(ui->student_tableWidget->horizontalHeader(), SIGNAL(sectionClicked(int)), this, SLOT(sortColumn(int)));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_insert_pushButton_clicked()
{
student_insert.initUi();
student_insert.show();
}
void MainWindow::on_delete_pushButton_clicked()
{ ... }
void MainWindow::on_search_pushButton_clicked()
{ ... }
void MainWindow::on_update_pushButton_clicked()
{ ... }
void MainWindow::on_major_info_pushButton_clicked()
{ ... }
void MainWindow::on_undo_pushButton_clicked()
{
QSqlQuery query;
MainWindow::logIndex--;
QString undoSql = MainWindow::log[MainWindow::logIndex][1];
query.exec(undoSql);
if(query.numRowsAffected() > 0)
{
QMessageBox::information(this, "Note", "Undo successfully");
MainWindow::refresh();
}
else
{
QMessageBox::information(this, "Note", "Undo failed");
}
}
void MainWindow::on_redo_pushButton_clicked()
{
QSqlQuery query;
QString redoSql = MainWindow::log[MainWindow::logIndex][0];
MainWindow::logIndex++;
query.exec(redoSql);
if(query.numRowsAffected() > 0)
{
QMessageBox::information(this, "Note", "Redo successfully");
MainWindow::refresh();
}
else
{
QMessageBox::information(this, "Note", "Redo failed");
}
}
void MainWindow::refresh()
{
refreshTable();
refreshDoButtonStatus();
}
void MainWindow::refreshTable()
{
ui->student_tableWidget->clearContents();
QSqlQuery query;
query.exec("SELECT s.id, s.name, s.gender, m.name major, m.course FROM students s, major m WHERE s.major_id = m.id;");
int row = 0;
while(query.next())
{
ui->student_tableWidget->setRowCount(row + 1);
for (int i = 0; i < 5; i++)
{
ui->student_tableWidget->setItem(row, i, new QTableWidgetItem(query.value(i).toString()));
}
row++;
}
}
void MainWindow::refreshDoButtonStatus()
{
// refresh undo button
if(MainWindow::log.size() == 0 || MainWindow::logIndex == 0)
{
ui->undo_pushButton->setEnabled(false);
}
else
{
ui->undo_pushButton->setEnabled(true);
}
// refresh redo button
if(MainWindow::log.size() == 0 || MainWindow::logIndex == MainWindow::log.size())
{
ui->redo_pushButton->setEnabled(false);
}
else
{
ui->redo_pushButton->setEnabled(true);
}
}
void MainWindow::sortColumn(int col)
{
if(sortAscOrDesc)
{
ui->student_tableWidget->sortItems(col, Qt::AscendingOrder);
sortAscOrDesc = false;
}
else
{
ui->student_tableWidget->sortItems(col, Qt::DescendingOrder);
sortAscOrDesc = true;
}
}
void MainWindow::connectDatabase()
{
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");
db.setHostName("localhost");
db.setPort(3306);
db.setDatabaseName("std_mgs_sys");
db.setUserName("root");
db.setPassword("123456");
if(db.open())
{
qDebug()<<"\ropen database successfully";
}
}
void MainWindow::addLogItem(QString redoSql, QString undoSql)
{
std::vector item;
item.push_back(redoSql);
item.push_back(undoSql);
MainWindow::log.push_back(item);
MainWindow::logIndex++;
}
Insert界面实现
因为比较类似,在这里我从增删查改中选择一个进行介绍。从MainWindow点击Insert button后,会跳出插入数据的窗口。输入数据点击Ok即可向数据库插入数据,同时发送Signal给MainWindow,通知MainWindow刷新其页面,保证新的学生信息能够显示。
这里对Insert实现做一个简单介绍,首先在构造函数中,我们先对窗口进行清理,重新将数据库的数据填充进Insert窗口(下拉窗口需要可选数据)。在ok按钮的实现中,先判定输入数据是否齐全,否则报错,不允许插入,接着判断ID的合理性(8位),最后才是执行sql,插入数据,同时将对应的反向操作与当前操作组成数组加入到全局的log中(为了实现undo和redo)。
insertdialog.h
namespace Ui {
class InsertDialog;
}
class InsertDialog : public QDialog
{
Q_OBJECT
public:
explicit InsertDialog();
~InsertDialog();
void initUi();
private slots:
void on_ok_pushButton_clicked();
void on_cancel_pushButton_clicked();
signals:
void refreshMainWindow();
private:
Ui::InsertDialog *ui;
QTimer *tabletime;
};
#endif // INSERTDIALOG_H
insertdialog.cpp
InsertDialog::InsertDialog() :
ui(new Ui::InsertDialog)
{
ui->setupUi(this);
}
InsertDialog::~InsertDialog()
{
delete ui;
}
void InsertDialog::initUi()
{
// clean window
ui->id_lineEdit->clear();
ui->name_lineEdit->clear();
ui->gender_comboBox->clear();
ui->major_comboBox->clear();
// fill combo box
QSqlQuery query;
query.exec(QString("SELECT name FROM major"));
while(query.next()){
ui->major_comboBox->addItem(QString(query.value(0).toString()));
}
ui->gender_comboBox->addItem(QString("male"));
ui->gender_comboBox->addItem(QString("female"));
}
void InsertDialog::on_ok_pushButton_clicked()
{
if(ui->id_lineEdit->text().isEmpty() || ui->name_lineEdit->text().isEmpty() ||
ui->gender_comboBox->currentText().isEmpty() || ui->major_comboBox->currentText().isEmpty())
{
QMessageBox::information(this, "Note", "Please input the student's complete information");
}
else
{
QSqlQuery query;
QString id = ui->id_lineEdit->text(), name = ui->name_lineEdit->text(),
gender = ui->gender_comboBox->currentText(), major = ui->major_comboBox->currentText();
if(!verifyID(id))
{
QMessageBox::information(this, "Note", "Student ID is invalid");
return;
}
// insert data
QString sql = QString("INSERT INTO students (id, name, gender, major_id)VALUES(\"%1\",\"%2\",\"%3\",(SELECT id FROM major WHERE name = \"%4\"))").arg(id, name, gender, major);
query.exec(sql);
if(query.numRowsAffected() > 0)
{
QString undoSql = QString("DELETE FROM students WHERE id = %1").arg(id);
MainWindow::addLogItem(sql, undoSql);
QMessageBox::information(this, "success", "Added successfully");
}
else
{
QMessageBox::information(this, "fail", "Add failed");
}
emit InsertDialog::refreshMainWindow();
}
}
void InsertDialog::on_cancel_pushButton_clicked()
{
this->close();
}
Undo与Redo的实现
用了最简单的实现方式,在增删查改操作的同时,将当前执行的sql与对应的反向操作的sql进行组合,并装入一个全局的log数组中,并由一个全局的logIndex指针来觉得Undo与Redo的位置。最初启动程序时,Undo与Redo按钮都不能被点击,直到完成第一次操作(如Insert)时,发送Signal通知MainWindow刷新Undo与Redo按钮。
// 刷新Undo/Redo Button
// refresh undo button
if(MainWindow::log.size() == 0 || MainWindow::logIndex == 0)
{
ui->undo_pushButton->setEnabled(false);
}
else
{
ui->undo_pushButton->setEnabled(true);
}
// refresh redo button
if(MainWindow::log.size() == 0 || MainWindow::logIndex == MainWindow::log.size())
{
ui->redo_pushButton->setEnabled(false);
}
else
{
ui->redo_pushButton->setEnabled(true);
}
排序功能
在MainWindow的构造函数中创建连接sortColumn的SLOT,只要用户有点击列头的操作,激活sortColumn进行排序,Qt有自带的排序函数,让列按照ASCII进行排序。
connect(ui->student_tableWidget->horizontalHeader(), SIGNAL(sectionClicked(int)), this, SLOT(sortColumn(int)));
void MainWindow::sortColumn(int col)
{
if(sortAscOrDesc)
{
ui->student_tableWidget->sortItems(col, Qt::AscendingOrder);
sortAscOrDesc = false;
}
else
{
ui->student_tableWidget->sortItems(col, Qt::DescendingOrder);
sortAscOrDesc = true;
}
}