单列QVBoxLayout如何修改为多列?

How to modify single column QVBoxLayout to be multi-column?

我有一列自动生成的按钮,如果按钮太多,可以挤压 window 中的 UI 个元素。因此,我想通过以下方式自动将单列按钮 - 名义上在 QVBoxLayout 内称为 self.main_layout - 转换为多列事件:

我的尝试只会导致按钮停留在一列中,但现在甚至不调整大小以填充它们占据的 QSplitter 框架:

app = QApplication(sys.argv)
window = TestCase()
app.exec_()

class TestCase(QMainWindow):
    def __init__(self):
        super().__init__()
        test = QWidget()
        self.layout = QVBoxLayout()
        test.setLayout(self.layout)
        for i in range(10):
            temp_btn = QPushButton(str(i))
            temp_btn.pressed.connect(self.multi_col)
            self.layout.addWidget(temp_btn)
        self.setCentralWidget(test)

    @pyqtSlot()
    def multi_col(self):
        cols = [QVBoxLayout(), QVBoxLayout()]
        while self.layout.count():
            child = self.layout.takeAt(0)
            if child.widget():
                self.layout.removeItem(child)
                cols[0].addItem(child)
                cols[1], cols[0] = cols[0], cols[1]
        self.layout = QHBoxLayout()
        self.layout.addLayout(cols[0])
        self.layout.addLayout(cols[1])

我这里有什么明显的错误吗?

替换 QWidget 的布局并不是那么简单,只需将另一个对象分配给存储其他布局引用的变量即可。在你正在做的几行代码中:

self.layout = Foo()
widget.setLayout(self.layout)
self.layout = Bar()

对象与变量不同,对象本身是执行动作的实体,而变量只是存储对象引用的地方。例如,对象可以是人和变量我们的名字,所以如果他们改变了我们的名字,并不意味着他们改变了我们这个人。

解决方案是使用 sip.delete 删除 QLayout,然后设置新布局:

import sys

from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QMainWindow,
    QPushButton,
    QVBoxLayout,
    QWidget,
)
import sip


class TestCase(QMainWindow):
    def __init__(self):
        super().__init__()
        test = QWidget()
        self.setCentralWidget(test)

        layout = QVBoxLayout(test)
        for i in range(10):
            temp_btn = QPushButton(str(i))
            temp_btn.pressed.connect(self.multi_col)
            layout.addWidget(temp_btn)

    @pyqtSlot()
    def multi_col(self):
        cols = [QVBoxLayout(), QVBoxLayout()]
        old_layout = self.centralWidget().layout()

        while old_layout.count():
            child = old_layout.takeAt(0)
            widget = child.widget()
            if widget is not None:
                old_layout.removeItem(child)
                cols[0].addWidget(widget)
                cols[1], cols[0] = cols[0], cols[1]
        sip.delete(old_layout)
        lay = QHBoxLayout(self.centralWidget())
        lay.addLayout(cols[0])
        lay.addLayout(cols[1])


def main():
    app = QApplication(sys.argv)
    window = TestCase()
    window.show()
    app.exec_()


if __name__ == "__main__":
    main()

我想提出一个替代解决方案,即使用 QGridLayout 并仅更改小部件的列而不是每次都设置新布局。 “技巧”是 addWidget() 总是在指定位置添加小部件,即使它已经是布局的一部分,所以您不需要删除布局项。
显然,这种方法的缺点是,如果小部件具有不同的高度,则每一行都取决于该行中所有小部件所需的最小高度,但由于 OP 是关于使用按钮的,因此情况不应如此。

这样做的主要好处是可以通过一个函数自动完成切换,可能通过设置最大列数以提供进一步的实现。
在下面的示例中,multi_col 函数实际上增加了列数,直到达到最大数,然后它再次重置为一列。

class TestCase(QMainWindow):
    def __init__(self):
        super().__init__()
        test = QWidget()
        self.layout = QGridLayout()
        test.setLayout(self.layout)
        for i in range(10):
            temp_btn = QPushButton(str(i))
            temp_btn.clicked.connect(self.multi_col)
            self.layout.addWidget(temp_btn)
        self.setCentralWidget(test)
        self.multiColumns = 3
        self.columns = 1

    def multi_col(self):
        maxCol = 0
        widgets = []
        for i in range(self.layout.count()):
            item = self.layout.itemAt(i)
            if item.widget():
                widgets.append(item.widget())
                row, col, rSpan, cSpan = self.layout.getItemPosition(i)
                maxCol = max(col, maxCol)
        if maxCol < self.multiColumns - 1:
            self.columns += 1
        else:
            self.columns = 1
        row = col = 0
        for widget in widgets:
            self.layout.addWidget(widget, row, col)
            col += 1
            if col > self.columns - 1:
                col = 0
                row += 1

注意:我更改为 clicked 信号,因为它通常比 pressed 更受青睐,因为按钮的标准约定(只有在释放鼠标按钮时才会“接受”点击 里面),在你的情况下它也会产生两个问题:

  1. 在视觉上,UI 造成混乱,按下的按钮在不同的位置变为未按下的状态(因为鼠标按钮在其实际几何形状之外释放);
  2. 概念上,因为如果用户在释放鼠标之前再次将鼠标移动到先前按下的按钮内,它将再次触发pressed