PyQt5でQLayoutへのQWidgetの動的追加と動的削除

PyQt5でQLayoutへのQWidgetの動的追加と動的削除を試してみました。

QLayoutにQWidgetを追加する

insertWidgetを使用して、QPushButtonがクリックされたときにQVBoxLayoutにQWidgetを追加するというプログラムを作成しました。QVBoxLayoutにはあらかじめQSpacerItemを追加しておいて、QSpacerItemの1個前のインデックスに挿入する形にしました。

例えば、QGroupBoxを一つ追加した場合

QVBoxLayout
├QGroupBox
└QSpacerItem

という構成となり、2個目を追加した場合

QVBoxLayout
├QGroupBox
├QGroupBox
└QSpacerItem

という構成になるような処理にします。追加に関するコードは以下の通りです。Layout.count()でレイアウト内のアイテムの数を取得して、その数の1個前、つまりQSpacerItemの前にQGroupBoxを挿入しています。

def addGroupBox(self):
    count = self.scrollAreaWidgetLayout.count() - 1
    groupBox = QtWidgets.QGroupBox('GroupBox ' + str(count), self.scrollAreaWidget)
    self.scrollAreaWidgetLayout.insertWidget(count, groupBox)

QLayoutからQWidgetを削除する

リファレンスを見たところ、削除の処理には下記3つの関数が使用できそうなので、それぞれ試してみました。結果的にはdeleteLaterを使用すれば正常にQWidgetの削除を行うことができました。

widget.deleteLater()で削除

deleteLaterは、オブジェクトを削除するようにスケジュールする関数で、コントロールがイベントループに戻るとオブジェクトが削除されるという処理のようです。下記のコードのように、レイアウト内のアイテムが1個より多い場合に最後のアイテムから1個前のアイテムをdeleteLaterで削除するという処理にしました。これでQWidgetを動的に削除されることを確認できました。

def deleteLaterGroupBox(self):
    count = self.scrollAreaWidgetLayout.count()
    if count == 1:
        return
    item = self.scrollAreaWidgetLayout.itemAt(count - 2)
    widget = item.widget()
    widget.deleteLater()

removeItem(self, QLayoutItem)で削除

リファレンスを見るとremoveItemもQWidgetの削除に使用できるのではないかとお思います。しかし下記のコードを実行してみたところ、QWidgetが削除されているような動作はしていますが、ウィンドウには削除したQWidgetとその子ウィジェットが描画されたままになっており、子ウィジェットも操作可能な状態になってしまいました。

def removeItemGroupBox(self):
    count = self.scrollAreaWidgetLayout.count()
    if count == 1:
        return
    item = self.scrollAreaWidgetLayout.itemAt(count - 2)
    self.scrollAreaWidgetLayout.removeItem(item)

removeWidget(self, QWidget w)で削除

removeWidgetも使えそうなので、removeItemと同じように下記のコードを実行してみましたが、removeItemと同じ動作をして、描画も子ウィジェットも残ったままとなってしまいました。リファレンスには、removeItemを呼出しした後、ウィジェットに適切なジオメトリを与えるか、ウィジェットをレイアウトに戻すが、必要に応じて明示的に非表示にすると描かれているので、QWidgetを動的に削除するような用途を想定していないのかもしれません。

def removeWidgetGroupBox(self):
    count = self.scrollAreaWidgetLayout.count()
    if count == 1:
        return
    item = self.scrollAreaWidgetLayout.itemAt(count - 2)
    widget = item.widget()
    self.scrollAreaWidgetLayout.removeWidget(widget)

ソースコード

