采用异步加载来实现pdf的阅读会大效率的来加载pdf文件,使得加载的过程变得快速。缺点是加载的界面顺序是乱的,因此需要在加载到窗口的时候先排个序。此过程会浪费大约0.6s的时间。使用时需要导入poppler库
实现阅读器的窗口:
PdfReaderWidget::PdfReaderWidget(QWidget *parent)
: QWidget(parent), m_document(nullptr), m_completedThreads(0)
{
setAutoFillBackground(true);
m_threadPool = QThreadPool::globalInstance();
m_layout = new QVBoxLayout();
m_scrollArea = new QScrollArea();
m_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
m_scrollArea->setWidgetResizable(true);
QWidget* scrollAreaContent = new QWidget(this);
QVBoxLayout *scrollAreaLayout = new QVBoxLayout(scrollAreaContent);
scrollAreaLayout->setAlignment(Qt::AlignCenter);
m_scrollArea->setWidget(scrollAreaContent);
m_scrollArea->viewport()->installEventFilter(this);
m_layout->addWidget(m_scrollArea);
setLayout(m_layout);
}
PdfReaderWidget::~PdfReaderWidget()
{
m_threadPool->clear(); // 清除所有任务
m_threadPool->waitForDone(); // 等待所有线程完成
delete m_document;
m_document = nullptr;
delete m_layout;
m_layout = nullptr;
delete m_scrollArea;
m_scrollArea = nullptr;
}
h:
#ifndef PDFREADERWIDGET_H
#define PDFREADERWIDGET_H
#include "qmutex.h"
#include <QQueue>
#include <QThreadPool>
#include <QWidget>
#include <QVBoxLayout>
#include <QScrollArea>
#include <QLabel>
#include <poppler-qt5.h>
class PdfReaderWidget : public QWidget
{
Q_OBJECT
struct PageData {
int pageNum;
QImage image;
QLabel* label;
};
public:
explicit PdfReaderWidget(QWidget *parent = nullptr);
~PdfReaderWidget();
void loadPdf(const QString& filePath);
void loadVisiblePages();
protected:
bool eventFilter(QObject* obj, QEvent* event) override;
void onPageLoaded(int pageNum, QImage image);
void downloadPdf();
void closeEvent(QCloseEvent *event)
{
m_threadPool->clear(); // 清除所有任务
m_threadPool->waitForDone(); // 等待所有线程完成
delete m_document;
m_document = nullptr;
for (auto it = m_pages.begin(); it != m_pages.end(); ++it) {
if(it.value())
{
delete it.value();
it.value() = nullptr;
}
}
//qDeleteAll(m_pages);
m_pages.clear();
QWidget::closeEvent(event);
}
private:
QVBoxLayout* m_layout;
QScrollArea* m_scrollArea;
QString m_filePath;
Poppler::Document* m_document;
QThreadPool* m_threadPool;
QMutex m_mutex;
QMap<int, QLabel*> m_pages;
int m_completedThreads;
QQueue<PageData> m_pageQueue;
void processPageQueue();
signals:
void PdfClose();
};
#endif // PDFREADERWIDGET_H
CPP:
#include "pdfreaderwidget.h"
#include "usbthread.h"
#include <QTimer>
#include <QApplication>
#include <QScrollBar>
#include <QMessageBox>
#include <QThreadPool>
#include <QVector>
#include <QDebug>
#include <QFile>
PdfReaderWidget::PdfReaderWidget(QWidget *parent)
: QWidget(parent), m_document(nullptr), m_completedThreads(0)
{
setAutoFillBackground(true);
m_threadPool = QThreadPool::globalInstance();
m_layout = new QVBoxLayout();
m_scrollArea = new QScrollArea();
m_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
m_scrollArea->setWidgetResizable(true);
QWidget* scrollAreaContent = new QWidget(this);
QVBoxLayout *scrollAreaLayout = new QVBoxLayout(scrollAreaContent);
scrollAreaLayout->setAlignment(Qt::AlignCenter);
m_scrollArea->setWidget(scrollAreaContent);
m_scrollArea->viewport()->installEventFilter(this);
m_layout->addWidget(m_scrollArea);
setLayout(m_layout);
}
PdfReaderWidget::~PdfReaderWidget()
{
m_threadPool->clear(); // 清除所有任务
m_threadPool->waitForDone(); // 等待所有线程完成
delete m_document;
m_document = nullptr;
delete m_layout;
m_layout = nullptr;
delete m_scrollArea;
m_scrollArea = nullptr;
}
void PdfReaderWidget::loadPdf(const QString& filePath)
{
QFile file(filePath);
if (!file.exists()) {
QMessageBox::warning(nullptr, tr("Error"), tr("PDF file not found."));
return;
}
delete m_document;
m_document = Poppler::Document::load(filePath);
if (!m_document || m_document->isLocked()) {
QMessageBox::warning(this, tr("Error"), tr("Unable to open the PDF."));
return;
}
QFont font = QApplication::font();
font.setHintingPreference(QFont::PreferFullHinting);
font.setStyleStrategy(QFont::PreferQuality);
QApplication::setFont(font);
m_document->setRenderHint(Poppler::Document::Antialiasing);
m_document->setRenderHint(Poppler::Document::TextAntialiasing);
int m_pageHeight = 0;
int m_pageWidth = 0;
for (int i = 0; i < m_document->numPages(); ++i) {
Poppler::Page* pdfPage = m_document->page(i);
m_pageWidth = qMax(m_pageWidth, pdfPage->pageSize().width());
m_pageHeight += pdfPage->pageSize().height();
delete pdfPage;
}
int scrollAreaHeight = m_scrollArea->viewport()->height();
m_scrollArea->setMinimumSize(1000, scrollAreaHeight);
QScrollBar *scrollBar = m_scrollArea->verticalScrollBar();
scrollBar->setPageStep(m_scrollArea->viewport()->height());
int scrollBarMax = m_pageHeight - m_scrollArea->viewport()->height();
scrollBar->setRange(0, scrollBarMax);
loadVisiblePages();
}
void PdfReaderWidget::loadVisiblePages()
{
QTimer::singleShot(100, this, [this] {
if (!this->isVisible()) return; // 确保窗口仍然可见
int viewportHeight = m_scrollArea->viewport()->rect().height();
int scrollPosition = m_scrollArea->verticalScrollBar()->value();
int startPage = qMax(0, (scrollPosition * m_document->numPages()) / viewportHeight);
int endPage = qMin(m_document->numPages() - 1, ((scrollPosition + viewportHeight) * m_document->numPages()) / viewportHeight);
QLayoutItem* child;
while ((child = m_scrollArea->widget()->layout()->takeAt(0)) != nullptr) {
delete child->widget();
delete child;
}
for (int i = startPage; i <= endPage; ++i) {
QLabel* label = new QLabel;
label->setMinimumSize(1, 1);
{
QMutexLocker locker(&m_mutex);
m_pages.insert(i, label);
m_scrollArea->widget()->layout()->addWidget(label);
}
PdfLoadThread* loadThread = new PdfLoadThread(m_document, i);
loadThread->setProperty("pageNum", i);
connect(loadThread, &PdfLoadThread::pageLoaded, this, [this](int pageNum, QImage image) {
onPageLoaded(pageNum, image);
});
m_threadPool->start(loadThread);
}
});
}
void PdfReaderWidget::onPageLoaded(int pageNum, QImage image)
{
QMutexLocker locker(&m_mutex);
if (m_pages.contains(pageNum)) {
QLabel* pageLabel = m_pages.value(pageNum);
pageLabel->setPixmap(QPixmap::fromImage(image));
pageLabel->setMinimumSize(image.size());
if (pageNum == 0) {
pageLabel->setVisible(true);
} else {
pageLabel->setVisible(false);
}
}
m_completedThreads++;
if (m_completedThreads == m_pages.size()) {
m_completedThreads = 0;
for (auto pageLabel : m_pages) {
pageLabel->setVisible(true);
}
}
}
bool PdfReaderWidget::eventFilter(QObject* obj, QEvent* event)
{
if (obj == m_scrollArea->viewport() && event->type() == QEvent::Paint)
{
loadVisiblePages();
}
return QObject::eventFilter(obj, event);
}
thread.h:
class PdfLoadThread : public QObject , public QRunnable
{
Q_OBJECT
public:
PdfLoadThread(Poppler::Document* document, int pageNum, QObject* parent=nullptr)
: QObject(parent), QRunnable(), m_document(document), m_pageNum(pageNum) {}
void run()
{
Poppler::Page* pdfPage = m_document->page(m_pageNum);
if (!pdfPage) {
return;
}
QImage image = pdfPage->renderToImage(195, 195, -1, -1, -1, -1, Poppler::Page::Rotation::Rotate0);//可更改195, 数值越大,渲染率越高
delete pdfPage;
emit pageLoaded(m_pageNum, image);
}
signals:
void pageLoaded(int pageNum, QImage image);
private:
Poppler::Document* m_document;
int m_pageNum;
};
效果图如下:
//打开PDF窗口:
QString exePath = QCoreApplication::applicationDirPath();
QString filePath = exePath +QDir::separator()+ your_PDfName;
if (filePath.isEmpty()) {
return;
}
pdfReader = new PdfReaderWidget;
pdfReader->loadPdf(filePath);
QString tittle = tr("电子说明书");
pdfReader->setWindowTitle(tittle);
pdfReader->setWindowIcon(QIcon(":/img/2.png"));
pdfReader->setAttribute(Qt::WA_DeleteOnClose);
pdfReader->show()