QT QWebEngine在滚动后呈现? [英] QT QWebEngine render after scrolling?

查看:1119
本文介绍了QT QWebEngine在滚动后呈现?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用WebEngineView保存网页的图像效果很好,但是当我要滚动并保存另一个图像时,生成的图像不会显示网站已经滚动(它显示网页的顶部)。

Saving an image of a webpage with a WebEngineView works fine, but when I want to scroll and save another image, the resulting image does not show the website has been scrolled (it shows the top of the webpage).

我的问题是:如何在QWebEngineView中向下滚动,然后保存屏幕截图,显示正确滚动的网页?

My question is: how do I scroll down in the QWebEngineView then save a screen shot that shows the correctly scrolled webpage?

我在网页顶部截图,向下滚动大约700像素,等待javascript回调触发,然后再采取另一个屏幕截图。 javascript和回调工作正常(我观察到QWebEngineView滚动)。

I take a screenshot at the top of the webpage, scroll down ~700 pixels, wait for a javascript callback to trigger which then takes another screenshot. The javascript and callback works fine (I observe the QWebEngineView scrolling).

    this->setScrollPageHandlerFunc([&] (const QVariant &result) {
        saveSnapshotScroll();
    });
    saveSnapshotScroll();
    view->page()->runJavaScript("scrollPage();",this->scrollPageHandlerFunc);

截图代码:

void MainWindow::saveSnapshotScroll()
{

QPixmap pixmap(this->size());
view->page()->view()->render(&pixmap);
pixmap.save(QString::number(QDateTime::currentMSecsSinceEpoch()) + ".png");

}

Javascript:

Javascript:

function scrollPage()
{
    var y = qt_jq.jQuery(window).scrollTop();
    qt_jq.jQuery(window).scrollTop(y+708);
}

UPDATE:将saveSnapshotScroll()放在〜100ms或更长的定时器上(即在滚动后等待100ms保存快照),而不是一旦页面滚动就截取屏幕截图,它工作。因此,在执行滚动时的JavaScript回调和滚动页面的呈现之间存在一些延迟。我不会称这是一个完整的解决方案,因此我只是更新的职位。我真正想要的是来自QT的回调,表示呈现的网页已经在屏幕缓冲区中更新。

UPDATE: I've found that if I put the saveSnapshotScroll() on a timer of ~100ms or more (i.e. wait 100ms to save the snapshot after scrolling), instead of taking the screenshot as soon as the page is scrolled, it works. So there is some latency between the javascript callback when a scroll has been performed, and the rendering of the scrolled page. I wouldn't call this a complete solution and thus why I'm only updating the post. What I would really like is a callback from QT that says the rendered webpage has been updated in the screen buffer. Does something like this exist?

推荐答案

runJavaScript 的回调是触发脚本完成。但是,窗口应该重新绘制(或至少准备重绘)使用 QWidget :: render(& pixmap)

When the callback of runJavaScript is triggered the script is finished. However, the window should be repainted (or at least prepared for repainting) to use QWidget::render(&pixmap).

看起来,一些绘图事件可以用于检测窗口小部件的重绘。不幸的是, QWebEngineView 几乎不捕获任何事件(除了鼠标进入和退出,最近添加了未处理的键盘事件),例如参见[QTBUG-43602] WebEngineView不处理鼠标事件

It seems that some paint event can be useful to detect repainting of the widget. Unfortunately the QWebEngineView does not catch almost any event (except mouse enter and exit, recently added unhandled keyboard events), for example see "[QTBUG-43602] WebEngineView does not handle mouse events".

所有事件(如鼠标移动或绘画)由 QWebEngineView 处理私有类型 RenderWidgetHostViewQtDelegateWidget 的子代理, code> QOpenGLWidget 。

Almost all events (like mouse move or paint) are handled by QWebEngineView child delegate of private type RenderWidgetHostViewQtDelegateWidget that is derived from QOpenGLWidget.

可以捕获 QWebEngineView的新子类型 QOpenGLWidget ,并在此子项上安装所有需要的事件的事件过滤器挂钩。

