最初在 QGraphics 中移动滚动条

Posted

技术标签:

【中文标题】最初在 QGraphics 中移动滚动条【英文标题】:Initially moved scroll bar inside QGraphics 【发布时间】:2019-09-08 22:25:32 【问题描述】:

我正在尝试设置最初在 QGraphicsScene 小部件内移动的垂直和水平滚动条。以下代码应该移动条形并将它们设置在中间,但它们不会移动:

from PyQt5 import QtCore, QtGui, QtWidgets
import sys


class Diedrico(QtWidgets.QWidget):
    def paintEvent(self, event):
        qp = QtGui.QPainter(self)
        pen = QtGui.QPen(QtGui.QColor(QtCore.Qt.black), 5)
        qp.setPen(pen)
        qp.drawRect(500, 500, 1000, 1000)


class UiVentana(QtWidgets.QMainWindow):

    def __init__(self, parent=None):
        super(UiVentana, self).__init__(parent)

        self.resize(1000, 1000)
        self.setFixedSize(1000, 1000)

        self.scene = QtWidgets.QGraphicsScene(self)
        self.view = QtWidgets.QGraphicsView(self.scene)
        # This two lines should move the scroll bar
        self.view.verticalScrollBar().setValue(500)
        self.view.horizontalScrollBar().setValue(500)
        self.diedrico = Diedrico()
        self.diedrico.setFixedSize(2000, 2000)

        self.scene.addWidget(self.diedrico)

        self.setCentralWidget(self.view)

    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_R:
            self.view.setTransform(QtGui.QTransform())

        elif event.key() == QtCore.Qt.Key_Plus:
            scale_tr = QtGui.QTransform()
            scale_tr.scale(1.5, 1.5)
            tr = self.view.transform() * scale_tr
            self.view.setTransform(tr)

        elif event.key() == QtCore.Qt.Key_Minus:
            scale_tr = QtGui.QTransform()
            scale_tr.scale(1.5, 1.5)
            scale_inverted, invertible = scale_tr.inverted()
            if invertible:
                tr = self.view.transform() * scale_inverted
                self.view.setTransform(tr)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    ui = UiVentana()
    ui.show()
    sys.exit(app.exec_())

当我使用滚动区域时,我可以移动条形图,例如 question

【问题讨论】:

【参考方案1】:

@S.Nick 给出的答案很好,但我想补充一些关于您为什么会面临这个问题以及“幕后”发生的事情的见解。

首先,在您的代码中,您尝试在将任何对象添加到场景之前设置滚动条的值。 那时,您刚刚创建了视图和场景。视图小部件尚未显示(因此它还不“知道”它的实际大小),并且场景是空的,这意味着 sceneRectnull,如 0 宽度和 0高度:在这种情况下,滚动条的最大值为 0,设置任何值都不会产生任何结果。

注意:有一个非常需要牢记的重要方面:除非 明确声明或请求sceneRect QGraphicsScene 总是 null 直到视图显示它。并且通过 “请求”我的意思是即使只是打电话给scene.sceneRect() 足以确保场景实际上并最终“知道”它 范围。

尝试设置滚动条后(没有结果),您将小部件添加到场景中。问题是视图(它是 QAbstractScrollArea 的后代)只有在实际映射到屏幕上时才会更新其滚动条。 这是一个复杂的“path”,它从显示主父 window(如果有)开始,它根据其内容调整自身大小,并再次调整其内容的大小,如果他们需要它,最终基于他们的 [嵌套小部件] 大小策略。只有然后,视图“决定”是否需要滚动条,并最终设置它们的最大值。而且,只有然后您才能真正为这些滚动条设置一个值,这是因为只有视图“询问”场景关于它的sceneRect。 这也(部分地)解释了为什么视图的行为方式与标准滚动区域不同:小部件有一个 sizeHint 被包含在滚动区域内的 QWidget 使用,并且理论上,它们的大小在它们被创建。 但是。这取决于它们的大小提示和策略,因此在最终映射/显示之前,您无法保证实际的滚动区域内容大小;长话短说:它“有效”,但并不完美 - 至少在所有内容最终展示之前不会。

一个测试例子

根据您的需要和实施,有不同的方法可以解决您的问题。

独立设置 sceneRect,甚至在向场景添加任何对象之前(但如果这些对象边界超出场景,您将面临一些不一致) 如上所述调用scene.sceneRect()添加所有对象后 仅在视图显示并调整大小后设置 scoll 条

我准备了一个示例来显示上述三种情况。它将创建一个新视图并根据复选框在不同点更新其滚动条,以显示它们的行为方式有何不同。请注意,在设置 sceneRect 时,我使用了一个小于小部件大小的矩形来更好地显示其行为:您可以看到“设置场景矩形”和“检查场景矩形”的视觉结果相似,但滚动条位置不同.

import sys
from PyQt5 import QtCore, QtGui, QtWidgets

class Diedrico(QtWidgets.QWidget):
    def paintEvent(self, event):
        qp = QtGui.QPainter(self)
        pen = QtGui.QPen(QtGui.QColor(QtCore.Qt.black), 5)
        qp.setPen(pen)
        qp.drawRect(500, 500, 1000, 1000)

