Qt5中show()后QGraphicsView/QGraphicsScene初始定位的基本问题

Posted

技术标签:

【中文标题】Qt5中show()后QGraphicsView/QGraphicsScene初始定位的基本问题【英文标题】:Basic problem with initial positioning of QGraphicsView/QGraphicsScene after show() in Qt5 【发布时间】:2019-11-02 12:00:36 【问题描述】:

我为板子的可视化编写了一些代码,但我在初始化时遇到了问题。我想从头到尾生成充满木板的视图(是否显示在第三张图片上)。我尝试使用许多 Qt5 方法但没有结果(我是 Qt5 的初学者)。第一次调整大小后视图看起来很完美。

我不知道我在初始化时做错了什么。

就在 .show() 之后:

失焦后(我转写这个问题):

调整大小后,它应该看起来像/我想要实现的目标:

如何修复此代码以使其在初始化时工作 - 我是 Qt5 的第 1 级(初学者)和编程的第 7 级。也许它需要非常简单的改变。

这是 Python 3.8/Qt5 的工作代码:

import logging
import sys
import typing

from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import QSize, QPoint, Qt, QRect, QMargins
from PyQt5.QtGui import QFont, QPaintEvent, QPainter, QBrush, QColor, QPen
from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QSizePolicy, QVBoxLayout, QHBoxLayout, QGraphicsWidget, \
    QGraphicsScene, QGraphicsView, QGraphicsGridLayout, QStyleOptionGraphicsItem, QGraphicsSceneMouseEvent


class Application(QApplication):
    pass


class SquareWidget(QGraphicsWidget):
    def __init__(self, color):
        super().__init__()
        if color:
            self.color = QtCore.Qt.white
        else:
            self.color = QtCore.Qt.black

    def paint(self, painter: QtGui.QPainter, option: QStyleOptionGraphicsItem, widget: typing.Optional[QWidget] = ...) -> None:
        painter.fillRect(option.rect, self.color)


class BoardContainer(QGraphicsWidget):
    def __init__(self):
        super().__init__()
        logging.debug('size is %s for %s.', self.size(), self.__class__.__name__)

        grid = QGraphicsGridLayout()
        grid.setSpacing(0)
        grid.setContentsMargins(0, 0, 0, 0)
        self.setLayout(grid)
        for row in range(8):
            for column in range(8):
                square_widget = SquareWidget((row + column) % 2)
                grid.addItem(square_widget, row, column)


class BoardScene(QGraphicsScene):
    def __init__(self):
        super().__init__()

        self.board_container = board_container = BoardContainer()
        self.addItem(board_container)


class BoardView(QGraphicsView):
    def __init__(self):
        super().__init__()
        logging.debug('size is %s for %s.', self.size(), self.__class__.__name__)

        scene = BoardScene()
        self.setScene(scene)
        # no frame
        self.setFrameShape(0)
        # transparent background
        # self.setStyleSheet('QGraphicsView background: transparent;')
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

    def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
        super().resizeEvent(event)
        self.fitInView(self.scene().board_container, Qt.KeepAspectRatio)


class BoardWidget(QWidget):
    def __init__(self):
        super().__init__()
        logging.debug('size is %s for %s.', self.size(), self.__class__.__name__)

        grid = QGridLayout()

        board_view = BoardView()
        grid.addWidget(board_view, 0, 0)

        self.setLayout(grid)


def main():
    # show exceptions
    def excepthook(cls, exception, traceback):
        sys.__excepthook__(cls, exception, traceback)
    sys.excepthook = excepthook

    logging.basicConfig(level=logging.DEBUG)
    app = Application(sys.argv)
    app.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)

    default_font = QFont()
    default_font.setPointSize(12)
    app.setFont(default_font)

    board_widget = BoardWidget()
    board_widget.setMinimumSize(640, 640)
    board_widget.show()

    sys.exit(app.exec())


if __name__ == '__main__':
    main()

【问题讨论】:

【参考方案1】:

试试看:

import logging
import sys
import typing

from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import QSize, QPoint, Qt, QRect, QMargins
from PyQt5.QtGui import QFont, QPaintEvent, QPainter, QBrush, QColor, QPen
from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QSizePolicy, QVBoxLayout, QHBoxLayout, QGraphicsWidget, \
    QGraphicsScene, QGraphicsView, QGraphicsGridLayout, QStyleOptionGraphicsItem, QGraphicsSceneMouseEvent


class Application(QApplication):
    pass


class SquareWidget(QGraphicsWidget):
    def __init__(self, color):
        super().__init__()

        self.resize(640, 640)                                    # +++        

        if color:
            self.color = QtCore.Qt.white
        else:
            self.color = QtCore.Qt.black

    def paint(self, painter: QtGui.QPainter, option: QStyleOptionGraphicsItem, widget: typing.Optional[QWidget] = ...) -> None:
        painter.fillRect(option.rect, self.color)


class BoardContainer(QGraphicsWidget):
    def __init__(self):
        super().__init__()
        logging.debug('size is %s for %s.', self.size(), self.__class__.__name__)

        self.resize(640, 640)                                     # +++
        self.setMinimumSize(80, 80)                               # +++       


        grid = QGraphicsGridLayout()
        grid.setSpacing(0)
        grid.setContentsMargins(0, 0, 0, 0)
        self.setLayout(grid)
        for row in range(8):
            for column in range(8):
                square_widget = SquareWidget((row + column) % 2)
                grid.addItem(square_widget, row, column)


