Programmatic QGraphicsView scrolling not updating

2019-02-17 05:44发布

问题:

I have a custom class derived from QGraphicsView that implements a slot call scrollHorizontal(int dx), inside the code is simply

void CustomView::scrollHorizontal(int dx){
    scrollContentsBy(dx, 0);
}

My problem is, scrolling like this works but doesn't update the scene properly, instead any pixels found on the edge of the view are repeated instead of having a fresh call to the item's paint() method.

I've attempted calling update() after, but nothing happens. I tried enabling scrolling by dragging and updates work fine! But I need it done programmatically, and since I have the scroll bars hidden things like horizontalScrollBar()->setValue() do not scroll the view.

I also tried :

scrollContentsBy(dx, 0);
this->scene()->invalidate(sceneRect());
this->update();

update:

QPointF center = mapToScene(viewport()->rect().center()); 
centerOn(center.x() - dx, center.y()); 
update(); 

is working, but now my top view is scrolling slower than my bottom view, which is a new problem. They are linked with signals and slots, in the bottom view i have scrollContentsBy(int dx, int dy) overrided to emit horizontalScroll(dx); which is caught by the above slot in the top view.

Any ideas why the scrolls happen at different rates? Might it have something to do with the scroll bars being a part of the bottom view effectively making it a "smaller" window?

update 2:

The different scroll rates seems to stem from some rounding happening to give me an integer based "center" using mapToScene(viewport()->rect().center()); , as you scroll and the slower you scroll the more this error adds up, the faster you scroll the less total error.

Is there a way for me to get around this? I don't see any way to get a floating point center point.

update 3:

So I have this mostly solved, turns out the mapToScene was needed(code I found elsewhere on the web).

I fixed this by storing QPointF of FP calculated center of the viewport, now the amount of error when scrolling the two views is unnoticeable.

The final issue is, the views no longer line up when you scroll ANY amount to the right, and then resize the window then scroll again. I assume this has something to do with the logical ordering of when the center point is calculated and when the centering happens.

Right now I use the following code snippet in QGraphicsScene::ResizeEvent() and elsewhere that updates the center as needed

QRectF viewPort(viewport()->rect());
QPointF rectCenter((viewPort.x() + viewPort.x() + viewPort.width())/2.0, (viewPort.y() + viewPort.y() + viewPort.height())/2.0);

viewCenter = rectCenter;

and my horizontalScroll(int dx) slot

void CustomView::horizontalScroll(int dx)
{
    viewCenter.setX(viewCenter.x() - dx);
    centerOn(viewCenter.x(), viewCenter.y());
    update();
}

How can I fix the issue when re-sizing the window breaking the alignment of the two views? If any more clarification is needed please just ask, I can try to give skeletons of what I'm referring to if need be.

Update 4:

Rough code Skeleton

Class HeaderView:

class HeaderView View : public QGraphicsView
{
    Q_OBJECT
public:
    HeaderView(QWidget * parent = 0);
    HeaderView(QGraphicsScene * scene, QWidget * parent = 0);

private:
    QPointF viewCenter;

protected:
    void resizeEvent ( QResizeEvent * event );

public slots:
    void horizontalScroll(int);
    void addModel(qreal, qreal, const QString&);

};

HeaderView.cpp

void HeaderView::resizeEvent(QResizeEvent *event)
{
    QGraphicsView::resizeEvent(event);
    QRectF viewPort(viewport()->rect());
    QPointF rectCenter((viewPort.x() + viewPort.x() + viewPort.width())/2.0, (viewPort.y() + viewPort.y() + viewPort.height())/2.0);

    viewCenter = rectCenter;
}

void HeaderView::horizontalScroll(int dx)
{
    viewCenter.setX(viewCenter.x() - dx);
    centerOn(viewCenter.x(), viewCenter.y());
    update();
}

Class EventView:

class EventView : public QGraphicsView
{
Q_OBJECT
public:
    EventView(QWidget * parent = 0);
    EventView(QGraphicsScene * scene, QWidget * parent = 0);
    QRectF visibleRect();

protected:
     void scrollContentsBy ( int dx, int dy );

signals:
    void horizontalScroll(int);

};

EventView.cpp

void EventView::scrollContentsBy(int dx, int dy)
{
    QGraphicsView::scrollContentsBy(dx, dy);

    if(dx != 0){
        emit horizontalScroll(dx);
    }
}

Somwhere in Class MainWindow:

connect(eventView, SIGNAL(horizontalScroll(int)), headerView, SLOT(horizontalScroll(int));

回答1:

I've worked with QGraphicsView in Qt 4.6.3 - 4.7.2 and have to argue that you can use the respective QScrollBar in the following way:

//graphics view initialization
QGraphicsView *graphicsView = new QGraphicsView(parent);
QGraphicsScene *scene = new QGraphicsScene(0,0,widthOfScene,heightOfScene,parent);
graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
graphicsView->setScene(scene);

//in another method
QScrollBar* yPos=graphicsView->verticalScrollBar();
yPos->setValue((int) newValue);

It does not matter if they are hidden or not. They will still respond to setValue(int) as long as you have a graphics scene that is larger than the graphics view.

The QGraphicsView will also respond to ensureVisible, which moves the scrollbars to the appropriate location.



回答2:

You are not supposed to call scrollContentsBy as explained here: http://qt-project.org/doc/qt-4.8/qabstractscrollarea.html#scrollContentsBy

I don't know if you can still call the hidden scrollbar to scroll it. If not, translate is an option.



回答3:

Did you try to use the scroll bars? Hiding them doesn't make them non-existent, and the documentation says you should use QScrollBar::setValue to scroll to a given position.

Another option would be to use QGraphicsView::centerOn(QPointF) in conjunction with the current center point -- as you've also tried -- but directly calculating the center point within your method (do not precalculate and store the center point), by using QGraphicsView::mapToScene(int,int):

void CustomView::horizontalScroll(int dx)
{
    QPointF viewCenter = mapToScene(width() / 2, height() / 2);
    viewCenter += QPointF(dx, 0); // Why did you subtract instead of add dx?
    centerOn(viewCenter); // BTW, you don't need to do .x(), .y()
    // You can remove update(); as this is already called in centerOn().
}

Please note that if you have, as you said, "scrollContentsBy(int dx, int dy) overrided to emit horizontalScroll(dx)", you also have to call the super class method so that the view can scroll itself:

void CustomView::scrollContentsBy(int dx, int dy)
{
    emit horizontalScrolled(dx); // (You should call it different than the slot!)
    QGraphicsView::scrollContentsBy(dx, dy); // <-- This is what I mean!
}