Updating dynamic QGridLayout - Python PyQt - python

I recently started studying Python and now I'm making a software with a GUI using PyQt Libraries.
Here's my problem:
I create a Scrollarea, I put in this scrollarea a widget which contains a QGridLayout.
sa = QtGui.QScrollArea()
sa_widget = QtGui.QWidget()
self.sa_grid.setSizeConstraint(QtGui.QLayout.SetMinAndMaxSize)
sa_widget.setLayout(self.sa_grid)
sa.setWidgetResizable(True)
sa.setWidget(sa_widget)
Then I add 10 QLabel (this is just an example of course, in this example I'm using a QGridLayout just like a Vertical Layout):
i = 0
while i<100:
i = i +1
add = QtGui.QLabel("Row %i" % i)
self.sa_grid.addWidget(add)
Then I create a button that calls the function "function_name", I want that this function deletes a row, so far this is what I've written:
tmp = QtGui.QWidget()
tmp = self.sa_grid.itemAt(0)
self.sa_grid.removeItem(tmp)
It deletes the first row and every x row of the gridlayout becomes row x-1, however, it doesn't delete the text "Row 1" so I see "Row 0" and "Row 1" on the same row.
Anyone can help me?
Thanks a lot in advance, Davide

Removing an item from a layout does not delete it. The item will just become a free-floating object with no associated layout.
If you want to get rid of the object completely, explicitly delete it:
def deleteGridWidget(self, index):
item = self.sa_grid.itemAt(index)
if item is not None:
widget = item.widget()
if widget is not None:
self.sa_grid.removeWidget(widget)
widget.deleteLater()

Related

Get state of checkbox cellWidget in QTableWidget PyQT

I work on python plugin for QGIS. In this plugin I have created a QTableWidget with 3 columns. These columns are QCheckbox, QTableWidgetItem and QComboBox. I would like to retrieve the values contained in these 3 columns. For the moment I managed to get the values of QComboBox and QTableWidgetItem but I can't seem to get the value of the QCheckBox.
liste = ['Carte 1','Carte 2','Carte 3','Carte 4','Carte 5','Carte 6']
combo_box_options = ["A4 Paysage","A4 Portrait", "A3 Paysage","A3 Portrait"]
self.dlg_format = Dialog_format()
self.dlg_format.tableWidget.setRowCount(len(liste))
for index in range(len(liste)):
item = QTableWidgetItem(liste[index])
self.dlg_format.tableWidget.setItem(index, 1, item)
self.dlg_format.tableWidget.setColumnWidth(0, 20)
self.dlg_format.tableWidget.setColumnWidth(1, 350)
combo = QComboBox()
for t in combo_box_options:
combo.addItem(t)
self.dlg_format.tableWidget.setCellWidget(index, 2, combo)
widget = QWidget()
checkbox = QCheckBox()
checkbox.setCheckState(Qt.Checked)
playout = QHBoxLayout(widget)
playout.addWidget(checkbox)
playout.setAlignment(Qt.AlignCenter)
playout.setContentsMargins(0,0,0,0)
widget.setLayout(playout)
self.dlg_format.tableWidget.setCellWidget(index, 0, widget)
self.dlg_format.show()
result = self.dlg_format.exec_()
if result:
for index in range(len(liste)):
text = self.dlg_format.tableWidget.item(index, 1).text()
format = self.dlg_format.tableWidget.cellWidget(index, 2).currentText()
check = self.dlg_format.tableWidget.cellWidget(index, 0).checkState() #Does not work
The QWidget is what is set as cell widget, not the checkbox, and that widget obviously has no checkState attribute.
There are various possibilities for this scenario.
Make the checkbox an attribute of the widget:
widget = QWidget()
widget.checkbox = QCheckBox()
playout.addWidget(widget.checkbox)
# ...
check = self.dlg_format.tableWidget.cellWidget(index, 0).checkbox.checkState()
Make the checkbox's checkState function a reference of the widget (note: no parentheses!) so that you can access it with the existing cellWidget(index, 0).checkState():
checkbox = QCheckBox()
widget.checkState = checkbox.checkState
Since all happens within the same scope (the function), you can totally ignore the cellWidget and use a list of tuples that contains the widgets:
widgets = []
for index in range(len(liste)):
# ...
widgets.append((item, combo, checkbox))
# ...
if result:
for item, combo, checkbox in widgets:
text = item.text()
format = combo.currentText()
check = checkbox.checkState()
Note that:
checkState()
returns a Qt.CheckState enum, which results in 2 (Qt.Checked) for a checked box; if you need a boolean, use isChecked() instead;
you can use enumerate instead of range, since you are iterating through the list items anyway: for index, text in enumerate(liste):;
if you don't need to add item data and the contents of the combo are always the same, just use combo.addItems(combo_box_options);
setting the column width for every cycle is pointless, just do it once outside the for loop;
if you use QHBoxLayout(widget) there's no need for widget.setLayout(playout), as the widget argument on a layout already sets that layout on the widget;
instance attribute are created in order to make them persistent (it ensures that they are not garbage collected and allows future access); from your code it seems unlikely that you're going to use that dialog instance after that function returns, so making it a member of the instance (self.dlg_format) is unrequired and keeps resources unnecessarily occupied: the dialog would be kept in memory even after it's closed, and would be then deleted and overwritten as soon as it's created again; just make it a local variable (dlg_format = Dialog_format());

How to print more than one items in QListWidget if more than one items selected

I have QListWidget and there are strings there, when I select a string, I wanted to display the index number and text of that. But the problem is, if I select more than 1 items, it doesn't display all of the indexes. It displays only one.
from PyQt5.QtWidgets import *
import sys
class Pencere(QWidget):
def __init__(self):
super().__init__()
self.layout = QVBoxLayout(self)
self.listwidget = QListWidget(self)
self.listwidget.addItems(["Python","Ruby","Go","Perl"])
self.listwidget.setSelectionMode(QAbstractItemView.MultiSelection)
self.buton = QPushButton(self)
self.buton.setText("Ok")
self.buton.clicked.connect(self.but)
self.layout.addWidget(self.listwidget)
self.layout.addWidget(self.buton)
def but(self):
print (self.listwidget.currentRow()+1)
uygulama = QApplication(sys.argv)
pencere = Pencere()
pencere.show()
uygulama.exec_()
How can I display all of the items names and indexes if I select more than 1 items?
I solved it with this
def but(self):
x = self.listwidget.selectedItems()
for y in x:
print (y.text())
You need to use QListWidget's selectedItems() function, which returns a list. currentRow() only returns a single integer, and is intended to be used only in single-selection instances.
Once you've got the list of QListWidgetItems, you can use the text() function on each item to retreive the text.
Getting the index is slightly more complicated, you'll have to get a QModelIndex object from your original QListWidgetItem using the QListWidget.indexFromItem() and then use the QModelIndex.row() function.
Source: http://pyqt.sourceforge.net/Docs/PyQt4/qlistwidget.html#selectedItems
Note: You specified PyQt5 in your tags, but the API of QListWidget remains the same in this case; see the C++ API docs if you want to make sure.

PySide: How Could I trigger the current clicked QPushbutton, not other later added

I am new to PySide. In my program, I encountered a problem that when I click one button, it triggers other button later added. Thanks!
self.addContentButton = QtGui.QPushButton('Add')
self.addContentButton.clicked.connect(self.addContent)
def addContent(self):
'''
slot to add a row that include a lineedit, combobox, two buttons
'''
self.contentTabHBoxWdgt = QtGui.QWidget()
self.contentName = QtGui.QLineEdit('line edit')
self.conetentTypeBox = QtGui.QComboBox()
self.conetentTypeBox.addItem('elem1')
self.conetentTypeBox.addItem('elem2')
self.contentSave = QtGui.QPushButton('save',parent = self.contentTabHBoxWdgt)
self.contentSave.clicked.connect(self.contntSaveAct)
self.contentDelete = QtGui.QPushButton('delete',parent=self.contentTabHBoxWdgt)
self.contentDelete.clicked.connect(self.contntDel)
self.contentTabHBox = QtGui.QHBoxLayout()
self.contentTabHBox.addWidget(self.contentName)
self.contentTabHBox.addWidget(self.conetentTypeBox)
self.contentTabHBox.addWidget(self.contentSave)
self.contentTabHBox.addWidget(self.contentDelete)
self.contentTabHBoxWdgt.setLayout(self.contentTabHBox)
self.contentTabVBox.addWidget(self.contentTabHBoxWdgt)
def contntDel(self):
'''
slot to delete a row
'''
msgBox = QtGui.QMessageBox(QtGui.QMessageBox.Warning, '', 'Be sure to delete')
okBttn = msgBox.addButton('Yes', QtGui.QMessageBox.AcceptRole)
noBttn = msgBox.addButton('Cancel', QtGui.QMessageBox.RejectRole)
ret = msgBox.exec_()
if msgBox.clickedButton() == okBttn:
self.contentTabVBox.removeWidget(self.contentDelete.parentWidget());
When I Add one row and click its delete button, it does not work as expected.While I add two or three row , I click one delete button , it remove one row that is not the clicked delete button belong to. How could I achieve this function. Ths!
Your problem is because you aren't really taking advantage of object oriented programming properly.
All rows in your example call the same instance of the method contntDel. This method uses self.contentDelete which always contains a reference to the last row added.
What you need to do is separate out everything related to a row to a new class. When you add a row, create a new instance of this class and pass in the contentTabVBox. That way each row (or instance of the new class you will write) will have it's own delete method.
Without a complete code example, I can't provide a complete solution, but this should give you a rough idea:
class MyRow(object):
def __init__(self,contentTabVBox, rows):
self.contentTabVBox = contentTabVBox
self.my_list_of_rows = rows
self.addContent()
def addContent(self):
# The code for your existing addContent method here
def contntDel(self):
# code from your existing contntDel function here
# also add (if Ok button clicked):
self.my_list_of_rows.remove(self)
class MyExistingClass(??whatever you have here normally??):
def __init__(....):
self.addContentButton = QtGui.QPushButton('Add')
self.addContentButton.clicked.connect(self.addContent)
self.my_list_of_rows = []
def addContent(self):
my_new_row = MyRow(self.contentTabVBox,self.my_list_of_rows)
# You mustsave a reference to my_new_row in a list or else it will get garbage collected.
self.my_list_of_rows.append(my_new_row)
Hope that helps!

Is storing pyQT widgets in a list bad form?

So I am building I program that manages a bunch of custom slider widgets. Currently, I have a slider_container(class) that holds a list of slider objects(class). These slider objects are then inserted into the layout in the main window. This has been working well while I was only adding and moving the position of the sliders up and down. But when I try to delete the sliders, everything goes bad. When ever there the list of slider is manipulated (add, move or delete a slider), the clear and rebuild functions are called in the main window as seen below.
def clear_layout(self, layout):
print "Cleared Layout..."
while layout.count() > 0:
item = layout.takeAt(0)
widget = item.widget()
if widget is not None:
widget.deleteLater()
def rebuild_slider_display(self):
""" Delete the old sliders, then add all the new sliders """
self.clear_layout(self.slider_layout)
print "Rebuild layout..."
print len(self._widgets.widgets)
for i, widget in enumerate(self._widgets.widgets):
print widget.slider_name, " ", i
self.slider_layout.insertWidget(i, widget)
print "Layout widget count: ", self.slider_layout.count()
Currently I am running into this error on this line "self.slider_layout.insertWidget(i, widget)"
RuntimeError: wrapped C/C++ object of type SliderWidget has been deleted
My hunch is that storing the actual widget in the widget container is bad form. I think what is happening when I deleteLater() a widget, is that it isnt just deleting a widget from the list, it actually deletes the widget class that was store in the widget container itself.
Hopefully that is explained clearly, thanks for your help in advance.
Edit:
Here is the widget class:
class SliderWidget(QWidget, ui_slider_widget.Ui_SliderWidget):
""" Create a new slider. """
def __init__(self, name, slider_type, digits, minimum, maximum, value, index, parent=None):
super(SliderWidget, self).__init__(parent)
self.setupUi(self)
self.slider_name = QString(name)
self.expression = None
self.accuracy_type = int(slider_type)
self.accuracy_digits = int(digits)
self.domain_min = minimum
self.domain_max = maximum
self.domain_range = abs(maximum - minimum)
self.numeric_value = value
self.index = index
#self.name.setObjectName(_fromUtf8(slider.name))
self.update_slider_values()
self.h_slider.valueChanged.connect(lambda: self.update_spinbox())
self.spinbox.valueChanged.connect(lambda: self.update_hslider())
self.edit.clicked.connect(lambda: self.edit_slider())
# A unique has for the slider.
def __hash__(self):
return super(Slider, self).__hash__()
# How to compare if this slider is less than another slider.
def __lt__(self, other):
r = QString.localAware.Compare(self.name.toLower(), other.name.toLower())
return True if r < 0 else False
# How to compare if one slider is equal to another slider.
def __eq__(self, other):
return 0 == QString.localAwareCompare(self.name.toLower(), other.name.toLower())
And here is the actually creation of the widget in the widget container:
def add_slider(self, params=None):
if params:
new_widget = SliderWidget(params['name'], params['slider_type'], params['digits'], params['minimum'],
params['maximum'], params['value'], params['index'])
else:
new_widget = SliderWidget('Slider_'+str(self.count()+1), 1, 0, 0, 50, 0, self.count())
#new_widget.h_slider.valueChanged.connect(self.auto_save)
#new_widget.h_slider.sliderReleased.connect(self.slider_released_save)
new_widget.move_up.clicked.connect(lambda: self.move_widget_up(new_widget.index))
new_widget.move_down.clicked.connect(lambda: self.move_widget_down(new_widget.index))
self.widgets.append(new_widget)
Thanks for all the help!
The problem I was having was with the way I cleared the layout. It is important to clear the layout from the bottom to the top as seen below.
for i in reversed(range(layout.count())):
layout.itemAt(i).widget().setParent(None)

Finding checked QRadioButton among many into a QVBoxLayout

I used the code below to dynamically create a group of radio buttons:
self.wPaymantType.qgbSomeSelectionGroup = QtGui.QGroupBox()
vbox = QtGui.QVBoxLayout()
for row in listOfChoices:
radio = QtGui.QRadioButton(row)
if bIsFirst:
radio.setChecked(True)
bIsFirst = False
if len(row.name) > nMaxLen:
nMaxLen = len(row.name)
vbox.addWidget(radio)
self.wPaymantType.qgbSomeSelectionGroup.setLayout(vbox)
How can I iterate through all radio buttons to find out which one is checked?
I tried something like this, but I didn't get anything good from it:
qvbl = self.qgbSomeSelectionGroup.children()[0]
for i in range(0, qvbl.count()):
child = qvbl.itemAt(i)
radio = QtGui.QRadioButton(child.widget())
if radio != None:
if radio.isChecked():
print "radio button num " + str(i) + " is checked"
Your code is not minimal and self-contained, so it's really hard to help you -- but I've anyway gone through the effort of building a near-minimal self-contained approximation of what you're trying to do and which does seem to work correctly -- here comes...:
from PyQt4 import QtGui
import sys
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.dowid()
self.setCentralWidget(self.thewid)
def dowid(self):
self.thewid = QtGui.QGroupBox()
vbox = QtGui.QVBoxLayout()
self.radiobuttons = []
listOfChoices = 'one two three'.split()
for i, row in enumerate(listOfChoices):
radio = QtGui.QRadioButton(row)
if i == 0:
radio.setChecked(True)
self.radiobuttons.append(radio)
vbox.addWidget(radio)
self.thewid.setLayout(vbox)
def examine(self):
for i, radio in enumerate(self.radiobuttons):
if radio.isChecked():
print "radio button num " + str(i) + " is checked"
else:
print "radio button num " + str(i) + " is NOT checked"
if __name__ == '__main__':
app = QtGui.QApplication([])
mainWin = MainWindow()
mainWin.show()
rc = app.exec_()
mainWin.examine()
This seems to do what you want. Key change is to keep the actual Python widget objects around rather than trying to restore them from the layout vbox -- that attempt seems to not be working as intended, at least as regards correct access to the crucial detail about whether a given radio button is checked or not, which is of course the heart of your Q.
I believe the reason why it's not working is your
radio = QtGui.QRadioButton(child.widget())
call at the code where you're checking if your checkbox is checked. I think what you're trying to do is typecast the child object to QtGui.QRadioButton and it doesn't work in this case. Instead you should be creating a new widget. Try changing it to smth. like this:
qvbl = self.qgbSomeSelectionGroup.layout()
for i in range(0, qvbl.count()):
widget = qvbl.itemAt(i).widget()
if (widget!=0) and (type(widget) is QtGui.QRadioButton):
if widget.isChecked():
print "radio button num " + str(i) + " is checked"
the code above should be iterating through child objects of the layout object, check their type and print "radio button..." in case it's radio buttong and it's checked
hope this helps, regards
I guess a better way to identify which button is checked is to use a QButtonGroup, as it provides a container to organize groups of button widgets. It is not a visual object, so it doesn't substitute the layout for visually arranging your radio buttons, but it does allow you to make them mutually exclusive and associate an integer "id" with them, letting you know which one is checked without the need to iterate through all the widgets present in the layout.
If you decide to use it, your code should turn into something like this:
self.wPaymantType.qgbSomeSelectionGroup = QtGui.QGroupBox()
vbox = QtGui.QVBoxLayout()
radioGroup = QtGui.QButtonGroup()
radioGroup.setExclusive(True)
for i,row in enumerate(listOfChoices):
radio = QtGui.QRadioButton(row)
radioGroup.addButton(radio, i)
if bIsFirst:
radio.setChecked(True)
bIsFirst = False
if len(row.name) > nMaxLen:
nMaxLen = len(row.name)
vbox.addWidget(radio)
self.wPaymantType.qgbSomeSelectionGroup.setLayout(vbox)
To identify the checked button, you can use QButtonGroup's checkedId method:
buttonId = radioGroup.checkedId()
or if you want to retrive the button object itself you can use the checkedButton method:
button = radioGroup.checkedButton()

Categories

Resources