如何从 QWebEngineView 打印

Posted

技术标签:

【中文标题】如何从 QWebEngineView 打印【英文标题】:How to print from QWebEngineView 【发布时间】:2020-04-04 01:52:18 【问题描述】:

我正在尝试从我的应用程序中打印一份报告,其中包括文本和表格。由于QTextDocument 不够用,我决定使用QWebEngineView 和Qt 富文本引擎不支持的更复杂的html/CSS。

我能够从视图创建 PDF,但是我有一些普遍的误解,因为有时它会崩溃,而且打印而不是 PDF 创建也会崩溃。

这是我的尝试:


方法一:创建PDF

这是唯一有效的变体:

    auto webView = new QWebEngineView();
    webView->setHtml(contents);

    const QString fn = QFileDialog::getSaveFileName(0, "Save pdf", ".", "PDF Files (*.pdf)");

    if (!fn.isEmpty())
       webView->page()->printToPdf(fn);

但是,这仅因对话框 (?!) 而有效。如果我这样改变它:

    QString fn ="/Users/s710/Downloads/test.pdf";
    auto webView = new QWebEngineView();
    webView->setHtml(contents);
    webView->page()->printToPdf(fn);

它将创建一个带有空白页面的 PDF。所以我猜以上只是偶然的。


方法二:直接打印

这种方法会崩溃:

    auto webView = new QWebEngineView();
    webView->setHtml(contents);

    QPrinter printer(QPrinter::QPrinter::ScreenResolution);
    printer.setOutputFormat(QPrinter::NativeFormat);
    printer.setPaperSize(QPrinter::A4);
    printer.setPageMargins(12, 16, 12, 20, QPrinter::Millimeter);

    webView->page()->print(&printer, [](bool));

Crash:
1   QPrinter::pageRect() const                                                                                                                                                                                                                                 (x86_64) /Users/s710/Qt/5.12.6/clang_64/lib/QtPrintSupport.framework/Versions/5/QtPrintSupport                       0x100247fe4    
2   QWebEnginePagePrivate::didPrintPage(unsigned long long, QByteArray const&)                                                                                                                                                                                 (x86_64) /Users/s710/Qt/5.12.6/clang_64/lib/QtWebEngineWidgets.framework/Versions/5/QtWebEngineWidgets               0x100200f0a    
3   QtWebEngineCore::callbackOnPrintingFinished(QtWebEngineCore::WebContentsAdapterClient *, int, std::vector<char> const&)                                                                                                                                    (x86_64) /Users/s710/Qt/5.12.6/clang_64/lib/QtWebEngineCore.framework/Versions/5/QtWebEngineCore                     0x100899693    
4   base::debug::TaskAnnotator::RunTask(const char *, base::PendingTask *)                                                                                                                                                                                     (x86_64) /Users/s710/Qt/5.12.6/clang_64/lib/QtWebEngineCore.framework/Versions/5/QtWebEngineCore                     0x10295d402    
5   base::MessageLoop::RunTask(base::PendingTask *)                                                                                                                                                                                                            (x86_64) /Users/s710/Qt/5.12.6/clang_64/lib/QtWebEngineCore.framework/Versions/5/QtWebEngineCore                     0x10298395f    
6   base::MessageLoop::DoWork()                                                                                                                                                                                                                                (x86_64) /Users/s710/Qt/5.12.6/clang_64/lib/QtWebEngineCore.framework/Versions/5/QtWebEngineCore                     0x102983ef9    
7   std::__function::__func<QtWebEngineCore::(anonymous namespace)::MessagePumpForUIQt::MessagePumpForUIQt()::'lambda'(), std::allocator<QtWebEngineCore::(anonymous namespace)::MessagePumpForUIQt::MessagePumpForUIQt()::'lambda'()>, void ()>::operator()() (x86_64) /Users/s710/Qt/5.12.6/clang_64/lib/QtWebEngineCore.framework/Versions/5/QtWebEngineCore                     0x100839f99    
8   QObject::event(QEvent *)                                                                                                                                                                                                                                   (x86_64) /Users/s710/Qt/5.12.6/clang_64/lib/QtCore.framework/Versions/5/QtCore                                       0x10824bcf6    
...

方法3:等待webview加载完毕,然后创建PDF