import sys
from PyQt5 import QtWidgets, QtCore

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.centralwidget = QtWidgets.QWidget(self)
        self.centralwidgetLayout = QtWidgets.QVBoxLayout(self.centralwidget)

        self.scrollArea = QtWidgets.QScrollArea(self.centralwidget)
        self.scrollArea.setWidgetResizable(True)
        self.scrollAreaWidget = QtWidgets.QWidget()
        self.scrollAreaWidget.setGeometry(QtCore.QRect(0, 0, 780, 539))
        self.scrollAreaWidgetLayout = QtWidgets.QVBoxLayout(self.scrollAreaWidget)
        self.scrollAreaWidgetLayout.addItem(QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding))
        self.scrollArea.setWidget(self.scrollAreaWidget)

        self.buttonWidget = QtWidgets.QWidget(self.centralwidget)
        self.buttonAddGroupBox = QtWidgets.QPushButton('Add GroupBox', self.buttonWidget)
        self.buttonDeleteLaterGroupBox = QtWidgets.QPushButton('DeleteLater GroupBox', self.buttonWidget)
        self.buttonRemoveItemGroupBox = QtWidgets.QPushButton('RemoveItem GroupBox', self.buttonWidget)
        self.buttonRemoveWidgetGroupBox = QtWidgets.QPushButton('RemoveWidget GroupBox', self.buttonWidget)
        self.buttonLayout = QtWidgets.QGridLayout(self.buttonWidget)
        self.buttonLayout.addWidget(self.buttonAddGroupBox,          0, 0, 1, 1)
        self.buttonLayout.addWidget(self.buttonDeleteLaterGroupBox,  0, 1, 1, 1)
        self.buttonLayout.addWidget(self.buttonRemoveItemGroupBox,   1, 0, 1, 1)
        self.buttonLayout.addWidget(self.buttonRemoveWidgetGroupBox, 1, 1, 1, 1)
        
        self.centralwidgetLayout.addWidget(self.buttonWidget)
        self.centralwidgetLayout.addWidget(self.scrollArea)
        self.setCentralWidget(self.centralwidget)

        self.buttonAddGroupBox.clicked.connect(self.addGroupBox)
        self.buttonDeleteLaterGroupBox.clicked.connect(self.deleteLaterGroupBox)
        self.buttonRemoveItemGroupBox.clicked.connect(self.removeItemGroupBox)
        self.buttonRemoveWidgetGroupBox.clicked.connect(self.removeWidgetGroupBox)

    def addGroupBox(self):
        count = self.scrollAreaWidgetLayout.count() - 1
        groupBox = QtWidgets.QGroupBox('GroupBox ' + str(count), self.scrollAreaWidget)
        self.scrollAreaWidgetLayout.insertWidget(count, groupBox)

        comboBox = QtWidgets.QComboBox(groupBox)
        comboBox.addItems(['val1', 'val2', 'val3'])

        gridLayout = QtWidgets.QGridLayout(groupBox)
        gridLayout.addWidget(QtWidgets.QLabel('Label ' + str(count), groupBox),       0, 0, 1, 1)
        gridLayout.addWidget(QtWidgets.QLineEdit('LineEdit ' + str(count), groupBox), 0, 1, 1, 1)
        gridLayout.addWidget(comboBox,                                                1, 0, 1, 1)
        gridLayout.addWidget(QtWidgets.QSlider(QtCore.Qt.Horizontal, groupBox),       1, 1, 1, 1)

    def deleteLaterGroupBox(self):
        count = self.scrollAreaWidgetLayout.count()
        if count == 1:
            return
        item = self.scrollAreaWidgetLayout.itemAt(count - 2)
        widget = item.widget()
        widget.deleteLater()

    def removeItemGroupBox(self):
        count = self.scrollAreaWidgetLayout.count()
        if count == 1:
            return
        item = self.scrollAreaWidgetLayout.itemAt(count - 2)
        self.scrollAreaWidgetLayout.removeItem(item)

    def removeWidgetGroupBox(self):
        count = self.scrollAreaWidgetLayout.count()
        if count == 1:
            return
        item = self.scrollAreaWidgetLayout.itemAt(count - 2)
        widget = item.widget()
        self.scrollAreaWidgetLayout.removeWidget(widget)

def main():
    app = QtWidgets.QApplication(sys.argv)
    mainwindow = MainWindow()
    mainwindow.show()
    app.exec()

if __name__ == '__main__':
    main()

記事の共有

関連記事

コメント

comments powered by Disqus