It is possible to catch new child of QWebEngineView of type QOpenGLWidget and to install on this child the event filter hook for all needed events.

QWebEngineView 的无文档结构。因此,它可能不被未来的Qt版本支持。但是,它可用于具有当前Qt版本的项目。也许在将来有一些更方便的接口捕获 QWebEngineView 事件将被实现。

That solution relies on undocumented structure of QWebEngineView. Thus it may be not supported by future Qt releases. However, it is usable for projects with current Qt versions. Maybe in the future some more convenient interface to catch QWebEngineView events will be implemented.

#ifndef WEBENGINEVIEW_H
#define WEBENGINEVIEW_H

#include <QEvent>
#include <QChildEvent>
#include <QPointer>
#include <QOpenGLWidget>
#include <QWebEngineView>

class WebEngineView : public QWebEngineView
{
    Q_OBJECT

private:
    QPointer<QOpenGLWidget> child_;

protected:
    bool eventFilter(QObject *obj, QEvent *ev)
    {
        // emit delegatePaint on paint event of the last added QOpenGLWidget child
        if (obj == child_ && ev->type() == QEvent::Paint)
            emit delegatePaint();

        return QWebEngineView::eventFilter(obj, ev);
    }

public:
    WebEngineView(QWidget *parent = nullptr) :
        QWebEngineView(parent), child_(nullptr)
    {
    }

    bool event(QEvent * ev)
    {
        if (ev->type() == QEvent::ChildAdded) {
            QChildEvent *child_ev = static_cast<QChildEvent*>(ev);

            // there is also QObject child that should be ignored here;
            // use only QOpenGLWidget child
            QOpenGLWidget *w = qobject_cast<QOpenGLWidget*>(child_ev->child());
            if (w) {
                child_ = w;
                w->installEventFilter(this);
            }
        }

        return QWebEngineView::event(ev);
    }

signals:
    void delegatePaint();
};

#endif // WEBENGINEVIEW_H

$ c> WebEngineView :: event 。保存子指针,并在此子节点上安装事件过滤器。在child paint事件中,在 WebEngineView :: eventFilter 中发出信号 WebEngineView :: delegatePaint

Child adding is caught by WebEngineView::event. The child pointer is saved and the event filter is installed on this child. On the child paint event the signal WebEngineView::delegatePaint is emitted in WebEngineView::eventFilter.

当某些脚本或某些网络控件的突出显示更改了网页视图时,由于鼠标悬停或网页控件而改变了信息 delegatePaint

The signal delegatePaint is always emitted when the web view is changed by some script or by highlighting of some web controls due to mouse hover or for any other reason.

在实际执行之前从事件过滤器发出信号QOpenGLWidget :: paintEvent()。所以,它看起来需要只在完成绘制完成后采取页面快照(也许使用异步 Qt :: QueuedConnection 连接)。看来,在事件过滤器的这一点,因为JavaScript触发 delegatePaint 小部件已准备好 render()。但是,由于某些其他原因(例如由于窗口激活),可能会收到paint事件,并且可能会导致警告消息:

The signal is emitted from the event filter before actual execution of QOpenGLWidget::paintEvent(). So, it looks that it is needed to take page snapshot only after full paint is finished (maybe using asynchronous Qt::QueuedConnection connection). It appears that at this point in the event filter when delegatePaint is triggered because of the JavaScript the widget is ready for render(). However, it possible to receive the paint event for some other reason (for example due to window activation) and that may lead to the warning message:


QWidget :: repaint:检测到递归重绘

QWidget::repaint: Recursive repaint detected

所以,最好使用 Qt :: QueueConnection 以避免此类问题。

So, it is still better to use Qt::QueuedConnection to avoid such issues.

现在诀窍是使用事件 delegatePaint 只有一次当JavaScipt完成。该部分可以根据实际需求进行调整。

Now the trick is to use the event delegatePaint only once when the JavaScipt is finished. That part can be adjusted to actual requirements.