class BoardScene(QGraphicsScene):
    def __init__(self):
        super().__init__()

        self.board_container = board_container = BoardContainer()
        self.addItem(board_container)


class BoardView(QGraphicsView):
    def __init__(self):
        super().__init__()
        logging.debug('size is %s for %s.', self.size(), self.__class__.__name__)

        scene = BoardScene()
        self.setScene(scene)
        # no frame
        self.setFrameShape(0)
        # transparent background
        # self.setStyleSheet('QGraphicsView background: transparent;')
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

    def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
        super().resizeEvent(event)
        self.fitInView(self.scene().board_container, Qt.KeepAspectRatio)


class BoardWidget(QWidget):
    def __init__(self):
        super().__init__()
        logging.debug('size is %s for %s.', self.size(), self.__class__.__name__)

        grid = QGridLayout()

        board_view = BoardView()
        grid.addWidget(board_view, 0, 0)

        self.setLayout(grid)


def main():
    # show exceptions
    def excepthook(cls, exception, traceback):
        sys.__excepthook__(cls, exception, traceback)
    sys.excepthook = excepthook

    logging.basicConfig(level=logging.DEBUG)
    app = Application(sys.argv)
    app.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)

    default_font = QFont()
    default_font.setPointSize(12)
    app.setFont(default_font)

    board_widget = BoardWidget()
    board_widget.setMinimumSize(640, 640)
    board_widget.show()

    sys.exit(app.exec())


if __name__ == '__main__':
    main()

【讨论】:

答案也很好。 fitInView 只有一个问题会导致与第二个答案相同的边距。【参考方案2】:

由于网格没有关于 BoardContainer 大小的线索,它无法在 0 大小上正确布局项目。 您可以将 BoardContainer 的大小显式设置为您想要的任何大小。 这是代码的固定部分:

class BoardContainer(QGraphicsWidget):
    def __init__(self):
        super().__init__()
        self.resize(100,100)
        logging.debug('size is %s for %s.', self.size(), self.__class__.__name__)
        grid = QGraphicsGridLayout()
        grid.setSpacing(0)
        grid.setContentsMargins(0, 0, 0, 0)
        self.setLayout(grid)
        for row in range(8):
            for column in range(8):
                square_widget = SquareWidget((row + column) % 2)
                grid.addItem(square_widget, row, column)
        grid.activate()  

毕竟,调用 grid.activate() 会强制根据大小进行布局。

为了鼓励您使用 QML,这里有一个带有精美动画的同一应用程序的最小 QML 示例,以演示如何在 QML 上轻松使用动画。 如果你从这个实现中删除添加的动画,让它变得像你的代码的 c++ 版本,那么它只有 30 行代码,恕我直言。

import QtQml 2.12
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12

ApplicationWindow 

    id:mywin
    visible: true
    width: 640
    height: 640
    minimumWidth: 640
    minimumHeight: 480
    title: qsTr("Fancy Board")

    Item 
        anchors.centerIn: parent
        width: Math.min(parent.width,parent.height);
        height: width

        GridLayout
            id : grid
            anchors.fill: parent
            rows: 8
            columns: 8
            rowSpacing: 0
            columnSpacing: 0
            Repeater
                model: 64
                Rectangle
                    Layout.fillWidth: true
                    Layout.fillHeight: true
                    color: ((index%8) - (index/8 | 0)) %2 === 0 ? 'black' : 'white'

                    Rectangle
                        anchors.fill: parent
                        opacity: mouseArea.containsMouse ? 0.5 : 0
                        scale: mouseArea.containsMouse? 1 : 0
                        color: 'red'
                        Behavior on opacity
                            NumberAnimation duration: 300 
                        
                        Behavior on scale 
                            NumberAnimation duration: 300; easing.type: Easing.OutCubic
                        
                    
                    MouseArea
                        id: mouseArea
                        anchors.fill: parent
                        hoverEnabled: true
                    
                
            
        
    

您也可以将此代码复制并粘贴到 main.qml 文件中,将其压缩,然后将其上传到https://qt-webassembly.io/designviewer/ 以在您的浏览器上运行它,看看它也可以在您的浏览器下运行。

【讨论】:

我尝试将 setSceneRect 添加到场景或视图的代码中,设置后没有任何变化 - 你确定它会起作用吗?可以加这行代码测试一下吗? @Chameleon,我只是想测试一下。我正在安装 Python 3.8 以保持与您的环境相同。 @Chameleon,我认为调用 fitInView 是不合适的。让我尝试另一种方法,我会更新你。 我在 Python 3.7 上对其进行了测试(文本拼写错误,但我认为 3.8 无关紧要)。 fitInView 可能错误但有效(底部 1 像素大小行中有一些伪影)。 您需要从 x=0 和 y=0 使用 setSceneRect,然后您的小部件将不再居中。

以上是关于Qt5中show()后QGraphicsView/QGraphicsScene初始定位的基本问题的主要内容,如果未能解决你的问题,请参考以下文章

Qt5 paintEvent 传播到子小部件

QGraphicsView 适合任何大小的图像,没有滚动条

QGraphicsView Scale & QGraphicsRectItem 绘画未能调用

如何摆脱QT5中的默认窗口

重新实现 mousePressEvent 后无法在 QGraphicsView 中拖动项目

对话框关闭后嵌入式 QGraphicsView 不隐藏