尽管使用了 itemsBoundingRect(),但 QGraphicsScene 和 fitInView() 无法正确缩放

Posted

技术标签:

【中文标题】尽管使用了 itemsBoundingRect(),但 QGraphicsScene 和 fitInView() 无法正确缩放【英文标题】:QGraphicsScene and fitInView() not scaling correctly despite using itemsBoundingRect() 【发布时间】:2020-07-20 16:35:50 【问题描述】:

我包含了整个代码 - 问题是(我相信!)第 145-153 行的函数 adjust_gx..

问题是 QGraphicsScene 没有正确缩放到 QGraphicsView。我希望使用一组标准形状并填充一个网格,该网格可以根据窗口或单元格大小值调整大小。 - 但它的渲染非常小,或者以一种悲惨的方式调整大小。

我找到了放大它的方法,但是当“大小”变化(范围在 16 到 120 之间)或窗口边界发生变化时,它会完全不同步。

我发现的大部分建议都是关于使用 scene.itemsBoundingRect()- 我相信我使用正确。

您可以在屏幕截图中看到绘制的图形有多小。

from itertools import product
from math import ceil
from random import choices
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtGui import QPen, QPainterPath
from PyQt5.QtWidgets import QGraphicsScene
from PyQt5.QtCore import Qt, QRect, QPointF


