QT入门:学生管理系统

实习期间由于公司需要,对QT进行了学习,写了一个简易版的学生管理系统,在这里简单的记录一下,项目的源代码可以查看https://github.com/Kaiko1/StudentManagementSystem

QT环境配置

1、首先安装QTCreator:https://www.qt.io/product/development-tools
2、QT下载:https://www.qt.io/download

我选择的版本

3、一些配置
设置QT Versions

设置编译器

系统的功能

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
Major

实现MainWindow

MainWindow提供了7个button,左边4个button的功能分别是增删查改(功能1),分别对应了代码里的on_insert_pushButton_clicked ...4个函数,其实现就是show出每个功能对应的窗口,下文会列举一个做介绍。右边3个button的功能分别为Undo,Redo(功能5)和打开专业信息的页面(功能3)。


MainWindow Ui

在看代码之前我先对代码结构和关键函数做一些解释:
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<std::vector<QString>> log; //用于实现undo与redo功能的二维vector
    static int logIndex;
    static bool sortAscOrDesc; //记录当前顺序为升序还是降序
};
#endif // MAINWINDOW_H

mainwindow.cpp

std::vector<std::vector<QString>> MainWindow::log = std::vector<std::vector<QString>>();
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<QString> 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实现做一个简单介绍,首先在构造函数中,我们先对窗口进行清理,重新将数据库的数据填充进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);
    }
Undo/Redo Log

排序功能

在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;
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容