Qt6.4PDF阅读器(含章节目录和预览图)

一、前言

Qt6.4版本的在线安装器列表,有PDF模块,不清楚6.0到6.3是否有,反正5.X是没有的,以前用5.9版本,写过一个小例子,当时用的是MuPDF的源码下载自己编译的dll库,过程之艰辛,一把辛酸泪。。。

本文就以Qt最新版内置的PDF模块简单做一个PDF阅读器

二、说明

1、环境

MSVC2019 64bit
Qt6.4.2
Windows 10 64位

Qt6.4安装时需要勾选PDF模块

三、代码

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTcpServer>
#include <QPdfDocument>
#include <QPdfView>
#include <QPdfLink>
#include <QPdfSearchModel>
#include <QPdfBookmarkModel>
#include <QPdfPageNavigator>
#include <QFileDialog>
#include <QDebug>
#include <QTreeView>
#include <QListView>
#include <QMutex>
#include <QBoxLayout>
#include <QStandardItemModel>
#include <QDateTime>
#include <QScrollBar>
#include <QWheelEvent>
#include <QInputDialog>
#include <QtConcurrent/QtConcurrent>
#include <QFuture>
#include "qlistdelegate.h"
#include <QScrollArea>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    QPdfView* pdfview=nullptr;
    QPdfPageNavigator* mNav=nullptr;
    QPdfSearchModel* mSerachmodel=nullptr;

protected:
    bool eventFilter(QObject *watched, QEvent *event);
};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include <QtConcurrent/QtConcurrent>
#include <QFuture>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    qDebug()<<"中文";

    ui->pushButton->setText("打开");

    QPdfDocument* doc = new QPdfDocument(this);
    pdfview = new QPdfView(this);

    mNav = pdfview->pageNavigator();
    pdfview->viewport()->installEventFilter(this);
    pdfview->installEventFilter(this);
    pdfview->setPageMode(QPdfView::PageMode::MultiPage);
    pdfview->setZoomMode(QPdfView::ZoomMode::Custom);
    pdfview->setDocument(doc);

    QVBoxLayout* layout = new QVBoxLayout();
    ui->centralwidget->setLayout(layout);

    QHBoxLayout* lay = new QHBoxLayout;

    QTabWidget* tabViewContainer = new QTabWidget(this);
    tabViewContainer->setTabPosition(QTabWidget::TabPosition::North);
    QTreeView* bookview = new QTreeView(this);
    QListView* imgview = new QListView(this);
    imgview->setDragEnabled(false);
    imgview->setEditTriggers(QAbstractItemView::NoEditTriggers);
    imgview->setSelectionBehavior(QAbstractItemView::SelectItems);
    imgview->setSelectionMode(QAbstractItemView::SingleSelection);
    connect(imgview,&QListView::clicked,this,[=](const QModelIndex& index){
       int page = index.data(Qt::UserRole+1).toInt();
       qDebug()<<"clicked page is"<<page;
       mNav->jump(page,QPointF());
    });
    QListView *serchView = new QListView(this);
    mSerachmodel = new QPdfSearchModel;
    mSerachmodel->setDocument(doc);
    serchView->setModel(mSerachmodel);
    connect(serchView,&QListView::clicked,this,[&](const QModelIndex &index){
        int page = mSerachmodel->data(index,int(QPdfSearchModel::Role::Page)).toInt();
        qDebug()<<mSerachmodel->data(index,int(QPdfSearchModel::Role::IndexOnPage));
        QPointF loc = mSerachmodel->data(index,int(QPdfSearchModel::Role::Location)).toPointF();
        qDebug()<<mSerachmodel->data(index,int(QPdfSearchModel::Role::ContextBefore));
        qDebug()<<mSerachmodel->data(index,int(QPdfSearchModel::Role::ContextAfter));
        qDebug()<<mSerachmodel->data(index,int(QPdfSearchModel::Role::NRoles));
        mNav->jump(page,loc);
    });
    tabViewContainer->addTab(bookview,"book");
    tabViewContainer->addTab(imgview,"img");
    tabViewContainer->addTab(serchView,"find");
    QPdfBookmarkModel* bookmodel = new QPdfBookmarkModel;
    bookmodel->setDocument(doc);
    bookview->setModel(bookmodel);

    connect(bookview,&QTreeView::doubleClicked,this,[=](const QModelIndex &index){
        qDebug()<<"#######"<<bookmodel->data(index,int(QPdfBookmarkModel::Role::Title));
        qDebug()<<"#######"<<bookmodel->data(index,int(QPdfBookmarkModel::Role::Level));
        int jumpPage = bookmodel->data(index,int(QPdfBookmarkModel::Role::Page)).toInt();
        QPointF jumppos =  bookmodel->data(index,int(QPdfBookmarkModel::Role::Location)).toPointF();
        qDebug()<<"#######"<<jumpPage;
        qDebug()<<"#######"<<jumppos;
        qDebug()<<"#######"<<bookmodel->data(index,int(QPdfBookmarkModel::Role::Zoom));
        qDebug()<<"#######"<<bookmodel->data(index,int(QPdfBookmarkModel::Role::NRoles));
        mNav->jump(jumpPage,jumppos);
        mNav->update(jumpPage,jumppos,0);
    });

    lay->addWidget(tabViewContainer,1);
    lay->addWidget(pdfview,3);
    layout->addLayout(lay,1);

    QHBoxLayout* pL = new QHBoxLayout();
    pL->addWidget(ui->pushButton);
    pL->addWidget(ui->pushButton_2);
    layout->addLayout(pL,0);

    QStandardItemModel *imgmodel = new QStandardItemModel;
    imgview->setModel(imgmodel);
    QListDelegate *delegate = new QListDelegate;
    imgview->setItemDelegate(delegate);
    imgview->setItemAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
    connect(ui->pushButton,&QPushButton::clicked,this,[=](){
        QString filePath =  QFileDialog::getOpenFileName(this,"open PDF",QCoreApplication::applicationDirPath(),"*.pdf");
        if(filePath.isEmpty()) return;
        QPdfDocument::Error e = doc->load(filePath);
        qDebug()<<"load result:"<<e;
        int pagecount = doc->pageCount();
        qDebug()<<"### pageCount is:"<<pagecount;
        imgmodel->clear();
        QFuture<void> fu = QtConcurrent::run([=,&imgmodel](){
            for(int i=0;i<pagecount;i++){
//                qDebug()<<doc->getAllText(i).text().simplified();
                QImage image = doc->render(i,QSize(150,250));
                QStandardItem* it = new QStandardItem;
                it->setData(image,Qt::UserRole);
                it->setData(i,Qt::UserRole+1);
                imgmodel->setItem(i,it);
            }
        });
    });
}

