user1387866
user1387866

Reputation: 3024

Programmatic scrolling with QGraphicsView and QGraphicsItem?

I would like to programmatically scroll a scene to the left / right, but I am not sure how to do that properly. Note that I do not want to have (visible) scroll bars.

I use a standard QGraphicsView + QGraphicsScene + QGraphicsItem setup. I have downsized it to the minimum, with one single QGraphicsItem (a QGraphicsRectItem) in the scene.

I have managed to achieve programmatic scrolling by setting my view like this:

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

and then, in another part of the code:

// 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

This works, but... scrolling through invisible scrollbars doesn't feel right.

I tried this more straightforward approach:

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

This code does scroll, but when the rectangle goes off the left edge of the view, and I reverse the scrolling direction (increment changed from 100 to -100 in the call to scroll()), the uncovered part of the rectangle is not repainted. The reason is that QGraphicsRectItem::paint() is not called in that case (it is called when using the scrollbar method).

So, is there a way to get viewport()->scroll() work? Or some other simple way to achieve programmatic scrolling? Or is the artificial scrollbar method just the way to go?

Upvotes: 3

Views: 7842

Answers (3)

TheDarkKnight
TheDarkKnight

Reputation: 27639

Moving the view assumes that it's smaller than its scene. If they're the same size, it won't move.

QGraphicsView can be set to centerOn any position in scene coordinates. Use a timer to call centerOn to move the view one frame at a time.

Here's a working example: -

#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();
}

So, we create the animation by moving the view frame-by-frame using the call to centerOn.

For simplicity, the code just deals with moving in one axis. To move in 2 axis, use 2D vector maths to calculate the interval position.

Upvotes: 1

John Paine
John Paine

Reputation: 63

If I got your question correctly, there is a dojo classes library with such class as PanWebView that allow QWebView to scroll smoothly with mouse without any scrollbars. Take a look at sources. It supports panning and can be suitable for mobile apps, but maybe it'll help you too.

PanWebView class looks like this

#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);
}

};

And usage:

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

Also did you check this and this topics? I think it can be usefull.

Upvotes: 0

Tomas
Tomas

Reputation: 2210

Try to change the view transformation with the QGraphicsView::translate() or QGraphicsView::setTransform().

But keep in mind that you can't move the viewport "outside" the scene, so make sure that your scene rectangle is large enough.

Upvotes: 0

Related Questions