【问题标题】:Programmatic scrolling with QGraphicsView and QGraphicsItem?使用 QGraphicsView 和 QGraphicsItem 进行编程滚动?
【发布时间】:2016-01-28 21:54:08
【问题描述】:

我想以编程方式将场景向左/向右滚动,但我不确定如何正确执行此操作。请注意,我确实想要(可见)滚动条。

我使用标准的QGraphicsView + QGraphicsScene + QGraphicsItem 设置。我已将其缩小到最低限度,场景中有一个 QGraphicsItemQGraphicsRectItem)。

我已经设法通过这样设置我的视图来实现程序化滚动:

// view setup
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

然后,在另一部分代码中:

// programmatic scrolling
QScrollBar* const sb = view->horizontalScrollBar();
sb->setRange(0, 1000); // some values for experimenting
sb->setValue(sb->value() + 100 or -100); // some increment for experimenting

这行得通,但是...滚动不可见的滚动条感觉不对。

我尝试了这种更直接的方法:

// programmatic scrolling - doesn't quite work
view->viewport()->scroll(100 or -100, 0); // some increment for experimenting

这段代码确实滚动,但是当矩形离开视图的左边缘时,我反转滚动方向(在对scroll()的调用中,增量从100更改为-100),未覆盖的部分矩形的不重绘。原因是这种情况下没有调用QGraphicsRectItem::paint()(使用滚动条方法时调用)。

那么,有没有办法让viewport()->scroll() 工作?或者其他一些实现程序化滚动的简单方法?或者人工滚动条方法只是要走的路?

