最近在辅导我闺女数学题,发现她的思维逻辑是没有问题的,就是练习不够,因此在计算分数以及负数乘法的时候会经常出错,导致整个题目都做错了,为了让俺闺女能够在数学计算上得到充分的锻炼,锻炼的同时不希望她过多的接触电子设备。我开发了一个自动生成试卷(pdf)的程序,并且可以直接打印,然后她就能够有写不完的试卷了。(我闺女真是谢谢我了)
整个程序可以分为几个部分:界面部分、生成题目部分、写pdf文件部分。其中最复杂的是写pdf文件,因为涉及到大量的位置计算。
界面部分:用于展示和收集用户的需求,根据用户的需求生成不同的试卷。界面设计图如下:
可以根据概率生成分数和整数,根据概率生成负数和正数,可以调整分子以及分母的随机范围。
设置文件保存的路径。
生成题目:我这里仅设计了乘法,因此生成题目的主要难度还是在根据界面部分传递的参数生成对应的数字,由于涉及到分数,因此我们需要自己构建存储分数的类或者结构体。
写pdf部分:该部分的内容就是根据生成的题目将题目绘制到对应位置上,该部分涉及到位置结算因此比较复杂。
最后生成的题目如下图:
刚开始学习编程的同学一定需要习惯看类关系图,因为类关系图会让你很快的掌握各个类的功能以及关系。尤其是对于大型项目来说,光依靠阅读代码就希望了解整体功能不太可能的。
MainWindow:处理按键信息事务,将任务派发给其他类完成。
MainWindowPrivate:用于处理程序布局,以及获取控件信息等内容,这里还将数据的初始化放入在概类(随着业务的升级,数据的初始化应该由单独的类进行管理,并且提供固定接口给MainWindowPrivate使用)
NumItem:题目中涉及到分数计算,为保存初始化的数据,并且保存分数,因此涉及该类对NumItem本身的初始化以及数据存储做处理。
PDFManager:PDF文件管理类,根据初始化的数据信息对数据进行绘制生成对应的题目,其中绘制部分其实可以进行拆分,绘制分数,绘制整数,以后可能会增加绘制百分比,绘制幂指数,绘制根号等,我这里为了快速完成代码,因此将其都在一个类中进行处理。
DataFactory:根据条件(前端用户选择和输入)生成对应的数字数据,用于绘制公式。
MainWindow 的内容很简单,就是处理两个按钮信息就可以了,1. 生成dpf,将消息传递给DPFManager类,2. 获取文件存储路径。
// 写文件
void MainWindow::slotDoWrite()
{
Q_D(MainWindow);
QString file_path = QApplication::applicationDirPath() + "/text.pdf";
QList> list = d->initALLNumList();
pdfManager.writePDF(file_path, list);
}
void MainWindow::slotPickPath() {
// 获取文件路劲
// 代码略
}
用于处理布局等问题,以及内部展示问题例如:滑动的百分比显示,获取界面中的待初始化数据。
initUI():函数用来初始化UI布局问题
initConnec():用于处理信号于槽函数的绑定。
MainWindowPrivate::MainWindowPrivate(MainWindow *parent)
: q_ptr(parent)
, m_wdgMain(new QWidget())
, m_sliderIntegerChance(new QSlider())
, m_labIntegerChance(new QLabel(tr("分整数概率:")))
, m_labIntegerChanceValue(new QLabel("50%"))
, m_layoutIntegerChance(new QHBoxLayout)
, m_sliderPositiveChance(new QSlider())
, m_labPositiveChance(new QLabel(tr("正负数概率:")))
, m_labPositiveChanceValue(new QLabel(tr("50%")))
, m_layoutPositiveChance(new QHBoxLayout)
, m_lineEditMoleculeRange(new QLineEdit("10"))
, m_labMoleculeRange(new QLabel(tr("分子值范围:")))
, m_layoutMoleculeRange(new QHBoxLayout)
, m_lineEditDenominatorRange(new QLineEdit("10"))
, m_labDenominatorRange(new QLabel(tr("分母值范围:")))
, m_layoutDenominatorRange(new QHBoxLayout)
, m_lineEditNum(new QLineEdit("4"))
, m_labNum(new QLabel(tr("单行数字个数:")))
, m_layoutNum(new QHBoxLayout)
, m_lineEditPage(new QLineEdit("10"))
, m_labPage(new QLabel(tr("生成页码数量:")))
, m_layoutPage(new QHBoxLayout)
, m_btnSavePath(new QPushButton(tr("保存路径")))
, m_lineEditSavePath(new QLineEdit())
, m_hlayoutSavePath(new QHBoxLayout)
, m_btnWritePDF(new QPushButton(tr("生成试卷")))
, m_vlayoutMain(new QVBoxLayout)
{
initUI();
initConnect();
}
void MainWindowPrivate::initUI()
{
//
m_layoutIntegerChance->addWidget(m_labIntegerChance);
m_layoutIntegerChance->addWidget(m_sliderIntegerChance);
m_sliderIntegerChance->setValue(50);
m_sliderIntegerChance->setMaximum(100);
m_layoutIntegerChance->addWidget(m_labIntegerChanceValue);
m_sliderIntegerChance->setOrientation(Qt::Orientation::Horizontal);
m_vlayoutMain->addLayout(m_layoutIntegerChance);
//
m_layoutPositiveChance->addWidget(m_labPositiveChance);
m_layoutPositiveChance->addWidget(m_sliderPositiveChance);
m_sliderPositiveChance->setValue(50);
m_sliderPositiveChance->setMaximum(100);
m_layoutPositiveChance->addWidget(m_labPositiveChanceValue);
m_sliderPositiveChance->setOrientation(Qt::Orientation::Horizontal);
m_vlayoutMain->addLayout(m_layoutPositiveChance);
//
m_layoutMoleculeRange->addWidget(m_labMoleculeRange);
m_layoutMoleculeRange->addWidget(m_lineEditMoleculeRange);
m_lineEditMoleculeRange->setValidator(new QIntValidator(1, 1000, this));
m_vlayoutMain->addLayout(m_layoutMoleculeRange);
//
m_layoutDenominatorRange->addWidget(m_labDenominatorRange);
m_layoutDenominatorRange->addWidget(m_lineEditDenominatorRange);
m_lineEditDenominatorRange->setValidator(new QIntValidator(1, 1000, this));
m_vlayoutMain->addLayout(m_layoutDenominatorRange);
m_layoutNum->addWidget(m_labNum);
m_layoutNum->addWidget(m_lineEditNum);
m_lineEditNum->setValidator(new QIntValidator(2, 8, this));
m_vlayoutMain->addLayout(m_layoutNum);
m_layoutPage->addWidget(m_labPage);
m_layoutPage->addWidget(m_lineEditPage);
m_lineEditPage->setValidator(new QIntValidator(1, 1000, this));
m_vlayoutMain->addLayout(m_layoutPage);
//
m_hlayoutSavePath->addWidget(m_lineEditSavePath);
m_hlayoutSavePath->addWidget(m_btnSavePath);
m_vlayoutMain->addLayout(m_hlayoutSavePath);
m_vlayoutMain->addWidget(m_btnWritePDF);
m_vlayoutMain->addStretch();
m_wdgMain->setLayout(m_vlayoutMain);
}
void MainWindowPrivate::initConnect()
{
connect(m_sliderIntegerChance, &QSlider::valueChanged, this,
&MainWindowPrivate::slotIntegerChance);
connect(m_sliderPositiveChance, &QSlider::valueChanged, this,
&MainWindowPrivate::slotPositiveChance);
connect(m_btnWritePDF, &QPushButton::clicked, q_ptr,
&MainWindow::slotDoWrite);
connect(m_btnSavePath, &QPushButton::clicked, q_ptr,
&MainWindow::slotPickPath);
}
Numrule MainWindowPrivate::getNumRule()
{
Numrule item;
item.IntegerChance = m_sliderIntegerChance->value();
item.PositiveChance = m_sliderPositiveChance->value();
item.MoleculeRange = m_lineEditMoleculeRange->text().toInt();
item.DenominatorRange = m_lineEditDenominatorRange->text().toInt();
item.page = m_lineEditPage->text().toInt();
return item;
}
void MainWindowPrivate::slotIntegerChance(int value)
{
QString str = QString::number(value) + "%";
m_labIntegerChanceValue->setText(str);
}
void MainWindowPrivate::slotPositiveChance(int value)
{
QString str = QString::number(value) + "%";
m_labPositiveChanceValue->setText(str);
}
分数类,用于记录分数数据,以及初始化分数数据等操作。如果后期需要给出答案,这里还需要加上分数的计算式重载。
代码如下:
#include "numitem.h"
#include
#include
#include
NumItem::NumItem(Numrule rule)
: m_bSymbol(POSITIVENUMBER)
, m_iMolecule(1)
, m_iDenominator(1)
, m_numType(INTEGER)
{
init(rule);
}
void NumItem::init(Numrule rule)
{
// 1. 随机符号
m_bSymbol = initSymbol(rule.PositiveChance);
// 2. 随机整数或者分数。
m_numType = initNumType(rule.IntegerChance);
// 3. 随机数值
m_iMolecule = initMolecule(rule.MoleculeRange);
m_iDenominator = initMolecule(rule.DenominatorRange);
// 整理 1. 约分
Equivalency();
// 整理 2. 分母为1 那么为整数
if (m_iDenominator == 1) {
m_numType = INTEGER;
}
}
void NumItem::Equivalency()
{
int num = m_iMolecule > m_iDenominator ? m_iMolecule : m_iDenominator;
for (int i = 2; i <= num; i++) {
if (m_iDenominator % i == 0 && m_iMolecule % i == 0) {
m_iDenominator = m_iDenominator / i;
m_iMolecule = m_iMolecule / i;
num = m_iMolecule > m_iDenominator ? m_iMolecule : m_iDenominator;
i = 1;
}
}
}
NumItem::SymbolType NumItem::initSymbol(int rule)
{
int random = QRandomGenerator::global()->bounded(100);
// 概率为0表示全为负数
if (0 == rule) {
return NEGATIVENUMBER;
} else if (100 == rule) { // 概率为100表示全为正数。
return POSITIVENUMBER;
} else if (random < rule) {
return POSITIVENUMBER;
} else {
return NEGATIVENUMBER;
}
}
NumItem::NumType NumItem::initNumType(int change)
{
int random = QRandomGenerator::global()->bounded(100);
if (0 == random) {
return INTEGER;
} else if (100 == random) {
return FRACTION;
} else if (random < change) {
return INTEGER;
} else {
return FRACTION;
}
}
int NumItem::initMolecule(int rule)
{
return QRandomGenerator::global()->bounded(100) % (rule - 1) + 1;
}
int NumItem::initDenominator(int rule)
{
return QRandomGenerator::global()->bounded(100) % (rule - 1) + 1;
}
NumItem::SymbolType NumItem::getSymbol() { return m_bSymbol; }
NumItem::NumType NumItem::getNumType() { return m_numType; }
int NumItem::getMolecule() { return m_iMolecule; }
int NumItem::getDenominator() { return m_iDenominator; }
绘制打印页面内容部分。绘制流程如下。
绘制思路其实很简单,就是先绘制学生需要填写的信息,然后按行绘制,每一行中分别绘制数字,最后生成PDF文件。
代码如下:
#include "pdf_manager.h"
#include
#include
#include
#include
#include
#include
PDFManager::PDFManager(QObject *parent)
: QObject(parent)
{
}
int PDFManager::writePDF(QString file_path, Numrule rule)
{
QFile pdfFile(file_path);
pdfFile.open(QIODevice::WriteOnly);
QPdfWriter *pWriter = new QPdfWriter(&pdfFile);
pWriter->setPageSize(QPagedPaintDevice::A4);
pWriter->setResolution(96);
pWriter->setPageMargins(QMarginsF(35, 35, 35, 35));
QPainter *pPainter = new QPainter(pWriter);
int lastPage = rule.page;
for (int i = 0; i <= (lastPage - 1); i++) {
QList> list = m_dataFactory.initALLNumList(rule);
// 获取生成数据的随机规则
draw(list, pPainter);
if (i != (lastPage - 1)) pWriter->newPage();
}
delete pPainter;
delete pWriter;
pdfFile.close();
QDesktopServices::openUrl(QUrl::fromLocalFile(file_path));
return 0;
}
void PDFManager::draw(QList> list, QPainter *pPainter)
{
QFontMetrics fm(pPainter->fontMetrics());
// 绘制 填写的试卷信息。
int w = fm.width("姓名:______________");
pPainter->drawText(QRect(0, 0, w, fm.height()), Qt::AlignCenter,
QString((QString("姓名:______________"))));
int startX = w;
w = fm.width("日期:______________");
pPainter->drawText(QRect(startX, 0, startX + w, fm.height()),
Qt::AlignCenter,
QString((QString("日期:______________"))));
startX += w;
w = fm.width("分数:______________");
pPainter->drawText(QRect(startX, 0, startX + w, fm.height()),
Qt::AlignCenter,
QString((QString("分数:______________"))));
// 遍历绘制每一行的数学公式
for (int index = 1; index <= list.size(); index++) {
drawLine(index, list.at(index - 1), pPainter);
}
}
void PDFManager::drawLine(int index, QList list, QPainter *pPainter)
{
int w = 20;
QFontMetrics fm(pPainter->fontMetrics());
int startNumY = (LINE_HEIGHT * index) + (fm.height() / 2) + 2;
QString strTitle = QString::number(index);
// 绘制提标
strTitle.append(".");
int titleWidth = fm.width(strTitle);
pPainter->drawText(
QRect(0, startNumY, 0 + titleWidth, startNumY + fm.height()),
Qt::AlignCenter, strTitle);
for (int i = 0; i < list.size(); i++) {
w += drawNum(index, w, list.at(i), pPainter);
int width = fm.width(QString("*"));
if (i == list.size() - 1) {
pPainter->drawText(
QRect(w, startNumY, w + width, startNumY + fm.height()),
Qt::AlignCenter, QString((QString("="))));
w += width;
} else {
pPainter->drawText(
QRect(w, startNumY, w + width, startNumY + fm.height()),
Qt::AlignCenter, QString((QString("*"))));
w += width;
}
}
}
int PDFManager::drawNum(int index, int startPostionX, NumItem item,
QPainter *pPainter)
{
QString str = "";
QFontMetrics fm(pPainter->fontMetrics());
QPen pen;
pen.setWidth(4);
pPainter->setPen(pen);
// 起始的x坐标
int startNumX = 0;
// 起始的y坐标。
int startNumY = 0;
// 1. 绘制信息获取
startNumX = startPostionX;
if (item.getNumType() == NumItem::INTEGER) {
startNumY = index * LINE_HEIGHT + fm.height() / 2;
} else {
startNumY = index * LINE_HEIGHT;
}
// 如果是正数
if (item.getSymbol() == NumItem::POSITIVENUMBER) {
// 直接绘制数字
if (item.getNumType() == NumItem::INTEGER) {
QString strMolecule = QString::number(item.getMolecule());
int w = fm.width(strMolecule);
pPainter->drawText(QRect(startNumX, startNumY, startNumX + w,
startNumY + fm.height()),
Qt::AlignCenter, QString(strMolecule));
} else {
QString strMolecule = QString::number(item.getMolecule());
QString strDenominator = QString::number(item.getDenominator());
int w = fm.width(strMolecule);
// 绘制分子
pPainter->drawText(startNumX, startNumY, startNumX + w,
startNumY + fm.height(), Qt::AlignCenter,
QString(strMolecule));
// 绘制分母
pPainter->drawText(startNumX, startNumY + fm.height(),
startNumX + w, startNumY + fm.height() * 2,
Qt::AlignCenter, strDenominator);
// 分号
pPainter->drawText(
startNumX, index * LINE_HEIGHT + fm.height() / 2, startNumX + w,
index * LINE_HEIGHT + fm.height() / 2 + fm.height(),
Qt::AlignCenter, QString("-"));
}
} else {
int w = fm.width(QString("("));
pPainter->drawText(
QRect(startNumX, index * LINE_HEIGHT + fm.height() / 2,
startNumX + w,
index * LINE_HEIGHT + fm.height() / 2 + fm.height()),
Qt::AlignCenter, QString("("));
startNumX += w;
w = fm.width(QString("-"));
pPainter->drawText(
QRect(startNumX, index * LINE_HEIGHT + fm.height() / 2,
startNumX + w,
index * LINE_HEIGHT + fm.height() / 2 + fm.height()),
Qt::AlignCenter, QString("-"));
startNumX += w;
if (item.getNumType() == NumItem::INTEGER) {
QString strMolecule = QString::number(item.getMolecule());
int w = fm.width(strMolecule);
pPainter->drawText(QRect(startNumX, startNumY, startNumX + w,
startNumY + fm.height()),
Qt::AlignCenter, QString(strMolecule));
startNumX += w;
} else {
QString strMolecule = QString::number(item.getMolecule());
QString strDenominator = QString::number(item.getDenominator());
int w = fm.width(strMolecule);
// 绘制分子
pPainter->drawText(QRect(startNumX, startNumY, startNumX + w,
startNumY + fm.height()),
Qt::AlignCenter, QString(strMolecule));
// 绘制分母
pPainter->drawText(
QRect(startNumX, index * LINE_HEIGHT + fm.height(),
startNumX + w, index * LINE_HEIGHT + fm.height() * 2),
Qt::AlignCenter, strDenominator);
pPainter->drawText(
startNumX, index * LINE_HEIGHT + fm.height() / 2, startNumX + w,
index * LINE_HEIGHT + fm.height() / 2 + fm.height(),
Qt::AlignCenter, QString("-"));
startNumX += w;
}
w = fm.width(QString(")"));
// startNumX += w;
pPainter->drawText(
QRect(startNumX, index * LINE_HEIGHT + fm.height() / 2,
startNumX + w,
index * LINE_HEIGHT + fm.height() + fm.height() / 2),
Qt::AlignCenter, QString(")"));
}
return getNumWidth(item, pPainter);
}
int PDFManager::getNumWidth(NumItem item, QPainter *pPainter)
{
// 1. 获取纯数字的宽度。
QFontMetrics fm(pPainter->fontMetrics());
int numWidth = 0;
// 2. 分数判断
if (item.getNumType() == NumItem::INTEGER) {
// 如果其为整数直接获取数字的长度
numWidth = fm.width(QString::number(item.getMolecule()));
} else {
// 如果为分数
int iWidthMolecule = fm.width(QString::number(item.getMolecule()));
int iWidthDenominator =
fm.width(QString::number(item.getDenominator()));
numWidth = iWidthMolecule > iWidthDenominator ? iWidthMolecule
: iWidthDenominator;
}
// 3. 正负数判断
if (item.getSymbol() == NumItem::NEGATIVENUMBER) {
int iWidthKL = fm.width(QString("("));
int iWidthSymbol = fm.width(QString("-"));
int iWidthKR = fm.width(QString(")"));
numWidth += iWidthSymbol;
numWidth += iWidthKL;
numWidth += iWidthKR;
}
return numWidth;
}
地址:TestPaper · master · 三雷科技 / QT博客案例 · GitCode
https://gitcode.net/arv002/qt/-/tree/master/TestPaper