4

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 Image 1

After Clicking Add After clicking Add

After clicking remove (all fine so far), then click Add (no visible difference) after remove 1

After clicking Add a second time enter image description here

After clicking Remove2, Remove1 appears from under it enter image description here

"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_() )
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
evan54
  • 3,585
  • 5
  • 34
  • 61
  • Could you define what you mean by "misbehaves"? What is the error you are seeing? I've seen an issue in my own code where removing an item from a grid layout, and adding another one in its place, results in both the old and the new item being drawn on top of each other. Is this what you see? – three_pineapples Jan 19 '14 at 06:43
  • Yes!! This is exactly what I see! It's driving me nuts – evan54 Jan 19 '14 at 07:19
  • Cool, I'll post an answer to show you how I fixed it! – three_pineapples Jan 19 '14 at 07:20

3 Answers3

3

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)
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • firstly thanks! Almost there, so if i understand correctly there are still additional rows. Now am I correct to assume that your method doesn't actually get rid of these rows but just finds a way around this? The reason I say this is because the number next to the `Remove` button always increases – evan54 Jan 19 '14 at 23:23
  • ok i think that row count only goes up as per [here](http://www.qtcentre.org/threads/31297-rowCount-does-not-update-after-removing-widgets-from-grid-layout) – evan54 Jan 19 '14 at 23:41
  • @evan54. Yes, it's an implementation detail, as I said in my answer: added rows don't get deleted. I suppose you could re-use rows by deleting the widgets and leaving the layout-items in there. But that would make things a lot more complicated to manage. – ekhumoro Jan 20 '14 at 00:04
1

EDIT: This doesn't solve the problem, also I found the source of my knowledge: Is there any way to remove a QWidget in a QGridLayout?

-- For some reason, removing widgets from layouts is hard. I spent a long time searching for the answer once, and no longer remember where I found it, but I digress...

What you need to do is the following. First find the item you want to remove. You can use layout.itemAt() or layout.itemAtPosition to get a reference to it.

Now, to remove the item from the layout, simply call item.widget().setParent(None). This will have the effect of removing the item from the layout!

Note: If you already have a reference to the widget, you can probably just call setParent on it without having to find it from the layout. I haven't tried that though (but don't see why it wouldn't work).

Community
  • 1
  • 1
three_pineapples
  • 11,579
  • 5
  • 38
  • 75
  • unfortunately this didn't make a difference :( , I'm taking some more screen shots to demonstrate what is happening – evan54 Jan 19 '14 at 07:38
  • Can you click on "remove 1" when it shows up the second time? Is it behaving as a button or just drawn there? I think my solution will work, but perhaps your GUI structure is a little more complicated and you just haven't called `setParent(None)` on everything you should have? Could you post a more complete code example (aka a minimislistic working example) that I could run to test? – three_pineapples Jan 19 '14 at 07:55
  • let me try coming up with the minimalistic code example, also once I click on remove1 it works as expected, but the issue is that sometimes they appear as above then maybe below etc ie the placement of the buttons ends up all wrong even though they are there – evan54 Jan 19 '14 at 08:10
  • just added some working code, tried to make it as minimalistic as possible but still quite lengthy – evan54 Jan 19 '14 at 08:20
  • Ok, I'm wrong, it is something else that is wrong, but I'm not sure what yet. I don't have much time to look at it in detail right now, but should again in a bit over 12 hours. If no-one else has solved it by then, I'll take another crack at it – three_pineapples Jan 19 '14 at 09:56
  • Sorry, I've looked at it some more, tried a bunch of things and I can't make it work. I've tried with both PyQt4 and PySide and both show the same silly behaviour. My minimilistic example is here: http://pastebin.com/p8guRWMd The example does have some improved ways of finding the widgets to remove, so it is at least worth looking at. – three_pineapples Jan 20 '14 at 02:27
  • Oh, just refreshed and seen the ekhumoro's answer :P nevermind! – three_pineapples Jan 20 '14 at 02:34
  • yes! thanks for your help though! check out his solution it's neat and I learnt something new about gridlayout – evan54 Jan 20 '14 at 03:22
0

It looks like you didn't call QGridLayout::removeWidget() for all cells in row that you would like to remove. Then due to PySide the layout will keep referencing to the widgets and they are not really removed/deleted when you call setParent(None).

Also when you try deleteLater() of widget in the cell without QLayout::removeWidget(), then the cell will be not removed but stay alive (there is QLayoutItem will be left in the cell)

yshurik
  • 772
  • 5
  • 12
  • so are you suggesting I do `.removeWidget(widget)` and then `.deleteLater()`? I tried this and it didn't make a difference – evan54 Jan 19 '14 at 17:35