因此,由于阻止文件对话框似乎有所不同,我认为当视图尚未加载 HTML 时可能会出现问题。我还读到QWebEngineView 资源很重,所以我想我可以等待它完成加载。

但是,这也会崩溃

    auto webView = new QWebEngineView();

    QObject::connect(webView, &QWebEngineView::loadFinished, this, [&webView](bool ok)
    
        QString fn ="/Users/s710/Downloads/test.pdf";
        webView->page()->printToPdf(fn);
    );

    webView->setHtml(contents);

Crash:
1   QWebEnginePage::printToPdf(QString const&, QPageLayout const&)                                                                                                                                                          (x86_64) /Users/s710/Qt/5.12.6/clang_64/lib/QtWebEngineWidgets.framework/Versions/5/QtWebEngineWidgets     0x100204cd7    
2   Printer::print(MonthItem *, QWidget *)::$_0::operator()(bool) const                                                                                                                                                     printer.cpp                                                                                            203 0x100016eeb    
3   QtPrivate::FunctorCall<QtPrivate::IndexesList<0>, QtPrivate::List<bool>, void, Printer::print(MonthItem *, QWidget *)::$_0>::call(Printer::print(MonthItem *, QWidget *)::$_0&, void * *)                               qobjectdefs_impl.h                                                                                     146 0x100016dc8    
4   void QtPrivate::Functor<Printer::print(MonthItem *, QWidget *)::$_0, 1>::call<QtPrivate::List<bool>, void>(Printer::print(MonthItem *, QWidget *)::$_0&, void *, void * *)                                              qobjectdefs_impl.h                                                                                     256 0x100016d71    
5   QtPrivate::QFunctorSlotObject<Printer::print(MonthItem *, QWidget *)::$_0, 1, QtPrivate::List<bool>, void>::impl(int, QtPrivate::QSlotObjectBase *, QObject *, void * *, bool *)                                        qobjectdefs_impl.h                                                                                     439 0x100016d1d    
6   QMetaObject::activate(QObject *, int, int, void * *)                                                                                                                                                                    (x86_64) /Users/s710/Qt/5.12.6/clang_64/lib/QtCore.framework/Versions/5/QtCore                             0x10825153b    
7   QWebEngineView::loadFinished(bool)                                                                                                                                                                                      (x86_64) /Users/s710/Qt/5.12.6/clang_64/lib/QtWebEngineWidgets.framework/Versions/5/QtWebEngineWidgets     0x10020cb3f

方法4:等待webview加载完毕,然后打印

这也会崩溃:

    auto webView = new QWebEngineView();

    QObject::connect(webView, &QWebEngineView::loadFinished, this, [&webView](bool ok)
    
        QPrinter printer(QPrinter::PrinterResolution);
        printer.setOutputFormat(QPrinter::NativeFormat);
        printer.setPaperSize(QPrinter::A4);
        printer.setPageMargins(12, 16, 12, 20, QPrinter::Millimeter);

        webView->page()->print(&printer, [](bool));
    );

    webView->setHtml(contents);

Crash:
1   QWebEngineView::page() const                                                                                                                                                                                            (x86_64) /Users/s710/Qt/5.12.6/clang_64/lib/QtWebEngineWidgets.framework/Versions/5/QtWebEngineWidgets     0x10020f05d    
2   Printer::print(MonthItem *, QWidget *)::$_0::operator()(bool) const                                                                                                                                                     printer.cpp                                                                                            207 0x100016b35    
3   QtPrivate::FunctorCall<QtPrivate::IndexesList<0>, QtPrivate::List<bool>, void, Printer::print(MonthItem *, QWidget *)::$_0>::call(Printer::print(MonthItem *, QWidget *)::$_0&, void * *)                               qobjectdefs_impl.h                                                                                     146 0x100016a88    
4   void QtPrivate::Functor<Printer::print(MonthItem *, QWidget *)::$_0, 1>::call<QtPrivate::List<bool>, void>(Printer::print(MonthItem *, QWidget *)::$_0&, void *, void * *)                                              qobjectdefs_impl.h                                                                                     256 0x100016a31    
5   QtPrivate::QFunctorSlotObject<Printer::print(MonthItem *, QWidget *)::$_0, 1, QtPrivate::List<bool>, void>::impl(int, QtPrivate::QSlotObjectBase *, QObject *, void * *, bool *)                                        qobjectdefs_impl.h                                                                                     439 0x1000169dd    
6   QMetaObject::activate(QObject *, int, int, void * *)                                                                                                                                                                    (x86_64) /Users/s710/Qt/5.12.6/clang_64/lib/QtCore.framework/Versions/5/QtCore                             0x10825353b    
7   QWebEngineView::loadFinished(bool)                                                                                                                                                                                      (x86_64) /Users/s710/Qt/5.12.6/clang_64/lib/QtWebEngineWidgets.framework/Versions/5/QtWebEngineWidgets     0x10020eb3f    

