pyside - how to delete widgets from gridLayout
问题内容:
I built a ui in QT Designer and then used pyside-uic turned it into a python
file and have then written some code to edit it programmatically. In
otherwords I have a pushbutton Add Row
that when clicked will rename itself
to Remove1
and create another pusbutton name it Add Row
and add it to the
layout.
Code when clicking Add Row
, changes the name and the signals/slots:
self.pb_Row[-1].setText('Remove'+str(self.nRows))
self.pb_Row[-1].clicked.disconnect( self.addRow )
self.pb_Row[-1].clicked.connect( self.removeRow )
Code when clicking Remove
, removes selected button:
iRow = int(self.sender().objectName().split('_')[-1])-1
ind = self.PropertyLayout.indexOf(self.pb_Row[iRow])
t = self.PropertyLayout.takeAt(ind)
t.widget().deleteLater()
# self.pb_Row[iRow].hide()
# self.pb_Row[iRow].deleteLater()
self.pb_Row.pop(iRow)
This works just fine until you add at least one and then remove it, the next
time round it messes up. Basically, it misbehaves when I have two buttons and
remove one and then try to add one. By misbehaves I mean that the new button
ends up on top of the old, sometimes it appears below instead of above.
Also, with the lines as they currently are it doesn’t really reorganise things
in the gridlayout, if I use the .hide()
function it does. I’m not quite sure
which I should be using.
Thanks!
Here is a sequence that produces undesirable results:
Fresh start
After Clicking Add
After clicking remove (all fine so far), then click Add (no visible
difference)
After clicking Add a second time
After clicking Remove2, Remove1 appears from under it
“Working” example of code
import numpy as np
import sys
from PySide import QtCore, QtGui
import matplotlib.pyplot as plt
from ModesInFiber import Ui_fiberModesMainWindow
class Ui_fiberModesMainWindow(object):
def setupUi(self, fiberModesMainWindow):
fiberModesMainWindow.setObjectName("fiberModesMainWindow")
fiberModesMainWindow.resize(653, 597)
self.centralwidget = QtGui.QWidget(fiberModesMainWindow)
self.centralwidget.setObjectName("centralwidget")
self.horizontalLayout_2 = QtGui.QHBoxLayout(self.centralwidget)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.MainLayout = QtGui.QGridLayout()
self.MainLayout.setObjectName("MainLayout")
self.PropertyLayout = QtGui.QGridLayout()
self.PropertyLayout.setObjectName("PropertyLayout")
self.lbl_Name = QtGui.QLabel(self.centralwidget)
self.lbl_Name.setObjectName("lbl_Name")
self.PropertyLayout.addWidget(self.lbl_Name, 0, 1, 1, 1)
self.pb_addRow_1 = QtGui.QPushButton(self.centralwidget)
self.pb_addRow_1.setObjectName("pb_addRow_1")
self.PropertyLayout.addWidget(self.pb_addRow_1, 1, 5, 1, 1)
self.ledit_Name_1 = QtGui.QLineEdit(self.centralwidget)
self.ledit_Name_1.setObjectName("ledit_Name_1")
self.PropertyLayout.addWidget(self.ledit_Name_1, 1, 1, 1, 1)
self.MainLayout.addLayout(self.PropertyLayout, 0, 0, 1, 1)
spacerItem2 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.MainLayout.addItem(spacerItem2, 1, 0, 1, 1)
self.horizontalLayout_2.addLayout(self.MainLayout)
fiberModesMainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(fiberModesMainWindow)
QtCore.QMetaObject.connectSlotsByName(fiberModesMainWindow)
# fiberModesMainWindow.setTabOrder(self.ledit_Name_1, self.ledit_Width_1)
# fiberModesMainWindow.setTabOrder(self.ledit_Width_1, self.cmb_RIType_1)
# fiberModesMainWindow.setTabOrder(self.cmb_RIType_1, self.ledit_RIParam_1)
# fiberModesMainWindow.setTabOrder(self.ledit_RIParam_1, self.pb_addRow_1)
def retranslateUi(self, fiberModesMainWindow):
fiberModesMainWindow.setWindowTitle(QtGui.QApplication.translate("fiberModesMainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))
self.lbl_Name.setText(QtGui.QApplication.translate("fiberModesMainWindow", "Name", None, QtGui.QApplication.UnicodeUTF8))
self.pb_addRow_1.setText(QtGui.QApplication.translate("fiberModesMainWindow", "Add Row", None, QtGui.QApplication.UnicodeUTF8))
class DesignerMainWindow(QtGui.QMainWindow, Ui_fiberModesMainWindow):
def __init__(self, parent = None):
super(DesignerMainWindow, self).__init__(parent)
self.setupUi(self)
self.pb_addRow_1.clicked.connect( self.addRow )
self.ledit_Name = [ self.ledit_Name_1 ]
self.pb_Row = [ self.pb_addRow_1 ]
# number of rows
self.nRows = 1
def addRow( self ):
self.ledit_Name[-1].setEnabled(False)
self.pb_Row[-1].setText('Remove'+str(self.nRows))
self.pb_Row[-1].clicked.disconnect( self.addRow )
self.pb_Row[-1].clicked.connect( self.removeRow )
self.nRows += 1
self.ledit_Name.append( QtGui.QLineEdit(self.centralwidget) )
self.ledit_Name[-1].setObjectName('ledit_Name_'+str(self.nRows))
self.PropertyLayout.addWidget( self.ledit_Name[-1], self.nRows, 1, 1, 1)
self.pb_Row.append( QtGui.QPushButton(self.centralwidget) )
self.pb_Row[-1].setObjectName( 'pb_addRow_'+str(self.nRows) )
self.pb_Row[-1].setText('Add Row')
self.pb_Row[-1].clicked.connect( self.addRow )
self.PropertyLayout.addWidget( self.pb_Row[-1], self.nRows, 5, 1, 1)
def removeRow( self ):
iRow = int(self.sender().objectName().split('_')[-1])-1
self.nRows -= 1
ind = self.PropertyLayout.indexOf(self.ledit_Name[iRow])
t = self.PropertyLayout.takeAt(ind)
t.widget().setParent(None)
# t.widget().deleteLater()
# self.ledit_Name[iRow].hide()
# self.ledit_Name[iRow].deleteLater()
# self.ledit_Name[iRow].setParent(None)
self.ledit_Name.pop(iRow)
ind = self.PropertyLayout.indexOf(self.pb_Row[iRow])
t = self.PropertyLayout.takeAt(ind)
t.widget().setParent(None)
# t.widget().deleteLater()
# self.pb_Row[iRow].hide()
# self.pb_Row[iRow].deleteLater()
# self.pb_Row[iRow].setParent(None)
self.pb_Row.pop(iRow)
for iAfterRow in range(iRow, self.nRows):
self.ledit_Name[iAfterRow].setObjectName( 'ledit_Name_' + str(iAfterRow+1) )
self.pb_Row[iAfterRow].setObjectName( 'ledit_Name_' + str(iAfterRow+1) )
print 'Remove row', iRow
if __name__ == '__main__':
app = QtGui.QApplication( sys.argv )
dmw = DesignerMainWindow()
dmw.show()
sys.exit( app.exec_() )
问题答案:
The problem here is caused by an implementation detail of QGridLayout.
Whenever items are deleted from a QGridLayout, the number of logical rows
and columns will never decrease, even though the number of visual rows or
colums may do. Because of this, you should always work directly with the items
in the QGridLayout using methods such as getItemPosition and
itemAtPosition.
Below is a re-write of the DesignerMainWindow
class from the example using
this approach. Obviously, it may need tweaking somewhat to work with your real
application.
class DesignerMainWindow(QtGui.QMainWindow, Ui_fiberModesMainWindow):
def __init__(self, parent = None):
super(DesignerMainWindow, self).__init__(parent)
self.setupUi(self)
self.pb_addRow_1.clicked.connect( self.addRow )
def addRow( self ):
rows = self.PropertyLayout.rowCount()
columns = self.PropertyLayout.columnCount()
for column in range(columns):
layout = self.PropertyLayout.itemAtPosition(rows - 1, column)
if layout is not None:
widget = layout.widget()
if isinstance(widget, QtGui.QPushButton):
widget.setText('Remove %d' % (rows - 1))
widget.clicked.disconnect(self.addRow)
widget.clicked.connect(self.removeRow)
else:
widget.setEnabled(False)
widget = QtGui.QLineEdit(self.centralwidget)
self.PropertyLayout.addWidget(widget, rows, 1, 1, 1)
widget = QtGui.QPushButton(self.centralwidget)
widget.setText('Add Row')
widget.clicked.connect(self.addRow)
self.PropertyLayout.addWidget(widget, rows, columns - 1, 1, 1)
def removeRow(self):
index = self.PropertyLayout.indexOf(self.sender())
row = self.PropertyLayout.getItemPosition(index)[0]
for column in range(self.PropertyLayout.columnCount()):
layout = self.PropertyLayout.itemAtPosition(row, column)
if layout is not None:
layout.widget().deleteLater()
self.PropertyLayout.removeItem(layout)