class Art:
    normal = 100
    w_off = 2
    line = normal / 2
    t_l = QPointF(-line + w_off, -line + w_off)
    t_r = QPointF(+line - w_off, -line + w_off)
    b_r = QPointF(+line - w_off, +line - w_off)
    b_l = QPointF(-line + w_off, +line - w_off)

    @staticmethod
    def _poly(edges):
        pt = [Art.t_r, Art.b_r, Art.b_l, Art.t_l]
        path = QPainterPath()
        path.moveTo(Art.t_l)
        for i in range(4):
            path.lineTo(pt[i]) if edges[i] else path.moveTo(pt[i])
        return path

    def __init__(self):
        self.x = 1
        self.y = 1
        self.z = 1
        self.cells = 
        self.scenes = []
        self.polygons = x: self._poly(x) for x in product((False, True), repeat=4)

    def reset(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
        self.cells = tuple((ix, iy, iz)): tuple(choices([True, False], k=4))
                      for ix in range(self.x)
                      for iy in range(self.y)
                      for iz in range(self.z)
                      
        for scene in self.scenes:
            scene.clear()

    def draw(self):
        pen = QPen(Qt.black, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
        for x, y, z in self.cells:
            cx1 = x * Art.normal
            cy1 = y * Art.normal
            shape = self.polygons[self.cells[x, y, z]]
            self.scenes[z].addPath(shape.translated(QPointF(cx1, cy1)), pen)


class MyDialog(QtWidgets.QDialog):
    """
    Is the main application window, which is made of two parts:
    One one side is the pane of tabs which display the maze
    On the other are the configurable values of the maze (levels)
    """
    def __init__(self):
        self.pane = None
        self.form = None
        self.horizontal_layout = None
        super().__init__()

    def resizeEvent(self, event):
        self.pane.resize()
        super().resizeEvent(event)

    def setup(self):
        self.pane = Pane(self)
        self.form = Form(self)
        self.setWindowTitle("Configure")
        self.setObjectName("Dialog")
        size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        size_policy.setHorizontalStretch(0)
        size_policy.setVerticalStretch(0)
        size_policy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
        self.setSizePolicy(size_policy)
        self.horizontal_layout = QtWidgets.QHBoxLayout(self)
        self.horizontal_layout.setObjectName("horizontal_layout")
        self.pane.setup(self.horizontal_layout)
        self.form.setup(self.horizontal_layout)
        self.pane.connect_form(self.form)


class Pane:
    def __init__(self, parent):
        self.parent = parent    # This is the widget.
        self.layout = None      # This is the parent's layout.
        self.form = None        # This is the form used to control things.
        self.tab_widget = None  # This is the tab widget, which can be fully replaced.
        # Everything below are default values.
        self.offset = 12
        self.demi = self.offset // 2
        self.tmp_idx = 0
        self.levels = 4
        self.width = 3
        self.height = 3
        self.cell_size = 100
        self.art = None
        self.level_tabs = []
        self.level_gx = []

    def setup(self, parent_layout):
        self.layout = parent_layout
        self.art = Art()
        tab_widget_dim = QRect(0, 0, 360, 380)  # This is the size of the initial tab_widget
        self.set_tab_widget(tab_widget_dim)

    def connect_form(self, form):
        self.form = form
        self.form.set_values('Size': self.cell_size, 'Levels': self.levels)
        self.form.set_listen('Size': self.resize, 'Levels': self.re_depth)

    def make_tab_widget(self):
        self.tab_widget = QtWidgets.QTabWidget(self.parent)
        self.tab_widget.setMinimumSize(QtCore.QSize(360, 380))
        self.tab_widget.setTabPosition(QtWidgets.QTabWidget.North)
        self.tab_widget.setObjectName("tab_widget")

    def reset_tab_widget(self):
        self.tmp_idx = 0
        prev_widget = self.tab_widget
        self.make_tab_widget()
        if prev_widget:
            self.tmp_idx = prev_widget.currentIndex()
            self.parent.horizontal_layout.replaceWidget(prev_widget, self.tab_widget)
            prev_widget.clear()
            prev_widget.close()
        else:
            self.parent.horizontal_layout.addWidget(self.tab_widget)
        if self.art:
            for lt in self.level_tabs:
                lt.close()
            self.level_tabs = []
            for gx in self.level_gx:
                gx.close()
            self.level_gx = []
            self.art.scenes = []

    def adjust_gx(self, gv, scene, frame):
        # Resize the graphics port to fit the outer frame. (GUI pixels)
        gv.resize(frame.width(), frame.height())
        # Seems to make no difference.
        boundary = scene.itemsBoundingRect()
        gv.fitInView(boundary, Qt.KeepAspectRatio)
        scene.setSceneRect(boundary)
        # Also tried the below...
        # gv.fitInView(scene.sceneRect(), Qt.KeepAspectRatio)

    def set_tab_widget(self, tab_widget_dim):
        self.reset_tab_widget()  # initialises / replaces a QTabWidget into the horizontal_layout
        for level in range(self.levels):
            tab = QtWidgets.QWidget()
            tab.setObjectName(f"Tlevel")
            self.tab_widget.addTab(tab, f"level + 1")
            self.level_tabs.append(tab)
            scene = QGraphicsScene()
            self.art.scenes.append(scene)
            tab.resize(tab_widget_dim.width(), tab_widget_dim.height())
            gv = QtWidgets.QGraphicsView(scene, tab)
            gv.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
            gv.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
            gv.setMinimumSize(QtCore.QSize(340, 340))
            gv.setObjectName(f"Gxlevel")
            self.level_gx.append(gv)
            # self.adjust_gx(gv, scene, tab_widget_dim)
        self.art.reset(self.width, self.height, self.levels)
        self.art.draw()
        for idx in range(self.levels):
            self.adjust_gx(self.level_gx[idx], self.art.scenes[idx], tab_widget_dim)
        self.tab_widget.setCurrentIndex(self.tmp_idx % self.levels)

    def resize(self):
        if self.tab_widget:
            self.tab_widget.hide()
            idx = self.tab_widget.currentIndex()
            dt = QRect(self.level_tabs[idx].frameGeometry())
            self.cell_size = self.form.get_value('Size')
            self.width = ceil((dt.width() - self.offset) // self.cell_size)
            self.height = ceil((dt.height() - self.offset) // self.cell_size)
            self.form.set_texts('Width': f"self.width", 'Height': f"self.height")
            self.set_tab_widget(dt)
            self.tab_widget.show()
            self.parent.update()

    def re_depth(self):
        self.levels = self.form.get_value('Levels')
        self.resize()


class Form(QtWidgets.QWidget):
    def __init__(self, parent):
        self.parent = parent
        self.form = None
        self.label_text = ('Levels', 'Size', 'Width', 'Height')
        self.minimums = (1, 16)
        self.maximums = (20, 320)
        self.labels = []
        self.fields = 
        super().__init__(self.parent)

    def setup(self, parent_layout):
        self.form = QtWidgets.QFormLayout(self)
        self.form.setObjectName("form")
        self.setMaximumSize(QtCore.QSize(160, 200))
        self.setMinimumSize(QtCore.QSize(160, 200))

        # Create the fields within the form layout
        for field in range(4):
            if field < 2:
                ui = QtWidgets.QSpinBox(self)
                ui.setMinimum(self.minimums[field])
                ui.setMaximum(self.maximums[field])
                ui.setMinimumSize(QtCore.QSize(60, 0))
                ui.setObjectName(f"f_field")
            else:
                ui = QtWidgets.QLabel(self)
                ui.setObjectName(f"f_field")
                ui.setText(f"0")
            self.form.setWidget(field + 1, QtWidgets.QFormLayout.FieldRole, ui)
            self.fields[self.label_text[field]] = ui
            label = QtWidgets.QLabel(self)
            label.setObjectName(f"label_field")
            label.setText(self.label_text[field])
            self.form.setWidget(field + 1, QtWidgets.QFormLayout.LabelRole, label)
            self.labels.append(label)
        parent_layout.addWidget(self)

    def get_value(self, name):
        return self.fields[name].value()

    def set_values(self, config):
        for k in config.keys():
            self.fields[k].setValue(config[k])

    def set_texts(self, config):
        for k in config.keys():
            self.fields[k].setText(config[k])

    def set_listen(self, config):
        for k in config.keys():
            self.fields[k].valueChanged.connect(config[k])


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    dialog = MyDialog()
    dialog.setup()
    dialog.show()
    result = app.exec_()

【问题讨论】:

【参考方案1】:

一个可能的解决方案是稍后执行 fitview,因为出于效率原因 Qt 不会立即更新属性:

# ...
self.art.reset(self.width,self.height,self.levels)
self.art.draw()

定义调整():
    对于范围内的 idx(self.levels):
        self.adjust_gx(self.level_gx[idx], self.art.scenes[idx], tab_widget_dim)

QtCore.QTimer.singleShot(0, 调整)

self.tab_widget.setCurrentIndex(self.tmp_idx % self.levels)
# ...

【讨论】:

这似乎行得通!我可能需要尝试一下 - 您是否有任何文档的链接来解释正在发生的事情? @Konchog 我没有找到任何文档记录,但这是我在分析 Qt 源代码后所理解的

以上是关于尽管使用了 itemsBoundingRect(),但 QGraphicsScene 和 fitInView() 无法正确缩放的主要内容,如果未能解决你的问题,请参考以下文章

QGraphicsPixmapItem未在QGraphicsView中显示

Qt中的QGraphicsScene等对象的坐标系是怎么设置的

尽管使用了工作区,但 Azure Pipeline '没有这样的模块'

java.lang.ClassNotFoundException 尽管使用了 CLASSPATH 环境变量

尽管使用了 indexInBounds,但仍获得 ArrayIndexOutOfBounds

尽管使用了 if 语句,但 RelatedObjectDoesNotExist 错误