所以我觉得很愚蠢,因为到处都是崩溃,但我不知道可能出了什么问题。有人可以使用QWebEngineView 向我指出一个有效的打印功能吗?

【问题讨论】:

【参考方案1】:

在第一种方法中,我认为它失败了,因为尚未加载 HTML 或 url,并且您想要打印文本,因此可能的解决方案是使用 loadFinished 信号开始打印并使用 pdfPrintingFinished 来知道打印的时间完成了。

#include <QtWebEngineWidgets>

class Widget : public QWidget

public:
    explicit Widget(QWidget *parent = nullptr):
        QWidget(parent), button(new QPushButton), progressbar(new QProgressBar), view(new QWebEngineView)
    
        button->setText(tr("Press me"));
        button->setEnabled(false);

        connect(button, &QPushButton::clicked, this, &Widget::onClicked);
        connect(view, &QWebEngineView::loadFinished, this, &Widget::onLoadFinished);
        connect(view->page(), &QWebEnginePage::pdfPrintingFinished, this, &Widget::onPdfPrintingFinished);

        QString html = R"(<!DOCTYPE html>
                       <html>
                       <head>
                       <style>
                       table 
                       font-family: arial, sans-serif;
                       border-collapse: collapse;
                       width: 100%;
                       

                       td, th 
                       border: 1px solid #dddddd;
                       text-align: left;
                       padding: 8px;
                       

                       tr:nth-child(even) 
                       background-color: #dddddd;
                       
                       </style>
                       </head>
                       <body>

                       <h2>HTML Table</h2>

                       <table>
                       <tr>
                       <th>Company</th>
                       <th>Contact</th>
                       <th>Country</th>
                       </tr>
                       <tr>
                       <td>Alfreds Futterkiste</td>
                       <td>Maria Anders</td>
                       <td>Germany</td>
                       </tr>
                       <tr>
                       <td>Centro comercial Moctezuma</td>
                       <td>Francisco Chang</td>
                       <td>Mexico</td>
                       </tr>
                       <tr>
                       <td>Ernst Handel</td>
                       <td>Roland Mendel</td>
                       <td>Austria</td>
                       </tr>
                       <tr>
                       <td>Island Trading</td>
                       <td>Helen Bennett</td>
                       <td>UK</td>
                       </tr>
                       <tr>
                       <td>Laughing Bacchus Winecellars</td>
                       <td>Yoshi Tannamuri</td>
                       <td>Canada</td>
                       </tr>
                       <tr>
                       <td>Magazzini Alimentari Riuniti</td>
                       <td>Giovanni Rovelli</td>
                       <td>Italy</td>
                       </tr>
                       </table>

                       </body>
                       </html>
                       )";

        view->setHtml(html);
        auto lay = new QVBoxLayout(this);
        lay->addWidget(button);
        lay->addWidget(progressbar);
        lay->addWidget(view);
        resize(640, 480);
    
private:
    void onLoadFinished(bool ok)
        button->setEnabled(ok);
    
    void onClicked()
        progressbar->setRange(0, 0);
        QString fn = "/Users/s710/Downloads/test.pdf";
        view->page()->printToPdf(fn);
    
    void onPdfPrintingFinished(const QString & filename, bool ok)
        qDebug() << filename << ok;
        progressbar->setRange(0, 1);
    
private:
    QPushButton *button;
    QProgressBar *progressbar;
    QWebEngineView *view;
;

int main(int argc, char *argv[])

    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();