class TestView(QtWidgets.QGraphicsView):
    def __init__(self, setRect=False, checkScene=False, showEventCheck=False):
        super(TestView, self).__init__()
        self.setFixedSize(800, 800)
        scene = QtWidgets.QGraphicsScene()
        self.setScene(scene)
        self.diedrico = Diedrico()
        self.diedrico.setFixedSize(2000, 2000)
        scene.addWidget(self.diedrico)
        if setRect:
            scene.setSceneRect(0, 0, 1500, 1500)
        elif checkScene:
            scene.sceneRect()
        self.showEventCheck = showEventCheck
        if not showEventCheck:
            self.scroll()

    def scroll(self):
        self.verticalScrollBar().setValue(500)
        self.horizontalScrollBar().setValue(500)    

    def showEvent(self, event):
        super(TestView, self).showEvent(event)
        if not event.spontaneous() and self.showEventCheck:
            self.scroll()

class ViewTester(QtWidgets.QWidget):
    def __init__(self):
        QtWidgets.QWidget.__init__(self)
        layout = QtWidgets.QVBoxLayout()
        self.setLayout(layout)

        self.setRectCheck = QtWidgets.QCheckBox('Set scene rect')
        layout.addWidget(self.setRectCheck)

        self.checkSceneCheck = QtWidgets.QCheckBox('Check scene rect')
        layout.addWidget(self.checkSceneCheck)

        self.showEventCheck = QtWidgets.QCheckBox('Scroll when shown')
        layout.addWidget(self.showEventCheck)

        showViewButton = QtWidgets.QPushButton('Show view')
        layout.addWidget(showViewButton)
        showViewButton.clicked.connect(self.showView)
        self.view = None

    def showView(self):
        if self.view:
            self.view.close()
            self.view.deleteLater()
        self.view = TestView(
            setRect = self.setRectCheck.isChecked(), 
            checkScene = self.checkSceneCheck.isChecked(), 
            showEventCheck = self.showEventCheck.isChecked()
            )
        self.view.show()

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    viewTester = ViewTester()
    viewTester.show()
    sys.exit(app.exec_())

最后,请记住,对滚动条使用绝对值并不是一个好主意。如果您想将视图“居中”,请考虑使用centerOn(及其item based overload),或根据scrollBar.maximum()/2 设置值。

【讨论】:

你的回答比较准确和完整,也谢谢你最后的部分,很有用【参考方案2】:

您想在小部件尚未形成时设置值,请稍等。

import sys
from PyQt5 import QtCore, QtGui, QtWidgets


class Diedrico(QtWidgets.QWidget):
    def paintEvent(self, event):
        qp = QtGui.QPainter(self)
        pen = QtGui.QPen(QtGui.QColor(QtCore.Qt.black), 5)
        qp.setPen(pen)
        qp.drawRect(500, 500, 1000, 1000) 


class UiVentana(QtWidgets.QMainWindow):

    def __init__(self, parent=None):
        super(UiVentana, self).__init__(parent)

        self.resize(1000, 1000)
        self.setFixedSize(1000, 1000)

        self.scene = QtWidgets.QGraphicsScene(self)
        self.view  = QtWidgets.QGraphicsView(self.scene)

        # This two lines should move the scroll bar
        QtCore.QTimer.singleShot(0, self.set_Value)                   # +++

        self.diedrico = Diedrico()
        self.diedrico.setFixedSize(2000, 2000)

        self.scene.addWidget(self.diedrico)

        self.setCentralWidget(self.view)

    def set_Value(self):                                              # +++
        self.view.verticalScrollBar().setValue(500)
        self.view.horizontalScrollBar().setValue(500)    

    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_R:
            self.view.setTransform(QtGui.QTransform())

        elif event.key() == QtCore.Qt.Key_Plus:
            scale_tr = QtGui.QTransform()
            scale_tr.scale(1.5, 1.5)
            tr = self.view.transform() * scale_tr
            self.view.setTransform(tr)

        elif event.key() == QtCore.Qt.Key_Minus:
            scale_tr = QtGui.QTransform()
            scale_tr.scale(1.5, 1.5)
            scale_inverted, invertible = scale_tr.inverted()
            if invertible:
                tr = self.view.transform() * scale_inverted
                self.view.setTransform(tr)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    ui = UiVentana()
    ui.show()
    sys.exit(app.exec_())

【讨论】:

为什么当滚动条位于图形场景而不是滚动区域内时,必须以不同的方式完成?

以上是关于最初在 QGraphics 中移动滚动条的主要内容,如果未能解决你的问题,请参考以下文章

设置滚动区域最初移动

滚动条中的中心缩略图(jQuery)

如何从 JetBrains IDEA 的滚动条中删除大部分亮点?

当将相对尺寸设置为滚动条时,在滚动条中缩放图像会调整滚动条的大小

android中的垂直和水平滚动视图

android中的垂直和水平滚动视图