如何将单列 QVBoxLayout 修改为多列?

问题描述

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

  • self.main_layout删除按钮
  • 将它们添加到由 QVBoxLayouts 表示的交替新列
  • self.main_layout 更改为 QHBoxLayout
  • 向此布局添加新列

我的尝试只是导致按钮停留在单列中,但现在甚至不调整大小以填充它们占据的 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. 在视觉上,用户界面会造成混乱,按下的按钮在不同的位置变为未按下状态(因为鼠标按钮在其实际位置之外释放,并且此时是不同的几何形状);
  2. 概念上,因为如果用户在释放鼠标之前再次在先前按下的按钮内移动鼠标,它会再次触发pressed