MainWindow::~MainWindow()
{
    delete ui;
}

bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
    if(pdfview->viewport() == watched && event->type()==QEvent::Wheel){
        QWheelEvent* ev = static_cast<QWheelEvent*>(event);
        if(ev->modifiers() == Qt::ControlModifier){
            int dy = ev->angleDelta().y();
            qreal cdelta = pdfview->zoomFactor();
            if(dy>0) pdfview->setZoomFactor(cdelta*1.1);
            else pdfview->setZoomFactor(cdelta*0.9);
        }
    }else if(pdfview == watched && event->type()==QEvent::KeyPress){
        QKeyEvent* ev=static_cast<QKeyEvent*>(event);
        if(ev->modifiers() == Qt::ControlModifier){
            switch (ev->key()) {
            case Qt::Key_F:{
                qDebug()<<"Ctrl+F";
                QString text =  QInputDialog::getText(this,"查找","关键字");
                if(!text.isEmpty()) mSerachmodel->setSearchString(text);
            }
                break;
            case Qt::Key_Z:qDebug()<<"Ctrl+Z";
                break;
            case Qt::Key_Y:qDebug()<<"Ctrl+Y";
                break;
            case Qt::Key_S:qDebug()<<"Ctrl+S";
                break;
            default:
                break;
            }
        }
    }
    return QMainWindow::eventFilter(watched,event);
}


qlistdelegate.h

#ifndef QLISTDELEGATE_H
#define QLISTDELEGATE_H

#include <QObject>
#include <QStyledItemDelegate>
#include <QStyleOptionViewItem>
#include <QDebug>
#include <QPainter>
#include <QBrush>
#include <QDebug>
class QListDelegate : public QStyledItemDelegate
{
    Q_OBJECT
signals:

public:
    explicit QListDelegate(QObject *parent = 0);
    ~QListDelegate();

    //重写重画函数
    void paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const;
    QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;

private:
    const qreal radius = 10;
};

#endif // QLISTDELEGATE_H

qlistdelegate.cpp

#include "qlistdelegate.h"

QListDelegate::QListDelegate(QObject *parent) :
    QStyledItemDelegate(parent)
{

}

QListDelegate::~QListDelegate()
{

}

void QListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    if(index.isValid())
    {
        painter->save();
#if 0
        painter->setPen(QColor(200,200,200));
        painter->setBrush(QBrush(QColor(200,200,200)));
        painter->drawRect(option.rect);
        QImage qimg = index.data(Qt::UserRole).value<QImage>();
        painter->setRenderHints(QPainter::Antialiasing,true);
        int sx = option.rect.x()+ ( option.rect.width() - qimg.width() ) /2 ;
        int sy = option.rect.y()+ ( option.rect.height()- qimg.height()) /2 ;
        painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
        painter->setBrush(QColor(255,255,255));
        painter->drawImage(sx,sy,qimg);
#else

        //绘制第一张图片
        painter->setBrush(QColor(200, 200, 200));
        painter->drawRect(option.rect);

        QImage qimg = index.data(Qt::UserRole).value<QImage>();
        int pageindex = index.data(Qt::UserRole+1).toInt();
        int sx = option.rect.x()+ ( option.rect.width() - qimg.width() ) /2 ;
        int sy = option.rect.y()+ ( option.rect.height()- qimg.height()-15 ) /2 ;
        //绘制第二张图片
        painter->setBrush(QColor(255, 255, 255));
        painter->drawRect(sx, sy, qimg.width(), qimg.height());

        //设置图片的重叠模式
        painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
        //重叠图片
        painter->drawImage(sx, sy, qimg);
#endif
        painter->drawText(QRect(sx,sy + qimg.height()+5,qimg.width(),15), Qt::AlignCenter, QString("第%1页").arg(pageindex+1));
        painter->restore();
    }
}

QSize QListDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    Q_UNUSED(index)
//    return QSize(option.rect.width(), 40);
    return QSize(200, 300);
}


QListDelegate这个类是为了给QListView提供视图的委托,以显示我们文档每页的缩略图,如果不需要缩略图,可以移除这部分代码

四、效果

1、书签目录

在这里插入图片描述

2、页缩略图

在这里插入图片描述

3、简介

点击打开按钮,选择你要打开的pdf文件即可,pushbutton没啥用,可移除

tab页签:
book为书签
img为缩略图
find为查找结果

find界面有点粗糙,你可以自行实现好看的界面去展示

最后,码字不易,作者原创文章,如需转载,请注明出处。

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值