首页 > 解决方案 > QWebEngineView 触摸滚动

问题描述

我有一个应用程序从旧的 Qt 4.7.4 移植到 Qt5,据我了解,QWebView 变成了 QWebEngineView,并且使用 QWebView 我使用了 Qt 示例中的 FlickCharm,它仍然可以与 QScrollArea(例如 QListWidget、QTableWidget , ...当然还有基本的QScrollArea),但不再使用QWebEngineView,这是激活FlickCharm的代码,它在Qt4上工作:

void FlickCharm::activateOn(QWidget *widget, QWidget* p_viewport)
{
    QAbstractScrollArea *scrollArea = qobject_cast<QAbstractScrollArea*>(widget);
    if (scrollArea) {
        // Widget is a scroll area

        QAbstractItemView *itemView = qobject_cast<QAbstractItemView*>(widget);
        if(itemView)
        {
            itemView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
            itemView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
        }

        QWidget *viewport = scrollArea->viewport();
        if ( p_viewport )
        {
            viewport = p_viewport;
        }
        else
        {
            scrollArea->installEventFilter(this);
        }

        viewport->installEventFilter(this);

        QHash<QWidget*, FlickData*>::iterator oldViewport;
        if ( ( oldViewport = d->flickData.find(viewport) ) != d->flickData.end() )
        {
            delete oldViewport.value();
        }

        d->flickData.remove(viewport);
        d->flickData[viewport] = new FlickData;
        d->flickData[viewport]->widget = widget;
        d->flickData[viewport]->state = FlickData::Steady;
        d->flickData[viewport]->customViewPort = (viewport != scrollArea->viewport());

        return;
    }

    QWebView *webView = qobject_cast<QWebView*>(widget);
    if (webView) {
        // Widget is a web view
        webView->installEventFilter(this);

        QHash<QWidget*, FlickData*>::iterator oldViewport;
        if ( ( oldViewport = d->flickData.find(webView) ) != d->flickData.end() )
        {
            delete oldViewport.value();
        }

        d->flickData.remove(webView);
        d->flickData[webView] = new FlickData;
        d->flickData[webView]->widget = webView;
        d->flickData[webView]->state = FlickData::Steady;

        return;
    }

    qWarning() << "FlickCharm only works on QAbstractScrollArea (and derived classes)";
    qWarning() << "or QWebView (and derived classes)";
}

在 FlickData 类中有以下滚动功能:

bool scrollWidget(const int dx, const int dy)
    {
        {
            QAbstractScrollArea *scrollArea = qobject_cast<QAbstractScrollArea*>(widget);
            if (scrollArea) {
                const int x = scrollArea->horizontalScrollBar()->value();
                const int y = scrollArea->verticalScrollBar()->value();
                scrollArea->horizontalScrollBar()->setValue(x - dx);
                scrollArea->verticalScrollBar()->setValue(y - dy);
                return (scrollArea->horizontalScrollBar()->value() != x
                        || scrollArea->verticalScrollBar()->value() != y);
            }
        }

        {
            QWebEngineView *webEngineView = qobject_cast<QWebEngineView*>(widget);
            if (webEngineView) {
                const QPointF position = webEngineView->page()->scrollPosition();
                const QPointF newPosition = position - QPointF(dx, dy);
                webEngineView->page()->runJavaScript(QString("window.scrollTo(%1, %2);").arg(newPosition.x()).arg(newPosition.y()));
                return webEngineView->page()->scrollPosition() != position;
            }
        }
        return false;
    }

在 Qt5 中,我尝试直接应用到 QWebEngineView,如下所示:

    QWebEngineView *webEngineView = qobject_cast<QWebEngineView*>(widget);
    if (webEngineView) {
        webEngineView->installEventFilter(this);

        QHash<QWidget*, FlickData*>::iterator oldViewport;
        if ( ( oldViewport = d->flickData.find(webEngineView) ) != d->flickData.end() )
        {
            delete oldViewport.value();
        }

        d->flickData.remove(webEngineView);
        d->flickData[webEngineView] = new FlickData;
        d->flickData[webEngineView]->widget = webEngineView;
        d->flickData[webEngineView]->state = FlickData::Steady;

        return;
    }

并且还尝试了我认为是视口的页面视图:

    QWebEngineView *webEngineView = qobject_cast<QWebEngineView*>(widget);
    if (webEngineView) {
        QWidget *viewport = webEngineView->page()->view();

        webEngineView->installEventFilter(this);
        viewport->installEventFilter(this);

        QHash<QWidget*, FlickData*>::iterator oldViewport;
        if ( ( oldViewport = d->flickData.find(viewport) ) != d->flickData.end() )
        {
            delete oldViewport.value();
        }

        d->flickData.remove(viewport);
        d->flickData[viewport] = new FlickData;
        d->flickData[viewport]->widget = webEngineView;
        d->flickData[viewport]->state = FlickData::Steady;

        return;
    }

在 QWebEngineView::page() (即 QWebEnginePage)中,有一个 scrollPosition() 函数,但这是来自 Q_PROPERTY 但没有写访问器函数,但我发现使用我尝试使用的 javascript 滚动的代码和平:

    QWebEngineView *webEngineView = qobject_cast<QWebEngineView*>(widget);
    if (webEngineView) {
        const QPointF position = webEngineView->page()->scrollPosition();
        const QPointF newPosition = position - QPointF(dx, dy);
        webEngineView->page()->runJavaScript(QString("window.scrollTo(%1, %2);").arg(newPosition.x()).arg(newPosition.y()));
        return webEngineView->page()->scrollPosition() != position;
    }