【问题讨论】:

    标签: qt scroll graphics


    【解决方案1】:

    移动视图假定它小于其场景。如果它们的大小相同,它就不会移动。

    QGraphicsView可以设置为centerOn场景坐标中的任何位置。使用计时器调用centerOn 将视图一次移动一帧。

    这是一个工作示例:-

    #include <QApplication>
    #include <QGraphicsScene>
    #include <QGraphicsView>
    #include <QGraphicsRectItem>
    #include <QTimer>
    
    class MyView : public QGraphicsView
    {
    private:
    
    public:
        MyView(QGraphicsScene* pScene)
            : QGraphicsView(pScene, NULL)
        {}
    
        void AnimateBy(int x)
        {
            float updateFrequency = (1000/30.0); // ~30 frames per second
    
            QPointF currScenePos = sceneRect().center();
    
            int curX = currScenePos.x();
            int endPos = curX + x;
    
            int distanceToAnimate = (endPos - curX);
    
            // speed = dist / time
            float updatePosInterval = (float)distanceToAnimate / updateFrequency;
    
            printf("updatePosInterval: %f \n", updatePosInterval);
    
            static float newXPos = sceneRect().center().x();
    
            QTimer* pTimer = new QTimer;
            QObject::connect(pTimer, &QTimer::timeout, [=](){
    
                newXPos += updatePosInterval;
                centerOn(newXPos, sceneRect().center().y());   
    
                // check for end position or time, then....
                if(newXPos >= endPos)
                {
                    pTimer->stop();
                    pTimer->deleteLater();
                }
    
            });
            pTimer->start(updateFrequency);
        }
    };
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
        QGraphicsScene scene(0, 0, 10000, 20000);
        MyView* view = new MyView(&scene);
    
        QGraphicsRectItem* pRect = new QGraphicsRectItem(0, 0, 100, 100);
        pRect->setPos(scene.width()/2, scene.height()/2);
        scene.addItem(pRect);
    
        // timer to wait for the window to appear, before starting to move
        QTimer* pTimer = new QTimer;
        pTimer->setSingleShot(true);
    
        QObject::connect(pTimer, &QTimer::timeout,[=](){
    
            view->centerOn(pRect); // centre on the rectangle
            view->AnimateBy(100);
            pTimer->deleteLater();
        });
    
        pTimer->start(1000);
        view->show();
    
        return a.exec();
    }
    

    因此,我们通过调用 centerOn 逐帧移动视图来创建动画。

    为简单起见,代码只处理在一个轴上移动。要在 2 轴上移动,请使用 2D 矢量数学计算区间位置。

    【讨论】:

    • 是的,centerOn() 可以。它基于滚动条(在我的例子中是不可见的)。我认为这是人为的,但似乎这就是 Qt 中编程滚动的方式......
    【解决方案2】:

    尝试使用QGraphicsView::translate()QGraphicsView::setTransform() 更改视图转换。

    但请记住,您不能将视口移到“场景之外”,因此请确保您的场景矩形足够大。

    【讨论】:

    • 没有任何效果:(
    • 你的场景矩形有多大 (QGraphicsScene::sceneRect()) ?如果它完全在视图内,则无法滚动。但是如果它比视图大并且你只看到它的一部分,你应该能够使用这个 QGraphicView::translate() 方法到达场景矩形的边界。
    • 即使我将场景设置为(远)大于视图,view-&gt;translate() 也不会移动视图的内容。还有一个额外的问题:view-&gt;translate() 导致 QGraphicsRectItem::paint() 被调用,即使整个矩形都可见。滚动条方法没有这个问题(它内部用memmove()移动像素,效率更高)。
    【解决方案3】:

    如果我没有正确回答您的问题,那么有一个带有 PanWebView 等类的 dojo 类库,它允许 QWebView 使用鼠标平滑滚动而无需任何滚动条。 Take a look at sources。它支持平移,可以适用于移动应用程序,但也许它也会帮助你。

    PanWebView 类看起来像这样

    #include <QWebView>
    #include <QWebFrame>
    #include <QMouseEvent>
    #include <QApplication>
    
    class PanWebView : public QWebView
    {
        Q_OBJECT
    
    private:
        bool pressed;
        bool scrolling;
        QPoint position;
        QPoint offset;
        QList<QEvent*> ignored;
    
    public:
        PanWebView(QWidget *parent = 0): QWebView(parent), pressed(false),     scrolling(false) {
        QWebFrame *frame = page()->mainFrame();
        frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
        frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
    }
    
    protected:
    
    void mousePressEvent(QMouseEvent *mouseEvent) {
    
        if (ignored.removeAll(mouseEvent))
            return QWebView::mousePressEvent(mouseEvent);
    
        if (!pressed && !scrolling && mouseEvent->modifiers() == Qt::NoModifier)
            if (mouseEvent->buttons() == Qt::LeftButton) {
                pressed = true;
                scrolling = false;
                position = mouseEvent->pos();
                QWebFrame *frame = page()->mainFrame();
                int x = frame->evaluateJavaScript("window.scrollX").toInt();
                int y = frame->evaluateJavaScript("window.scrollY").toInt();
                offset = QPoint(x, y);
                QApplication::setOverrideCursor(Qt::OpenHandCursor);
                return;
            }
    
        return QWebView::mousePressEvent(mouseEvent);
    }
    
    void mouseReleaseEvent(QMouseEvent *mouseEvent) {
    
        if (ignored.removeAll(mouseEvent))
            return QWebView::mouseReleaseEvent(mouseEvent);
    
        if (scrolling) {
            pressed = false;
            scrolling = false;
            QApplication::restoreOverrideCursor();
            return;
        }
    
        if (pressed) {
            pressed = false;
            scrolling = false;
    
            QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress,
                                                  position, Qt::LeftButton,
                                                  Qt::LeftButton, Qt::NoModifier);
            QMouseEvent *event2 = new QMouseEvent(*mouseEvent);
    
            ignored << event1;
            ignored << event2;
            QApplication::postEvent(this, event1);
            QApplication::postEvent(this, event2);
            QApplication::restoreOverrideCursor();
            return;
        }
    
        return QWebView::mouseReleaseEvent(mouseEvent);
    }
    
    void mouseMoveEvent(QMouseEvent *mouseEvent) {
    
        if (scrolling) {
            QPoint delta = mouseEvent->pos() - position;
            QPoint p = offset - delta;
            QWebFrame *frame = page()->mainFrame();
            frame- >evaluateJavaScript(QString("window.scrollTo(%1,%2);").arg(p.x()).arg(p.y()));
            return;
        }
    
        if (pressed) {
            pressed = false;
            scrolling = true;
            return;
        }
    
        return QWebView::mouseMoveEvent(mouseEvent);
    }
    
    };
    

    及用法:

    PanWebView web;
    web.setUrl(QUrl("http://news.google.com"));
    web.setWindowTitle("Web View - use mouse to drag and pan around");
    web.show();
    

    您还检查了thisthis 主题吗?我认为它可能很有用。

    【讨论】:

    • 您在答案最后一行给出的两个链接分别使用centerOn()ensureVisible()。但这两种方法在内部使用滚动条,所以与我在代码 sn-p 中给出的方法相比,并没有什么新东西。
    猜你喜欢
    • 2016-04-25
    • 1970-01-01
    • 1970-01-01
    • 2011-08-04
    • 2013-10-13
    • 1970-01-01
    • 1970-01-01
    • 2014-07-04
    • 1970-01-01
    相关资源
    最近更新 更多