PyQt5 - 适当地动态调整和布置组件

时间:2017-04-22 20:37:18

标签: python qt pyqt qt5 pyqt5

我正在尝试制作一个能够显示(并最终让用户构建)电路的GUI。下面是应用程序应该是什么样子的粗略草图。

Sketch of application

底部面板(目前是一个简单的QToolBar)应该是恒定的高度,但是跨越应用程序的宽度,侧面板(下面代码中的IOPanel)应该具有恒定的宽度,跨越应用程序的高度。

应用程序的主要部分(Canvas,当前为QWidget,具有覆盖paintEvent方法,但可能最终成为QGraphicsScene且{{1}或者至少可以滚动的东西)然后填充剩余的空间。

这是我目前的代码:

QGraphicsView

运行该代码会给我以下内容:

Result

在这里,我对组件着色,以便在构造中使用这样的代码更好地看到它们:

from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt, QSize


class MainWindow(QMainWindow):
    def __init__(self, *args):
        super().__init__(*args)
        self._wire_ys = None
        self._init_ui()
        self.update_wire_ys()

    def update_wire_ys(self):
        self._wire_ys = [(i + 0.5) * self.panel.height() / 4 for i in range(4)]
        self.input.update_field_positions()
        self.output.update_field_positions()

    def wire_ys(self):
        return self._wire_ys

    def _init_ui(self):
        self.panel = QWidget(self)
        self.canvas = Canvas(self, self.panel)
        self.input = IOPanel(self, self.panel)
        self.output = IOPanel(self, self.panel)

        hbox = QHBoxLayout(self.panel)
        hbox.addWidget(self.canvas, 1, Qt.AlignCenter)
        hbox.addWidget(self.input, 0, Qt.AlignLeft)
        hbox.addWidget(self.output, 0, Qt.AlignRight)

        self.setCentralWidget(self.panel)
        self.addToolBar(Qt.BottomToolBarArea, self._create_run_panel())
        self.reset_placement()

    def _create_run_panel(self):
        # some other code to create the toolbar
        return QToolBar(self)

    def reset_placement(self):
        g = QDesktopWidget().availableGeometry()
        self.resize(0.4 * g.width(), 0.4 * g.height())
        self.move(g.center().x() - self.width() / 2, g.center().y() - self.height() / 2)

    def resizeEvent(self, *args, **kwargs):
        super().resizeEvent(*args, **kwargs)
        self.update_wire_ys()


class IOPanel(QWidget):
    def __init__(self, main_window, *args):
        super().__init__(*args)
        self.main = main_window
        self.io = [Field(self) for _ in range(4)]

    def update_field_positions(self):
        wire_ys = self.main.wire_ys()
        for i in range(len(wire_ys)):
            field = self.io[i]
            field.move(self.width() - field.width() - 10, wire_ys[i] - field.height() / 2)

    def sizeHint(self):
        return QSize(40, self.main.height())


class Field(QLabel):
    def __init__(self, *args):
        super().__init__(*args)
        self.setAlignment(Qt.AlignCenter)
        self.setText(str(0))
        self.resize(20, 20)


# This class is actually defined in another module and imported
class Canvas(QWidget):
    def __init__(self, main_window, *args):
        super().__init__(*args)
        self.main = main_window

    def paintEvent(self, e):
        print("ASFD")
        qp = QPainter()
        qp.begin(self)
        self._draw(qp)
        qp.end()

    def _draw(self, qp):
        # Draw stuff
        qp.drawLine(0, 0, 1, 1)


# __main__.py
def main():
    import sys
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

绿色是中央面板(p = self.palette() p.setColor(self.backgroundRole(), Qt.blue) self.setPalette(p) self.setAutoFillBackground(True) ),蓝色是MainWindow.panelIOPanel应该是红色,Field应该是白。

忽略底部工具栏,它是我上面未包含的一些额外代码(尽可能保持最小和相关性),但除了自己的子{{1}外,它不会调整任何大小和布局管理。 }。事实上,在上面的最小例子中包括绘画代码给出了类似的结果,底部工具栏更薄而没有“运行”按钮。我只是在这里包含工具栏,以便在常规布局中显示其预期的行为(因为工具栏工作正常)。

这个结果有几个问题。

问题1

Canvas最初未显示。 然而,一旦我调整主窗口的大小,它们就会显示(并且适当地放置在各自的面板中)。为什么是这样?主窗口QWidget执行的唯一操作是FieldresizeEvent,这些操作也由主窗口的update_wire_ys执行。

问题2

update_field_positions未正确对齐。第一个应位于中央面板的左侧。更改添加它们的顺序修复了这个问题,如下所示:

__init__

但是,IOPanel不应该这样做,不管他们添加的顺序如何?如果我以后想要在左侧添加另一个面板,我是否必须删除所有组件,添加新面板然后重新添加它们?

问题3

hbox.addWidget(self.input, 0, Qt.AlignLeft) hbox.addWidget(self.canvas, 1, Qt.AlignCenter) hbox.addWidget(self.output, 0, Qt.AlignRight) 的大小不正确。它们需要跨越中央面板的整个高度并触摸中央面板的左/右边缘。我不确定这是否是布局或面板着色的问题。我做错了什么?

问题4

Qt.AlignX根本没有显示,实际上它的IOPanel从未被调用(“ASFD”永远不会被打印到控制台)。我没有覆盖它的Canvas,因为我希望中心面板的布局能够适当地调整paintEvent的大小。我希望在添加组件时拉伸系数为1可以实现这一目标。

sizeHint

如何让画布实际显示并填充中央面板上的所有剩余空间?

1 个答案:

答案 0 :(得分:1)

这是典型的意大利面条代码,其中许多元素纠结,通常难以测试,我发现许多问题,例如sizeEvent仅在调用包含小部件的布局时调用,另一个例子是当您使用处理彼此对象的函数update_field_positionsupdate_wire_ys时。

在这个答案中,我将提出一个更简单的实现:

  • IOPanel clas必须包含处理图像大小更改的QVBoxLayout

  • MainWindow课程中,我们将使用带有对齐的布局,但您必须按顺序添加它们。

    lay.addWidget(self.input, 0, Qt.AlignLeft)
    lay.addWidget(self.canvas, 0, Qt.AlignCenter)
    lay.addWidget(self.output, 0, Qt.AlignRight)
    
  • 要为IOPanel设置最小宽度,我们使用QSizePolicy()setMinimumSize()

完整代码:

from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *

class Field(QLabel):
    def __init__(self, text="0", parent=None):
        super(Field, self).__init__(parent=parent)
        self.setAlignment(Qt.AlignCenter)
        self.setText(text)


class IOPanel(QWidget):
    numbers_of_fields = 4
    def __init__(self, parent=None):
        super(IOPanel, self).__init__(parent=None)
        lay = QVBoxLayout(self)
        for _ in range(self.numbers_of_fields):
            w = Field()
            lay.addWidget(w)

        self.setMinimumSize(QSize(40, 0))
        sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
        self.setSizePolicy(sizePolicy)



class Panel(QWidget):
    def __init__(self, parent=None):
        super(Panel, self).__init__(parent=None)
        lay = QHBoxLayout(self)
        self.input = IOPanel()
        self.output = IOPanel()
        self.canvas = QWidget()

        lay.addWidget(self.input, 0, Qt.AlignLeft)
        lay.addWidget(self.canvas, 0, Qt.AlignCenter)
        lay.addWidget(self.output, 0, Qt.AlignRight)


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent=parent)
        self.initUi()
        self.reset_placement()

    def initUi(self):
        panel = Panel(self)
        self.setCentralWidget(panel)
        self.addToolBar(Qt.BottomToolBarArea, QToolBar(self))

    def reset_placement(self):
        g = QDesktopWidget().availableGeometry()
        self.resize(0.4 * g.width(), 0.4 * g.height())
        self.move(g.center().x() - self.width() / 2, g.center().y() - self.height() / 2)


def main():
    import sys
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

截图:

enter image description here