本博主只关注实战系列,Qt的基本语法课参考其他博主的Qt教程
1:本章实战教程会实现一个图书信息管理的功能,其中包含分页查询、增删改查等功能。基于上一节创建的MysqlTest项目进行开发,图书信息管理项目基于visual studio 2022、Qt 5.14.2、,mysql 8.0.29进行开发,由于Qt本身不自带mysql驱动,需自行编译,具体操作请参考上一节【Qt实战教程第1篇 编译mysql数据库驱动–MSVC2017版本】
2:本章内容涉及知识点:
QTableView、QSqlTableModel、自定义委托类(delegate)、mysql基本语法等
3:本章内容只实现功能,函数析构时某些指针内存未被释放,可自行添加指针释放操作
4:项目源码下载
目录
一、图书信息管理项目运行结果
二、数据准备
1:数据库:在test数据库中创建图书信息表book_info。
CREATE TABLE `book_info` (
`id` int NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '图书名称',
`author` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '作者',
`type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '类型',
`publish` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '出版社',
`photo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '封面图片',
`intime` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
2:创建项目目录结构
创建BookInfo的相关文件,先创建空文件,下一步我们依次填充文件内容。最终的目录结构如下所示:
三、源码+注释
1:图书信息类
BookInfo.h文件
#pragma once
#include <QtWidgets/qwidget.h>
#include "ui_BookInfo.h"
#include "qsqlerror.h"
#include "qmessagebox.h"
#include "qsqlrecord.h"
#include "qdebug.h"
#include "qmath.h"
#include "BookQSqlTableModel.h"
#include "qdatetime.h"
#include "OperateColumnDelegate.h"
#include "BookInfoDialog.h"
#include "CommonDefine.h"
#include "qsqlrecord.h"
class BookInfo : public QWidget
{
Q_OBJECT
public:
BookInfo(QWidget* parent = Q_NULLPTR);
~BookInfo();
void initPagnition(int totalRowCount); // 初始化分页
void createSignalPageBtn(int page); // 创建单个的分页按钮
public slots:
void slotHandleSearch(); // 搜索
void slotHandleSave(); // 保存
void slotHandleBatchDelete(); // 批量删除
void slotPagnition(QPushButton* button, int currPage); // 分页跳转
void slotPageSizeChanged(int pageSize); // 每页显示数量
void slotSearchChanged(const QString& text); // 搜索框
void slotOperateEvent(QString text, QModelIndex index); // 操作列按钮点击事件
void slotSaveEvent(); // 新增按钮
void slotDialogOperate(BookInfoEntity* bookInfo, QModelIndex index); // 弹窗操作
private:
Ui::BookInfoClass ui;
BookQSqlTableModel* theModel; // 自定义QSqlTableModel
int m_nCurrPage; // 当前页
int m_nPagesToShow; // 显示的页码数量
QMap<int, QPushButton*> m_pPageMap; // 保存的页码列表指针
OperateColumnDelegate* m_ColumnDelegate; // 操作列的委托类
};
BookInfo.cpp
#include "BookInfo.h"
BookInfo::BookInfo(QWidget* parent)
: QWidget(parent)
{
ui.setupUi(this);
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");
db.setHostName("localhost");
db.setDatabaseName("test");
db.setUserName("root");
db.setPassword("root");
if (!db.open())
{
QString sqlError = db.lastError().text();
QString text = QString("%1\n%2").arg(QString::fromLocal8Bit("数据库连接失败!")).arg(sqlError);
QMessageBox::critical(this, QStringLiteral("提示"), text, QMessageBox::Yes);
return;
}
m_nCurrPage = 1; // 默认是第1页
m_nPagesToShow = 5; // 默认显示的页码数量
ui.comboBox->insertItem(0, QStringLiteral("20条/页"), 20);
ui.comboBox->insertItem(1, QStringLiteral("30条/页"), 30);
ui.comboBox->insertItem(2, QStringLiteral("50条/页"), 50);
ui.comboBox->insertItem(3, QStringLiteral("100条/页"), 100);
ui.comboBox->setCurrentIndex(2);
connect(ui.comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPageSizeChanged(int))); // 每页数量选择
connect(ui.lineEdit_search, &QLineEdit::textEdited, this, &BookInfo::slotSearchChanged); // 搜索框--文本更改
theModel = new BookQSqlTableModel(this, db);
theModel->setTable("book_info"); // 设置需要查询的数据库表名
theModel->setSort(theModel->fieldIndex("id"), Qt::DescendingOrder); // 设置排序字段及排序方式,此处为降序
theModel->selectPination(m_nCurrPage, ui.comboBox->currentData().toInt()); // 使用自定义的分页查询方法
// 设置QTableView表格的表头
theModel->setHeaderData(theModel->fieldIndex("name"), Qt::Horizontal, QStringLiteral("图书名称"));
theModel->setHeaderData(theModel->fieldIndex("author"), Qt::Horizontal, QStringLiteral("作者"));
theModel->setHeaderData(theModel->fieldIndex("type"), Qt::Horizontal, QStringLiteral("类型"));
theModel->setHeaderData(theModel->fieldIndex("publish"), Qt::Horizontal, QStringLiteral("出版社"));
theModel->setHeaderData(theModel->fieldIndex("photo"), Qt::Horizontal, QStringLiteral("封面"));
theModel->setHeaderData(theModel->fieldIndex("intime"), Qt::Horizontal, QStringLiteral("创建时间"));
ui.tableView->setModel(theModel); // 把数据模型添加到QTableView中
// 自定义委托类(代理),实现操作列按钮添加
m_ColumnDelegate = new OperateColumnDelegate(QStringList() << QStringLiteral("编辑") << QStringLiteral("删除"));
connect(m_ColumnDelegate, &OperateColumnDelegate::clicked, this, &BookInfo::slotOperateEvent); // 按钮点击信号槽
ui.tableView->setItemDelegateForColumn(theModel->getColumnCount(), m_ColumnDelegate); // 给最后一列添加代理
ui.tableView->setAlternatingRowColors(true); // 隔行颜色显示
ui.tableView->setEditTriggers(QAbstractItemView::NoEditTriggers); // 不允许编辑
ui.tableView->setColumnHidden(theModel->fieldIndex("id"), true); // 隐藏id列
ui.tableView->setColumnWidth(theModel->fieldIndex("name"), 260);
ui.tableView->setColumnWidth(theModel->fieldIndex("author"), 200);
ui.tableView->setColumnWidth(theModel->fieldIndex("type"), 200);
ui.tableView->setColumnWidth(theModel->fieldIndex("publish"), 260);
ui.tableView->setColumnWidth(theModel->fieldIndex("intime"), 200);
ui.tableView->setColumnWidth(theModel->getColumnCount(), 100); // 操作列
ui.tableView->horizontalHeader()->setSectionResizeMode(theModel->fieldIndex("photo"), QHeaderView::Stretch);
ui.tableView->setSelectionBehavior(QAbstractItemView::SelectRows); // 设置选中时为整行选中
ui.tableView->setSelectionMode(QAbstractItemView::MultiSelection);
connect(ui.btnSearch, SIGNAL(clicked()), this, SLOT(slotHandleSearch())); // 搜索操作
connect(ui.btnSave, SIGNAL(clicked()), this, SLOT(slotHandleSave())); // 新增操作
connect(ui.btnBatchDelete, SIGNAL(clicked()), this, SLOT(slotHandleBatchDelete())); //批量删除操作
int totalRowCount = theModel->totalRowCount(); // 获取数据总行数
ui.label_totalNum->setText(QString::fromLocal8Bit("共") + QString::number(totalRowCount) + QString::fromLocal8Bit("条"));
connect(ui.btnPrevPage, &QPushButton::clicked, this, [=]() {slotPagnition(ui.btnPrevPage, m_nCurrPage - 1); });
connect(ui.btnNextPage, &QPushButton::clicked, this, [=]() {slotPagnition(ui.btnNextPage, m_nCurrPage + 1); });
initPagnition(totalRowCount); // 初始化分页
}
BookInfo::~BookInfo()
{
QMap<int, QPushButton*>::iterator iteorPage = m_pPageMap.begin();
while (iteorPage != m_pPageMap.end())
{
QPushButton* button = iteorPage.value();
ui.PageBtnHorizontalLayout->removeWidget(button);
delete button; // 释放按钮指针
button = Q_NULLPTR;
iteorPage = m_pPageMap.erase(iteorPage);
}
}
void BookInfo::initPagnition(int totalRowCount)
{
// 清除之前的页码列表
QMap<int, QPushButton*>::iterator iteorPage = m_pPageMap.begin();
while (iteorPage != m_pPageMap.end())
{
QPushButton* button = iteorPage.value();
ui.PageBtnHorizontalLayout->removeWidget(button);
delete button; // 释放按钮指针
button = Q_NULLPTR;
iteorPage = m_pPageMap.erase(iteorPage);
}
// 计算页码,最多显示5页,不足5页显示全部
int lastPage = qCeil(totalRowCount * 1.0 / ui.comboBox->currentData().toInt());
if (m_nCurrPage == 1)
{
ui.btnPrevPage->setEnabled(false); // 默认上一页按钮不可点击
}
if (lastPage == 1)
{
ui.btnNextPage->setEnabled(false); // 默认下一页按钮不可点击
}
int startRangePage = qMax(1, m_nCurrPage - m_nPagesToShow / 2);
int endRangePage = qMin(lastPage, startRangePage + m_nPagesToShow - 1);
int firstPage = 1;
// 加载首页
if (firstPage == lastPage) // 只有一页的时候只显示一个按钮
{
createSignalPageBtn(firstPage);
return;
}
createSignalPageBtn(firstPage);
if (m_nCurrPage - (m_nPagesToShow / 2) - 1 > firstPage)
{
// 展示前面跳转的页码
createSignalPageBtn(INT_MIN);
}
for (int i = startRangePage; i <= endRangePage; i++) {
if (m_nCurrPage <= m_nPagesToShow && i == 1) {
continue;
}
if (m_nCurrPage >= (lastPage - m_nPagesToShow) && i == lastPage) {
continue;
}
createSignalPageBtn(i);
}
if (m_nCurrPage + (m_nPagesToShow / 2) + 1 < lastPage)
{
// 展示前面跳转的页码
createSignalPageBtn(INT_MAX);
}
// 加载尾页
createSignalPageBtn(lastPage);
}
void BookInfo::createSignalPageBtn(int page)
{
QString btnText = QString::number(page);
if (page == INT_MIN)
{
btnText = "<<";
}
if (page == INT_MAX)
{
btnText = ">>";
}
QPushButton* button = new QPushButton(this);
if (page == m_nCurrPage)
{
button->setStyleSheet("QPushButton{background-color: #5eaef1; color: #000;}");
}
button->setFixedSize(20, 20);
button->setText(btnText);
ui.PageBtnHorizontalLayout->addWidget(button);
connect(button, &QPushButton::clicked, this, [=]() {slotPagnition(button, page); }); // 点击分页按钮触发slotPagnition时间
m_pPageMap.insert(page, button);
}
void BookInfo::slotHandleSave()
{
BookInfoDialog dlg(FORM::SAVE);
connect(&dlg, &BookInfoDialog::update, this, &BookInfo::slotDialogOperate);
dlg.exec();
}
void BookInfo::slotHandleBatchDelete()
{
// 获取选中的索引列表
QItemSelectionModel* selectionModel = ui.tableView->selectionModel();
QModelIndexList selectedIndexes = selectionModel->selectedIndexes(); // 这里获得的是所有格子的总index数量
QMessageBox confirm;
confirm.setWindowTitle(QStringLiteral("提示"));
confirm.setText(QString("%1%2%3").arg(QStringLiteral("是否确认删除当前这")).arg(selectedIndexes.count() / 7).arg(QStringLiteral("条数据?")));
confirm.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
confirm.button(QMessageBox::Yes)->setText(QStringLiteral("确定"));
confirm.button(QMessageBox::No)->setText(QStringLiteral("取消"));
int ret = confirm.exec();
if (ret == QMessageBox::No) return;
// 遍历选中的索引,并处理每一行
for each (const QModelIndex & index in selectedIndexes) {
if (index.isValid()) {
theModel->removeRow(index.row());
}
}
if (theModel->submitAll())
{
m_nCurrPage = 1;
ui.comboBox->setCurrentIndex(2);
int pageSize = ui.comboBox->itemData(2).toInt();
theModel->selectPination(m_nCurrPage, pageSize);
int totalRowCount = theModel->totalRowCount();
ui.label_totalNum->setText(QString::fromLocal8Bit("共") + QString::number(totalRowCount) + QString::fromLocal8Bit("条"));
initPagnition(totalRowCount);
QMessageBox::information(this, QStringLiteral("提示"),
QString("%1%2%3").arg(QStringLiteral("共删除")).arg(selectedIndexes.count() / 7).arg(QStringLiteral("条数据"))); // 共7列
}
}
void BookInfo::slotPagnition(QPushButton* button, int currPage)
{
if (currPage == m_nCurrPage) return;
int lastPage = qCeil(theModel->totalRowCount() * 1.0 / ui.comboBox->currentData().toInt());
if (currPage == INT_MIN)
{
currPage = m_nCurrPage;
currPage = qMax(1, currPage - m_nPagesToShow);
}
if (currPage == INT_MAX)
{
currPage = m_nCurrPage;
currPage = qMin(lastPage, currPage + m_nPagesToShow);
}
m_nCurrPage = currPage;
theModel->selectPination(currPage, ui.comboBox->currentData().toInt());
// 刷新按钮状态
if (m_nCurrPage == 1)
{
ui.btnPrevPage->setEnabled(false);
}
else
{
ui.btnPrevPage->setEnabled(true);
}
if (m_nCurrPage == lastPage)
{
ui.btnNextPage->setEnabled(false);
}
else
{
ui.btnNextPage->setEnabled(true);
}
// 刷新列表
initPagnition(theModel->totalRowCount());
}
void BookInfo::slotPageSizeChanged(int index)
{
ui.comboBox->setCurrentIndex(index);
int pageSize = ui.comboBox->itemData(index).toInt();
m_nCurrPage = 1;
theModel->selectPination(m_nCurrPage, pageSize);
initPagnition(theModel->totalRowCount()); // 初始化分页
}
void BookInfo::slotSearchChanged(const QString& text)
{
theModel->setFilter(""); // 会自动执行sql查询,从而刷新模型
if (!text.isEmpty())
{
theModel->setFilter(" name like '%" + text + "%' or author like '%" + text + "%' or publish like '%" + text + "%'");
}
m_nCurrPage = 1;
ui.comboBox->setCurrentIndex(2);
int pageSize = ui.comboBox->itemData(2).toInt();
int totalRowCount = theModel->totalRowCount();
ui.label_totalNum->setText(QString::fromLocal8Bit("共") + QString::number(totalRowCount) + QString::fromLocal8Bit("条"));
initPagnition(totalRowCount);
}
void BookInfo::slotOperateEvent(QString text, QModelIndex index)
{
ui.tableView->selectRow(index.row());
if (text == QString::fromLocal8Bit("编辑"))
{
QSqlRecord record = theModel->record(index.row());
BookInfoEntity* bookInfo = new BookInfoEntity();
bookInfo->setId(record.value("id").toInt());
bookInfo->setName(record.value("name").toString());
bookInfo->setAuthor(record.value("author").toString());
bookInfo->setType(record.value("type").toString());
bookInfo->setPublish(record.value("publish").toString());
bookInfo->setPhoto(record.value("photo").toString());
BookInfoDialog dlg(FORM::EDIT, bookInfo, index);
connect(&dlg, &BookInfoDialog::update, this, &BookInfo::slotDialogOperate);
dlg.exec();
}
else if (text == QString::fromLocal8Bit("删除"))
{
QMessageBox confirm;
confirm.setWindowTitle(QStringLiteral("提示"));
confirm.setText(QStringLiteral("是否确认删除当前数据?"));
confirm.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
confirm.button(QMessageBox::Yes)->setText(QStringLiteral("确定"));
confirm.button(QMessageBox::No)->setText(QStringLiteral("取消"));
int ret = confirm.exec();
if (ret == QMessageBox::Yes)
{
theModel->removeRow(index.row());
if (theModel->submitAll())
{
m_nCurrPage = 1;
ui.comboBox->setCurrentIndex(2);
int pageSize = ui.comboBox->itemData(2).toInt();
theModel->selectPination(m_nCurrPage, pageSize);
int totalRowCount = theModel->totalRowCount();
ui.label_totalNum->setText(QString::fromLocal8Bit("共") + QString::number(totalRowCount) + QString::fromLocal8Bit("条"));
initPagnition(totalRowCount);
QMessageBox::information(this, QStringLiteral("提示"), QStringLiteral("数据删除成功"));
}
}
}
}
void BookInfo::slotDialogOperate(BookInfoEntity* bookInfo, QModelIndex index)
{
int rowCount = index.row();
if (!index.isValid())
{
rowCount = theModel->rowCount();
}
if (!bookInfo->id().isEmpty())
{
theModel->setData(theModel->index(rowCount, theModel->fieldIndex("id")), bookInfo->id().toInt());
}
else
{
theModel->insertRow(rowCount);
}
theModel->setData(theModel->index(rowCount, theModel->fieldIndex("name")), bookInfo->name());
theModel->setData(theModel->index(rowCount, theModel->fieldIndex("author")), bookInfo->author());
theModel->setData(theModel->index(rowCount, theModel->fieldIndex("type")), bookInfo->type());
theModel->setData(theModel->index(rowCount, theModel->fieldIndex("publish")), bookInfo->publish());
theModel->setData(theModel->index(rowCount, theModel->fieldIndex("photo")), bookInfo->photo());
theModel->setData(theModel->index(rowCount, theModel->fieldIndex("intime")), QDateTime::currentDateTime());
theModel->submitAll();
qDebug() << theModel->lastError();
m_nCurrPage = 1;
ui.comboBox->setCurrentIndex(2);
int pageSize = ui.comboBox->itemData(2).toInt();
theModel->selectPination(m_nCurrPage, pageSize);
int totalRowCount = theModel->totalRowCount();
ui.label_totalNum->setText(QString::fromLocal8Bit("共") + QString::number(totalRowCount) + QString::fromLocal8Bit("条"));
initPagnition(totalRowCount);
}
void BookInfo::slotHandleSearch()
{
// TODO 由于文字更改会触发搜索(数据量大会影响性能),此处未实现
// 如要实现则拷贝slotSearchChanged方法中的内容到这里,并在前面添加QString text = ui.lineEdit_search->text();
}
BookInfo.ui 使用xml方式打开
<?xml version="1.0" encoding="utf-8"?>
<ui version="4.0">
<class>BookInfoClass</class>
<widget class="QWidget" name="BookInfoClass">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>814</width>
<height>402</height>
</rect>
</property>
<property name="windowTitle">
<string>图书信息</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="lineEdit_search"/>
</item>
<item>
<widget class="QPushButton" name="btnSearch">
<property name="text">
<string>搜索</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnSave">
<property name="text">
<string>新增</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnBatchDelete">
<property name="text">
<string>批量删除</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QTableView" name="tableView"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_totalNum">
<property name="text">
<string>共0条</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBox"/>
</item>
<item>
<widget class="QPushButton" name="btnPrevPage">
<property name="text">
<string>上一页</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="PageBtnHorizontalLayout"/>
</item>
<item>
<widget class="QPushButton" name="btnNextPage">
<property name="text">
<string>下一页</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
2:图书信息弹窗类(新增、编辑)
BookInfoDialog.h
#pragma once
#include "qdialog.h"
#include "qlineedit.h"
#include "qcombobox.h"
#include "qpushbutton.h"
#include "qimage.h"
#include <QHBoxLayout>
#include "CommonDefine.h"
#include "qlabel.h"
#include "ui_BookInfoDialog.h"
#include "BookInfoEntity.h"
#include "qfiledialog.h"
/*
自定义弹窗类
1:实现表单布局
2:实现表单功能
*/
class BookInfoDialog : public QDialog
{
Q_OBJECT
public:
BookInfoDialog(FORM formType, BookInfoEntity* bookInfo = Q_NULLPTR, QModelIndex index = QModelIndex(), QWidget* parent = Q_NULLPTR);
~BookInfoDialog();
signals:
void update(BookInfoEntity* bookInfo, QModelIndex index);
private slots:
void slotBtnSaveClick();
private:
QLineEdit* name;
QLineEdit* author;
QComboBox* type;
QComboBox* publish;
QLabel* photo;
QPushButton* m_BtnSave;
QPushButton* m_BtnPhoto;
QString sPhotoPath;
Ui::BookInfoDialogClass ui;
FORM m_FormType;
BookInfoEntity* m_pBookInfoEntity;
QModelIndex m_pIndex;
};
BookInfoDialog.cpp
#include "BookInfoDialog.h"
BookInfoDialog::BookInfoDialog(FORM formType, BookInfoEntity* bookInfo, QModelIndex index, QWidget* parent)
: QDialog(parent)
{
ui.setupUi(this);
setWindowTitle(QStringLiteral("图书信息"));
m_pBookInfoEntity = bookInfo;
m_FormType = formType;
m_pIndex = index;
name = new QLineEdit(this);
name->setClearButtonEnabled(true);
author = new QLineEdit(this);
type = new QComboBox(this);
type->addItem(QStringLiteral("文学"));
type->addItem(QStringLiteral("历史"));
type->addItem(QStringLiteral("地理"));
type->addItem(QStringLiteral("化学"));
type->addItem(QStringLiteral("计算机"));
type->setCurrentIndex(0);
publish = new QComboBox(this);
publish->addItem(QStringLiteral("中信出版社"));
publish->addItem(QStringLiteral("机械工业出版社"));
publish->addItem(QStringLiteral("人民邮电出版社"));
publish->addItem(QStringLiteral("电子工业出版社"));
publish->addItem(QStringLiteral("北京大学出版社"));
photo = new QLabel(this);
if (bookInfo != Q_NULLPTR)
{
name->setText(bookInfo->name());
author->setText(bookInfo->author());
type->setCurrentText(bookInfo->type());
publish->setCurrentText(bookInfo->publish());
sPhotoPath = bookInfo->photo();
}
// 这里使用网格布局
QGridLayout* gridLayout1 = new QGridLayout();
// 第一行
gridLayout1->addWidget(new QLabel(QStringLiteral("名称:")), 0, 0, Qt::AlignLeft); // 第一行第1列
gridLayout1->addWidget(name, 0, 1); // 第一行第2列
gridLayout1->addWidget(new QLabel(QStringLiteral("作者:")), 0, 2, Qt::AlignLeft); // 第一行第3列
gridLayout1->addWidget(author, 0, 3); // 第一行第4列
// 第二行
gridLayout1->addWidget(new QLabel(QStringLiteral("类型:")), 1, 0, Qt::AlignLeft); // 第二行第1列
gridLayout1->addWidget(type, 1, 1); // 第二行第2列
gridLayout1->addWidget(new QLabel(QStringLiteral("出版社:")), 1, 2, Qt::AlignLeft); // 第二行第3列
gridLayout1->addWidget(publish, 1, 3); // 第二行第4列
// 第三行,这里使用了跨行和跨列
gridLayout1->addWidget(new QLabel(QStringLiteral("封面图片:")), 2, 0, 2, 1, Qt::AlignLeft); // 第三行第1列
photo->setAlignment(Qt::AlignCenter);
photo->setFixedSize(this->width(), 200);
photo->setStyleSheet("QLabel{background-color: #fff;}");
gridLayout1->addWidget(photo, 2, 1, 1, 3); // 第三行第2,3,4列
if (!sPhotoPath.isEmpty())
{
QPixmap pixmap(sPhotoPath);
pixmap = pixmap.scaled(this->width(), 200, Qt::KeepAspectRatio); // 保持图像长宽比
photo->setPixmap(pixmap);
}
m_BtnPhoto = new QPushButton(QStringLiteral("选择封面"));
gridLayout1->addWidget(m_BtnPhoto, 3, 1, 1, 3); // 第四行第2,3,4列,第四行的第一列被上面的跨行占用了
connect(m_BtnPhoto, &QPushButton::clicked, this, [=] { // 选择图片
QString sPhotoName = QFileDialog::getOpenFileName(this, QStringLiteral("选择图片封面"), "/", "*.jpg *.png;; All Files(*.*)");
sPhotoPath = sPhotoName;
QPixmap pixmap(sPhotoName);
pixmap = pixmap.scaled(this->width(), 200, Qt::KeepAspectRatio); // 保持图像长宽比
photo->setPixmap(pixmap);
});
// 第五行
m_BtnSave = new QPushButton(QStringLiteral("保存"));
gridLayout1->addWidget(m_BtnSave, 4, 0, 1, 4, Qt::AlignBottom); // 第五行第1,2,3,4列
ui.formLayout->setLayout(0, QFormLayout::SpanningRole, gridLayout1);
ui.formLayout->setSpacing(10);
ui.formLayout->setMargin(5);
ui.formLayout->setRowWrapPolicy(QFormLayout::DontWrapRows);
ui.formLayout->setFormAlignment(Qt::AlignLeft | Qt::AlignVCenter);
connect(m_BtnSave, &QPushButton::clicked, this, &BookInfoDialog::slotBtnSaveClick);
}
BookInfoDialog::~BookInfoDialog()
{
// TODO 执行close方法后会执行析构,这里需不需要删除字段的指针
}
void BookInfoDialog::slotBtnSaveClick()
{
if (m_pBookInfoEntity == Q_NULLPTR)
{
m_pBookInfoEntity = new BookInfoEntity(); // 这个指针放到BookInfo中去释放
}
if (m_FormType == FORM::SAVE)
{
//bookInfo->setId(1);
}
m_pBookInfoEntity->setName(name->text());
m_pBookInfoEntity->setAuthor(author->text());
m_pBookInfoEntity->setType(type->currentText());
m_pBookInfoEntity->setPublish(publish->currentText());
m_pBookInfoEntity->setPhoto(sPhotoPath);
// TODO 这里表单需做校验
emit update(m_pBookInfoEntity, m_pIndex);
this->close();
}
BookInfoDialog.ui
<?xml version="1.0" encoding="utf-8"?>
<ui version="4.0">
<class>BookInfoDialogClass</class>
<widget class="QWidget" name="BookInfoDialogClass">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QFormLayout" name="formLayout"/>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
3:图书信息实体类
BookInfoEntity.h
#pragma once
#include "qstring.h"
#include "qdatetime.h"
/*
图书信息实体类
1:用于在弹窗和图书信息类之间进行交互
*/
class BookInfoEntity
{
public:
BookInfoEntity(int id, QString name, QString author, QString type, QString publish, QString photo, QDateTime intime);
BookInfoEntity();
~BookInfoEntity();
public:
QString id();
void setId(int id);
QString name();
void setName(QString name);
QString author();
void setAuthor(QString author);
QString type();
void setType(QString type);
QString publish();
void setPublish(QString publish);
QString photo();
void setPhoto(QString photo);
QDateTime intime();
void setIntime(QDateTime intime);
private:
int m_id; // id
QString m_name; // 图书名称
QString m_author; // 作者
QString m_type; // 类型
QString m_publish; // 出版社
QString m_photo; // 封面图片
QDateTime m_intime; // 创建时间
};
BookInfoEntity.cpp
#include "BookInfoEntity.h"
BookInfoEntity::BookInfoEntity(int id, QString name, QString author, QString type, QString publish, QString photo, QDateTime intime)
{
m_id = id;
m_name = name;
m_author = author;
m_type = type;
m_publish = publish;
m_photo = photo;
m_photo = photo;
m_intime = intime;
}
BookInfoEntity::BookInfoEntity()
{
m_id = -1;
}
BookInfoEntity::~BookInfoEntity()
{
}
QString BookInfoEntity::id()
{
if (this->m_id == -1) return "";
return QString::number(this->m_id);
}
void BookInfoEntity::setId(int id)
{
this->m_id = id;
}
QString BookInfoEntity::name()
{
return this->m_name;
}
void BookInfoEntity::setName(QString name)
{
this->m_name = name;
}
QString BookInfoEntity::author()
{
return this->m_author;
}
void BookInfoEntity::setAuthor(QString author)
{
this->m_author = author;
}
QString BookInfoEntity::type()
{
return this->m_type;
}
void BookInfoEntity::setType(QString type)
{
this->m_type = type;
}
QString BookInfoEntity::publish()
{
return this->m_publish;
}
void BookInfoEntity::setPublish(QString publish)
{
this->m_publish = publish;
}
QString BookInfoEntity::photo()
{
return this->m_photo;
}
void BookInfoEntity::setPhoto(QString photo)
{
this->m_photo = photo;
}
QDateTime BookInfoEntity::intime()
{
return this->m_intime;
}
void BookInfoEntity::setIntime(QDateTime intime)
{
this->m_intime = intime;
}
4:自定义QSqlTableModel类
BookQSqlTableModel.h
#pragma once
#include "qsqltablemodel.h"
#include "qsqldatabase.h"
#include "qsqlquery.h"
#include "qdebug.h"
/*
自定义QSqlTableModel
1:用于实现添加操作列、分页
2:统一保存方法
*/
class BookQSqlTableModel : public QSqlTableModel
{
Q_OBJECT
public:
BookQSqlTableModel(QObject* parent = nullptr, QSqlDatabase db = QSqlDatabase());
~BookQSqlTableModel();
public:
int getColumnCount(); // 获取列数量
bool selectPination(int page, int pageSize); // 分页查询
int totalRowCount(); // 总条数
bool submitAll(); // 统一操作,添加事务
private:
// 自定义生成列
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; // 头数据
int columnCount(const QModelIndex& parent = QModelIndex()) const override; // 列数量
virtual QString selectStatement() const; // select方法执行前执行此方法,重写此方法为了加分页
int nPage; // 当前页
int nPageSize; // 每页显示条数
};
BookQSqlTableModel.cpp
#include "BookQSqlTableModel.h"
BookQSqlTableModel::BookQSqlTableModel(QObject* parent, QSqlDatabase db)
: QSqlTableModel(parent, db)
{
nPage = 0;
nPageSize = 20;
}
BookQSqlTableModel::~BookQSqlTableModel()
{
}
int BookQSqlTableModel::getColumnCount()
{
return QSqlTableModel::columnCount();
}
bool BookQSqlTableModel::selectPination(int page, int pageSize) // TODO这里得加状态,用于清空filter
{
nPage = page;
nPageSize = pageSize;
return QSqlTableModel::select();
}
int BookQSqlTableModel::totalRowCount()
{
QString filter = QSqlTableModel::filter();
QString sql = QString("select count(1) from %1").arg(QSqlTableModel::tableName());
if (!filter.isEmpty())
{
sql += " where " + filter;
}
QSqlQuery query;
query.exec(sql);
if (query.next()) {
int totalCount = query.value(0).toInt();
return totalCount;
}
return QSqlTableModel::rowCount();
}
QVariant BookQSqlTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (section == QSqlTableModel::columnCount() && orientation == Qt::Horizontal && role == Qt::DisplayRole)
{
return QStringLiteral("操作");
}
return QSqlTableModel::headerData(section, orientation, role);
}
int BookQSqlTableModel::columnCount(const QModelIndex& parent) const
{
return QSqlTableModel::columnCount() + 1; // 加一列
}
QString BookQSqlTableModel::selectStatement() const
{
int startCount = (nPage - 1) * nPageSize;
QString statement = QSqlTableModel::selectStatement() + " limit " + QString::number(startCount) + ", " + QString::number(nPageSize) + ";";
return statement;
}
bool BookQSqlTableModel::submitAll()
{
if (!QSqlTableModel::database().transaction()) return false; // 事务开启失败
if (!QSqlTableModel::submitAll())
{
QSqlTableModel::database().rollback(); // 事务回滚
return false;
}
return QSqlTableModel::database().commit(); // 提交事务
}
5:自定义操作列委托类(代理)
OperateColumnDelegate.h
#pragma once
#include "qstyleditemdelegate.h"
#include <QApplication>
#include <QMouseEvent>
#include "CommonDefine.h"
#include <QDebug>
/*
自定义委托类
1:实现操作列添加按钮
2:点击按钮发送信号
*/
class OperateColumnDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
OperateColumnDelegate(QStringList btnNameList, QWidget* parent = Q_NULLPTR);
~OperateColumnDelegate();
signals:
void clicked(QString text, QModelIndex index); // 操作列按钮点击事件
private:
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) override;
private:
QStringList m_BtnNameList; // 按钮名称列表
int m_BtnState; // 按钮状态:点击、按下、释放
int m_BtnNum; //按钮数量
int m_BtnSpace; // 按钮间隔
QPoint m_MousePoint; // 鼠标位置
};
OperateColumnDelegate.cpp
#include "OperateColumnDelegate.h"
OperateColumnDelegate::OperateColumnDelegate(QStringList btnNameList, QWidget* parent)
: QStyledItemDelegate(parent)
{
m_BtnNameList = btnNameList;
m_BtnNum = m_BtnNameList.count();
m_BtnSpace = 4;
}
OperateColumnDelegate::~OperateColumnDelegate()
{
}
void OperateColumnDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
Q_UNUSED(index)
QStyleOptionViewItem optionItem(option);
//根据按钮数量,确定按键长宽及坐标
int buttonWidth = (optionItem.rect.width() - (m_BtnNum + 1) * m_BtnSpace) / m_BtnNum;
int buttonHeight = optionItem.rect.height() - 2 * m_BtnSpace;
int buttonLeft = optionItem.rect.left() + m_BtnSpace;
int buttonTop = optionItem.rect.top() + m_BtnSpace;
for (int i = 0; i < m_BtnNum; i++) {
QStyleOptionButton styleButton; //绘制按键
styleButton.rect = QRect(buttonLeft, buttonTop, buttonWidth, buttonHeight);
buttonLeft += buttonWidth + m_BtnSpace;
styleButton.text = m_BtnNameList.at(i);
styleButton.state = QStyle::State_Enabled; //设置按键状态
if ((m_BtnState == 0) && (styleButton.rect.contains(m_MousePoint))) {
styleButton.state |= QStyle::State_MouseOver;
}
else if ((m_BtnState == 1) && (styleButton.rect.contains(m_MousePoint))) {
styleButton.state |= QStyle::State_Raised;
}
QApplication::style()->drawControl(QStyle::CE_PushButton, &styleButton, painter);
}
}
bool OperateColumnDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index)
{
Q_UNUSED(model)
bool ret = false;
m_BtnState = -1;
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
m_MousePoint = mouseEvent->pos();
int buttonWidth = (option.rect.width() - (m_BtnNum + 1) * m_BtnSpace) / m_BtnNum;
int buttonHeight = option.rect.height() - 2 * m_BtnSpace;
int buttonLeft = option.rect.left() + m_BtnSpace;
int buttonTop = option.rect.top() + m_BtnSpace;
for (int i = 0; i < m_BtnNum; i++) {
QStyleOptionButton styleButton;
styleButton.rect = QRect(buttonLeft, buttonTop, buttonWidth, buttonHeight);
buttonLeft += buttonWidth + m_BtnSpace;
if (!styleButton.rect.contains(mouseEvent->pos())) {
continue;
}
else {
switch (mouseEvent->type()) {
case QEvent::MouseMove:
ret = false;
break;
case QEvent::MouseButtonPress:
m_BtnState = 0;
ret = true;
break;
case QEvent::MouseButtonRelease:
m_BtnState = 1;
ret = true;
emit clicked(m_BtnNameList.at(i), index);
break;
case QEvent::MouseButtonDblClick:
ret = true;
break;
default:
ret = false;
break;
}
}
}
return ret;
}
6:常量类
CommonDefine.h
#pragma once
/*
常量类
*/
enum OPTCOLUMN // 操作列按钮
{
OPT_EDIT, // 编辑
OPT_SAVE,// 保存
OPT_VIEW // 查看
};
enum FORM
{
SAVE, // 新增
EDIT // 编辑
};
四、结语
下一节【Qt实战教程第3篇 Qt+ffmpeg实现视频播放器,含ffmpeg视频编解码源码(本地文件、http、rtsp)、项目源码+注释】