但是在添加了一些日志之后,我发现我从来没有为 QWebEngineView 传入 scrollWidget,并且,因为我有自己的类继承了实例化的 QApplication,所以我可以看到这不是直接单击的 QWebEngineView,而是 RenderWidgetHostViewQtDelegateWidget(具有 QWebEngineView作为父级),无法从任何地方访问,我看到了这一点,因为我重新实现了 MyApplication::notify 以记录进行了哪些小部件点击:

bool MyApplication::notify(QObject* p_object, QEvent* p_event)
{
    [...]
    if ( p_event->type() == QEvent::MouseButtonPress )
    {
        QWidget* widget = dynamic_cast<QWidget*>(p_object);
        if (widget != 0)
        {
            qDebug().nospace() << "Mouse pressed on [" << (widget->isEnabled() ? "enabled" :"disabled") << "] widget: "
                               << p_object
                               << ", parent: " << p_object->parent();
        }
        else
        {
            qDebug().nospace() << "Mouse pressed on object: " << p_object
                               << ", parent: " << p_object->parent();
        }
    }
    else if ( p_event->type() == QEvent::MouseButtonRelease )
    {
        QWidget* widget = dynamic_cast<QWidget*>(p_object);
        if (widget != 0)
        {
            qDebug().nospace() << "Mouse release on [" << (widget->isEnabled() ? "enabled" :"disabled") << "] widget: "
                               << p_object
                               << ", parent: " << p_object->parent();
        }
        else
        {
            qDebug().nospace() << "Mouse release on object: " << p_object
                               << ", parent: " << p_object->parent();
        }
    }
    [...]
}

此外,当我尝试在 QWebEngineView 上滚动时,会选择文本。

以下是 flickcharm 的完整代码:https ://doc.qt.io/archives/qt-4.8/qt-demos-embedded-anomaly-src-flickcharm-cpp.html

另外,我看到有一种解决方案可以使 WebEngineView 轻弹,但仅适用于 QtQuick QML:https ://stackoverflow.com/a/42817245

任何人都知道如何在 QWebEngineView 上进行触摸滚动?

谢谢

编辑:由于 RenderWidgetHostViewQtDelegateWidget 是 QWebEngineView 的子级,我尝试从 webEngineView->children() 访问它(至少作为 QWidget,如果可能的话)但没有成功,因为它唯一的子级是它的 QVBoxLayout(即为空)和轻弹魅力。

标签: qtqt5qwebengineview

解决方案


我终于设法做我想做的事,在 FlickCharm::activateOn for QWebEngineView 我放:

        QWebEngineView *webEngineView = qobject_cast<QWebEngineView*>(widget);
        if (webEngineView) {
            QLayout* webEngineViewLayout = webEngineView->layout();
            QWidget* webEngineViewChildWidget = 0;
            for (int i = 0; i < webEngineViewLayout->count(); ++i)
            {
                webEngineViewChildWidget = qobject_cast<QWidget*>(webEngineViewLayout->itemAt(i)->widget());

                if (webEngineViewChildWidget != nullptr)
                {
                    // There should be only one QWidget under QWebEngineView, but break just in case
                    break;
                }
            }

            if (webEngineViewChildWidget != nullptr)
            {
               // Install event filter on widget found in QWebEngineView layout
                webEngineViewChildWidget->installEventFilter(this);

                QHash<QWidget*, FlickData*>::iterator oldViewport;
                if ( ( oldViewport = d->flickData.find(webEngineViewChildWidget) ) != d->flickData.end() )
                {
                    delete oldViewport.value();
                }

                d->flickData.remove(webEngineViewChildWidget);
                d->flickData[webEngineViewChildWidget] = new FlickData;
                d->flickData[webEngineViewChildWidget]->widget = webEngineView;
                d->flickData[webEngineViewChildWidget]->state = FlickData::Steady;
            }
            else
            {
                // Web engine view has not yet a page loaded, activate "again" when a page has been loaded
                connect(webEngineView, &QWebEngineView::loadFinished, this, &FlickCharm::ReactToWebEngineViewLoaded);
            }

            return;
        }

并且FlickCharm::ReactToWebEngineViewLoaded()是:

void FlickCharm::ReactToWebEngineViewLoaded()
{
    QWebEngineView* webEngineView = qobject_cast<QWebEngineView*>(sender());

    if (webEngineView != nullptr)
    {
        // We need to pass only once there then we can disconnect now
        disconnect(webEngineView, &QWebEngineView::loadFinished, this, &FlickCharm::ReactToWebEngineViewLoaded);

        // Activate "again" so that view actually uses flick charm
        activateOn(webEngineView);
    }
    else
    {
        LOG_ERROR("Web engine view is NULL");
        assert(webEngineView != nullptr);
    }
}

当然 void ReactToWebEngineViewLoaded()在 flickcharm.h 中声明为插槽。

对于FlickData::scrollWidgetQWebEngineView (这很棘手,我不喜欢 qApp->processEvents 调用,但如果我们想实际比较函数返回的值,它们是强制性的)


            QWebEngineView *webEngineView = qobject_cast<QWebEngineView*>(widget);
            if (webEngineView) {
                 webEngineView->page()->runJavaScript(QString("window.scrollBy(%1, %2);").arg(-dx).arg(-dy));
                return true;
            }

推荐阅读