由于某些脚本或加载新图片,页面视图可能随时重新绘制。让我们假设我们需要捕获页面在脚本执行后的外观。因此,可以在脚本回调中将 delegatePaint 信号连接到 saveSnapshotScroll 槽,并断开 saveSnapshotScroll 。以下测试在三个不同滚动位置的循环中生成快照。类似的快照由文件夹 0 1 2

The page view can be repainted at any moment due to some scripts or loading new images. Let's assume that we need to capture the page how it looks after the script execution. So, it possible to connect delegatePaint signal to saveSnapshotScroll slot only in the script callback and disconnect that connection in saveSnapshotScroll. The following test generates snapshots in a loop for three different scroll positions. The similar snapshots are orginized by folders 0, 1 and 2:

void MainWindow::runJavaScript()
{
    // count initialized by 0
    if (++count > 1000)
        return;

    QString script = QString::asprintf("window.scrollTo(0, %d);", 708 * (count % 3));

    view->page()->runJavaScript(script,
        [&] (const QVariant&) {
            connect(view, &WebEngineView::delegatePaint,
                    this, &MainWindow::saveSnapshotScroll,
                    Qt::QueuedConnection);
        }
    );
}

void MainWindow::saveSnapshotScroll()
{
    disconnect(view, &WebEngineView::delegatePaint,
               this, &MainWindow::saveSnapshotScroll);

    QPixmap pixmap(view->size());
    view->render(&pixmap);
    pixmap.save(QString::number(count % 3) + "/" +
                QString::number(QDateTime::currentMSecsSinceEpoch()) + ".png");

    runJavaScript();
}

在这种情况下,当事件被其他窗口交互触发时,得到错误的快照。如果在脚本执行期间窗口没有被触摸,则结果是正确的。

In those cases when the event is trigged by some other window interaction it is possible to get wrong snapshot. If the window is not touched during script execution the result is correct.

为了避免处理错误的绘画事件,比较Web视图像素映射与以前保存的图像。如果这些图像之间的差异小,这意味着应当跳过当前绘画事件,并且需要等待下一个绘画事件:

To avoid handling of wrong paint events it is possible to compare Web view pixmap with previously saved image. If difference between those images is small it means that the current paint event should be skipped and it is needed to wait for the next paint event:

void MainWindow::saveSnapshotScroll()
{
    QSharedPointer<QPixmap> pixmap(new QPixmap(view->size()));
    view->render(pixmap.data());

    // wait for another paint event if difference with saved pixmap is small
    if (!isNewPicture(pixmap))
        return;

    pixmap->save(QString::number(count % 3) + "/" +
              QString::number(QDateTime::currentMSecsSinceEpoch()) + ".png");

    disconnect(view, &WebEngineView::delegatePaint,
               this, &MainWindow::saveSnapshotScroll);

    runJavaScript();
}

bool MainWindow::isNewPicture(QSharedPointer<QPixmap> pixmap)
{
    // initialized by nullptr
    if (!prevPixmap) {
        prevPixmap = pixmap;
        return true;
    }

    // <pixmap> XOR <previously saved pixmap>
    QPixmap prev(*prevPixmap);
    QPainter painter;
    painter.begin(&prev);
    painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination);
    painter.drawPixmap(0, 0, *pixmap);
    painter.end();

    // check difference
    QByteArray buf;
    QBuffer buffer(&buf);
    buffer.open(QIODevice::WriteOnly);
    prev.save(&buffer, "PNG");

    // almost empty images (small difference) have large compression ratio
    const int compression_threshold = 50;
    bool isNew = prev.width() * prev.height() / buf.size() < compression_threshold;

    if (isNew)
        prevPixmap = pixmap;

    return isNew;
}

上面的解决方案只是一个例子,它是基于Qt提供的工具。可以考虑其他比较算法。还可以针对特定情况调整相似性阈值。如果滚动视图与上一个图像非常相似(例如在长空白空间的情况下),则存在这种比较的限制。

The above solution is just an example and it is based on tools provided by Qt. It is possible to think about other comparison algorithms. Also similarity threshold may be adjusted to specific case. There is a limitation of such comparison if scrolled view is very similar to the previous image (for example in case of long empty space).

这篇关于QT QWebEngine在滚动后呈现?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