在您使用 QPrinter 的情况下,我认为该错误是由于 QPrinter 是一个局部变量,当函数完成执行但 Qt 尝试异步访问该变量但对象不存在时会被消除。解决方案是扩展QPrinter的范围。

#include <QtWebEngineWidgets>

class Widget : public QWidget

public:
    Widget(QWidget *parent = nullptr):
        QWidget(parent), button(new QPushButton), progressbar(new QProgressBar), view(new QWebEngineView)
    
        button->setText(tr("Press me"));
        button->setEnabled(false);

        connect(button, &QPushButton::clicked, this, &Widget::onClicked);
        connect(view, &QWebEngineView::loadFinished, this, &Widget::onLoadFinished);

        printer.setResolution(QPrinter::PrinterResolution);
        printer.setOutputFormat(QPrinter::NativeFormat);
        printer.setPaperSize(QPrinter::A4);
        printer.setPageMargins(12, 16, 12, 20, QPrinter::Millimeter);
        QString html = R"(<!DOCTYPE html>
                       <html>
                       <head>
                       <style>
                       table 
                       font-family: arial, sans-serif;
                       border-collapse: collapse;
                       width: 100%;
                       

                       td, th 
                       border: 1px solid #dddddd;
                       text-align: left;
                       padding: 8px;
                       

                       tr:nth-child(even) 
                       background-color: #dddddd;
                       
                       </style>
                       </head>
                       <body>

                       <h2>HTML Table</h2>

                       <table>
                       <tr>
                       <th>Company</th>
                       <th>Contact</th>
                       <th>Country</th>
                       </tr>
                       <tr>
                       <td>Alfreds Futterkiste</td>
                       <td>Maria Anders</td>
                       <td>Germany</td>
                       </tr>
                       <tr>
                       <td>Centro comercial Moctezuma</td>
                       <td>Francisco Chang</td>
                       <td>Mexico</td>
                       </tr>
                       <tr>
                       <td>Ernst Handel</td>
                       <td>Roland Mendel</td>
                       <td>Austria</td>
                       </tr>
                       <tr>
                       <td>Island Trading</td>
                       <td>Helen Bennett</td>
                       <td>UK</td>
                       </tr>
                       <tr>
                       <td>Laughing Bacchus Winecellars</td>
                       <td>Yoshi Tannamuri</td>
                       <td>Canada</td>
                       </tr>
                       <tr>
                       <td>Magazzini Alimentari Riuniti</td>
                       <td>Giovanni Rovelli</td>
                       <td>Italy</td>
                       </tr>
                       </table>

                       </body>
                       </html>
                       )";

        view->setHtml(html);

        auto lay = new QVBoxLayout(this);
        lay->addWidget(button);
        lay->addWidget(progressbar);
        lay->addWidget(view);
        resize(640, 480);
    
private:
    void onLoadFinished(bool ok)
        button->setEnabled(ok);
    
    void onClicked()
        progressbar->setRange(0, 0);
        QString fn = "/Users/s710/Downloads/test.pdf";
        printer.setOutputFileName(fn);
        view->page()->print(&printer, [this](bool ok)
            qDebug() << ok;
            progressbar->setRange(0, 1);
        );
    
private:
    QPushButton *button;
    QProgressBar *progressbar;
    QWebEngineView *view;
    QPrinter printer;
;

int main(int argc, char *argv[])

    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();

【讨论】:

天哪,真是个愚蠢的错误。你是对的,QPrinter 超出范围是一个问题。我回家后会试试这个。 然而,我的方法3等于你的解决方案,对吧?这种方法对我来说也崩溃了,没有对象超出范围可能导致崩溃? @user826955 我真的不知道,因为你没有提供minimal reproducible example 你的方法,我已经从你的代码中推测了很多东西。我只指出最明显的错误,所以如果你想为每个案例提供详细的答案,你必须提供一个 MRE 作为我提出的代码。也许在第三种方法中,您的代码没有显示另一个错误。 是的。谢谢你的回复,我回家再详细看看。

以上是关于如何从 QWebEngineView 打印的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Qt QWebEngineView 获取 HTTP 状态码?

如何从qwebengineview qt5.9中提取数据

从 js QWebEngineView 获取变量到 python

项目部署——宜家

从 QWebEngineView 获取 PDF 文件的链接

PyQt5 中的 QWebEngineView 和 QWidget