如何从 QWebEngineView 打印

How to print from QWebEngineView

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

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

这是我的尝试:


方法 1: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    
...

方法三:等待加载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

方法四:等待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 向我指出有效的打印功能吗?

在第一种方法中,我认为它失败了,因为 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();
}