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> 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实现做一个简单介绍,首先在构造函数中,我们先对窗口进行清理,重新将数据库的数据填充进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;
    }
}

你可能感兴趣的:(QT入门:学生管理系统)