のコールバックrunJavaScript
がトリガーされると、スクリプトは終了します。ただし、 を使用するには、ウィンドウを再描画する (または少なくとも再描画の準備をする) 必要がありますQWidget::render(&pixmap)
。
ウィジェットの再描画を検出するには、一部のペイント イベントが役立つようです。残念ながら、QWebEngineView
はほとんどすべてのイベントをキャッチしません (マウスの開始と終了、最近追加された未処理のキーボード イベントを除く)。
ほとんどすべてのイベント (マウスの移動やペイントなど) は、 から派生したQWebEngineView
プライベート タイプの子デリゲートによって処理されます。RenderWidgetHostViewQtDelegateWidget
QOpenGLWidget
QWebEngineView
タイプの新しい子をキャッチQOpenGLWidget
し、必要なすべてのイベントのイベント フィルター フックをこの子にインストールすることができます。
その解決策は、文書化されていない の構造に依存していQWebEngineView
ます。したがって、将来の Qt リリースではサポートされない可能性があります。ただし、現在の Qt バージョンのプロジェクトでは使用できます。将来的には、QWebEngineView
イベントをキャッチするためのより便利なインターフェイスが実装される可能性があります。
次の例は、その魔法を実装しています。
#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
子の追加が に引っかかりWebEngineView::event
ます。子ポインタが保存され、イベント フィルタがこの子にインストールされます。子ペイント イベントでは、信号WebEngineView::delegatePaint
が で発行されWebEngineView::eventFilter
ます。
delegatePaint
何らかのスクリプトによって、またはマウス ホバーやその他の理由により一部の Web コントロールが強調表示されることによって、Web ビューが変更されると、常にシグナルが発せられます。
信号は、 の実際の実行前にイベント フィルタから発信されQOpenGLWidget::paintEvent()
ます。そのため、フル ペイントが完了した後にのみページのスナップショットを取得する必要があるようです (非同期Qt::QueuedConnection
接続を使用する可能性があります)。この時点でdelegatePaint
、JavaScript が原因でイベント フィルターがトリガーされたときに、ウィジェットの準備ができているように見えますrender()
。ただし、他の理由 (ウィンドウのアクティブ化など) でペイント イベントを受け取る可能性があり、次の警告メッセージが表示される場合があります。
QWidget::repaint: 再帰的な再描画が検出されました
したがって、Qt::QueuedConnection
このような問題を回避するために使用することをお勧めします。
ここでの秘訣はdelegatePaint
、JavaScipt が終了したときにイベントを 1 回だけ使用することです。その部分は、実際の要件に合わせて調整できます。
ページ ビューは、スクリプトや新しい画像の読み込みにより、いつでも再描画できます。スクリプトの実行後にページがどのように見えるかをキャプチャする必要があると仮定しましょう。そのため、スクリプト コールバックでのみdelegatePaint
信号をsaveSnapshotScroll
スロットに接続し、その接続を で切断することができますsaveSnapshotScroll
。次のテストでは、3 つの異なるスクロール位置のループでスナップショットを生成します。同様のスナップショットは、フォルダー0
、1
およびによって編成され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();
}
イベントが他のウィンドウ インタラクションによってトリガーされた場合、間違ったスナップショットを取得する可能性があります。スクリプトの実行中にウィンドウが触れられていない場合、結果は正しいです。
誤った描画イベントの処理を避けるために、Web ビューのピックスマップを以前に保存した画像と比較することができます。これらの画像の違いが小さい場合は、現在のペイント イベントをスキップする必要があり、次のペイント イベントを待つ必要があることを意味します。
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 が提供するツールに基づいています。他の比較アルゴリズムについて考えることができます。また、類似度のしきい値は、特定のケースに合わせて調整することもできます。スクロールされたビューが前の画像と非常に似ている場合 (たとえば、長い空白の場合)、このような比較には制限があります。