so I have a mass of buttons on a QT Designer GUI application all named LED_i where i ranges from 0-191, ie: LED_0, LED_1, ..., LED_191. I would like basically the same thing to happen when clicked except changing the input i. So LED_0 when clicked would call the function OnClick(0), LED_75 would call OnClick(75) etc etc.
I am connecting my buttons with
ui.LED_0.clicked.connect(OnClick0)
usually using a separate function for each button. However this would require 191 functions, and 191 lines like the above connecting my buttons to their functions. I'm sure I could edit it s.t. I can use the same function by passing the button name that was clicked and getting the number from it but that would still require the 191 lines of button.clicked.connect. Is there any way to do this more efficiently?
TIA
It's not impossible with PyQt5. This is a good fit for a QButtonGroup. And there are a few ways to approach it.
QButtonGroup using Designer
To set up a QButtonGroup in Designer, do the following:
select two or more buttons, radioboxes, or checkboxes
right click on one of the buttons and you'll get a context menu item, Assign to button group --> New button group
After this, you'll see buttonGroup (the default name) show up in the Object Inspector.
To run code when one of your buttons is clicked, you can use the buttonClicked signal of QButtonGroup. It will give you a reference to the button that was clicked, and from the objectName, you can figure out what to do.
ui.buttonGroup.buttonClicked.connect(self.OnClicked)
then
def OnClicked(self, button):
# button objectName follows pattern LED_<number>
button_number = int(button.objectName()[4:])
... do stuff here with button_number
QButtonGroup in code
In the original post, there were 191 buttons in Designer. That is a lot of buttons to arrange. If for some reason, you wanted to do it in code instead, you could assign each button an id as it is added to the group and then you could use the idClicked signal:
grid = QGridLayout()
buttonGroup = QButtonGroup()
buttonGroup.idClicked.connect(OnClick)
buttonList = []
for row in range(14):
rowList = []
for col in range(14):
button_number = 14*row + col
button = QPushButton(f'{button_number}', objectName=f'LED_{button_number}')
rowList.append(button)
buttonGroup.addButton(button, button_number)
grid.addWidget(button, row, col)
then
def OnClick(self, idClicked):
... do something with idClicked here
Related
There are some examples of removing specific items from a layout, but I can not find anything on simply deleting everything from a frame.
Using pyqt designer, I have created a frame. Then using pyuic4 the file is converted to python. In the main program, some layouts, items, and widgets are dynamically inserted to the frame. However, I dont actually keep track of all the items. On a button refresh, I want to delete everything contained in the frame and populate it again.
My question is, is there a simple way to delete everything that is contained in a frame, including layouts, widgets, and items.
As of now, I can do:
for i in range(len(MyResourceFrame.children())):
MyResourceFrame.children()[i].deleteLater()
However, I have code directly under that, and after the first qframe population, clicking on repopulate give the error that a frame is already there, which then removes all items. The second click on repopulate works. Does this have something to do with "Later" wanting to be out of scope first or is that just a name?
The deleteLater slot will just schedule the object for deletion. That is, the object won't be deleted until control returns to the event loop (which will usually mean after the currently executing function has returned).
If you want to delete an object immediately, use the sip module. This should allow you delete a layout and all its contained widgets like this:
PyQt5:
from PyQt5 import sip
...
class Window(QtWidgets.QMainWindow):
...
def populateFrame(self):
self.deleteLayout(self.frame.layout())
layout = QtWidgets.QVBoxLayout(self.frame)
...
def deleteLayout(self, layout):
if layout is not None:
while layout.count():
item = layout.takeAt(0)
widget = item.widget()
if widget is not None:
widget.deleteLater()
else:
self.deleteLayout(item.layout())
sip.delete(layout)
PyQt4:
import sip
...
class Window(QtGui.QMainWindow):
...
def populateFrame(self):
self.deleteLayout(self.frame.layout())
layout = QtGui.QVBoxLayout(self.frame)
...
def deleteLayout(self, layout):
if layout is not None:
while layout.count():
item = layout.takeAt(0)
widget = item.widget()
if widget is not None:
widget.deleteLater()
else:
self.deleteLayout(item.layout())
sip.delete(layout)
The problem was the layout predefined inside your QFrame. If you remove it in QtDesigner your frame will appear correctly with the first click.
I have 2 radiobuttons created (inside a QMainWindow class) like:
def dtype_radiobuttons(self):
layout = QHBoxLayout()
rb1 = QRadioButton("complex")
rb1.toggled.connect(lambda: self.update_image("dtype", rb1.text()))
self.real_dtype_rb = QRadioButton("real", self)
self.real_dtype_rb.toggled.connect(lambda: self.update_image("dtype", self.real_dtype_rb.text()))
self.btngroup.append(QButtonGroup())
self.btngroup[-1].addButton(self.real_dtype_rb)
self.btngroup[-1].addButton(rb1)
rb1.setChecked(True)
layout.addWidget(rb1)
layout.addWidget(self.real_dtype_rb)
layout.addStretch()
return layout
def library_radiobutton(self):
layout = QHBoxLayout()
self.cvnn_library_rb = QRadioButton("cvnn", self)
self.cvnn_library_rb.toggled.connect(lambda: self.update_image("library", self.cvnn_library_rb.text()))
rb2 = QRadioButton("tensorflow", self)
rb2.toggled.connect(lambda: self.update_image("library", rb2.text()))
self.btngroup.append(QButtonGroup())
self.btngroup[-1].addButton(rb2)
self.btngroup[-1].addButton(self.cvnn_library_rb)
self.cvnn_library_rb.setChecked(True)
layout.addWidget(self.cvnn_library_rb)
layout.addWidget(rb2)
layout.addStretch()
return layout
I want to make it impossible to select the complex option of the dtype radiobuttons group and tensorflow radiobutton of the library radiobuttons. Leaving 3 out of the 4 possible combinations. So if I select complex and library was tensorflow, I want to automatically change the library to cvnn. I tried to implement it like this:
def update_image(self, key, value):
if value == "complex":
if hasattr(self, 'cvnn_library_rb'): # It wont exists if I still didnt create the radiobutton.
self.cvnn_library_rb.setChecked(True) # Set library cvnn
elif value == "tensorflow":
if hasattr(self, 'real_dtype_rb'):
self.real_dtype_rb.setChecked(True) # Set real dtype
... Do the other stuff I need to do.
The weird thing is that it actually works in the sense that, for example, if I am on complex activated and select tensorflow, the radiobutton changes to real (what I want!) but tensorflow does not get selected! I need to select it again as if making self.real_dtype_rb.setChecked(True) cancels the selection of the radiobutton I clicked on. (Very weird if you ask me).
The hasattr is used because depending on the order I call the
functions, there are some radiobuttons that will be created before
the other, so it might not exist.
This
is an option I am considering but it's disabling the radiobutton
group instead of changing their state (not what I prefer).
The signal toggled is triggered whenever you change the state of your radio buttons. So, it will be triggered when you call setChecked (once for the radio button you toggle and once for the other you untoggle) and update_image is called is the wrong case.
You have to check the state of the radio button and call update_image only if the radio button is toggled:
rb2.toggled.connect(lambda state: state and self.update_image("library", rb2.text(), state))
I'm making a user-interface in PYQT 5. It includes a QGroupBox containing several QRadioButton. When there is a toggled event I check the buttons and do some stuff. The problem is that there are two toggled events because one button is toggled on and one off so my code is always running twice.
I have been looking for an event from the QGroupBox instead. That should only happen once instead of twice when i toggle a radiobutton.
def __init__(self):
self.radioButton1.toggled.connect(self.update_stimulus)
self.radioButton2.toggled.connect(self.update_stimulus)
self.radioButton3.toggled.connect(self.update_stimulus)
def update_stimulus(self):
if self.radioButton1.isChecked():
print('1')
if self.radioButton2.isChecked():
print('2')
if self.radioButton3.isChecked():
print('3')
# Do stuff based on button positions
I tried using
self.groupBox.toggled.connect(self.update_stimulus)
But that only works if the groupbox itself is toggled. Is there any way to get a signal from the groupbox when one of the radiobuttons changes or do I have to find some way of doing it with the indivdual signals from the radiobuttons?
As #ekhumoro explains, you can add the checked parameter to the function and do your processing only if it's True:
def update_stimulus(self, checked):
if not checked:
return
if self.radioButton1.isChecked():
print('1')
if self.radioButton2.isChecked():
print('2')
if self.radioButton3.isChecked():
print('3')
Keep in mind, though, that if you want to have different radio button "groups" within the same groupbox, none of this will work properly as they will all be considered as part of the same single group: all Qt buttons (widgets that inherit QAbstractButton: QPushButton, QToolButton, QCheckBox and QRadioButton) have an autoExclusive property which is off by default except from radio buttons. This property makes all button that belong to the same parent widget automatically exclusive.
If you need different groups within the same parent, the solution is to use a QButtonGroup, which extends the exclusive functionality by limiting the membership of each button to a specific group.
def __init__(self):
# ...
self.buttonGroupA = QtWidgets.QButtonGroup()
self.buttonGroupA.addButton(self.radioButton1)
self.buttonGroupA.addButton(self.radioButton2)
self.buttonGroupA.addButton(self.radioButton3)
self.buttonGroupA.buttonToggled[QtWidgets.QAbstractButton, bool].connect(self.update_stimulusA)
self.buttonGroupB = QtWidgets.QButtonGroup()
self.buttonGroupB.addButton(self.radioButton4)
self.buttonGroupB.addButton(self.radioButton5)
self.buttonGroupB.addButton(self.radioButton6)
self.buttonGroupB.buttonToggled[QtWidgets.QAbstractButton, bool].connect(self.update_stimulusB)
def update_stimulusA(self, btn, checked):
if not checked:
return
# do something with group A
def update_stimulusB(self, btn, checked):
if not checked:
return
# do something with group B
Creation of a button group is also possible from Designer: just select at least two buttons that will be members of the same group, right click on one of them, go to the "Assign to button group" sub menu and select "New button group". To add a button to an existing group, just use the same context menu and choose the group you want to add that button to.
I'm working on a GUI that will eventually run one of several data analyses depending on which the user selects. In this part of the GUI, I have four radio buttons with options, and then a display button. I would like one of four imported functions to run when the user hits display.
It boils down to something like this
import myFunction
class myUi(QtGui.QWidget):
def retranslateUi(self, myUi):
self.option1.clicked.connect(self.option1task)
self.option2.clicked.connect(self.option2task)
self.display.clicked.connect(self.displaytask)
def option1task(self):
#do something
def option2task(self):
#do something
def displaytask(self):
#if option 1 was clicked, run myFunction.option1()
#if option 2 was clicked, run myFunction.option2()
I'm just having trouble making it work. Is there some way to solve it just by passing variables or will I need to use the signal/slot method?
First of all you do not want to react immediately when a radio button is clicked, so you do not need to connect to their clicked signal.
Instead the radio buttons (which are automatically exclusive within the same parent) can be selected by the user and at the moment the display button is clicked, you just read out which of the radio buttons is selected (can be at most one) and do something according to which one is selected.
For four radio buttons you can do that by a if else clause on isChecked() of the buttons.
For larger numbers of button I recommend additionally using a QButtonGroup (documentation) which allowes to assign an integer to each button within the addButton() method and then easily retrieve the integer of the selected button with checkedId(). If no button is selected the return value is -1.
My example (using PySide which is very similar to PyQt):
from PySide import QtGui
def do_something():
id = g.checkedId()
if id == -1:
print('no option selected')
else:
print('selected option {}, read for display'.format(id))
app = QtGui.QApplication([])
w = QtGui.QWidget()
l = QtGui.QVBoxLayout(w)
# three radio buttons
b1 = QtGui.QRadioButton('Option 1')
l.addWidget(b1)
b2 = QtGui.QRadioButton('Option 2')
l.addWidget(b2)
b3 = QtGui.QRadioButton('Option 3')
l.addWidget(b3)
# a button group (mapping from buttons to integers)
g = QtGui.QButtonGroup(w)
g.addButton(b1, 1)
g.addButton(b2, 2)
g.addButton(b3, 3)
# display button
b = QtGui.QPushButton('Display')
b.clicked.connect(do_something)
l.addWidget(b)
w.show()
app.exec_()
And it looks like:
I have dialog:
It contains many flat QPushButtons, QTextEdit and another QPushButton. After click on 'Get list' we can see list of checked buttons in QTextEdit.
My question is how to get this functionality in some smart way. Right now I'm checking every button:
if self.ui.bq6o.isChecked():
cards.append("Q6o")
if self.ui.bk2o.isChecked():
cards.append("K2o")
if self.ui.bq3o.isChecked():
cards.append("Q3o")
if self.ui.bt7s.isChecked():
cards.append("T7s")
if self.ui.bq4o.isChecked():
cards.append("Q4o")
if self.ui.bt4s.isChecked():
cards.append("T4s")
if self.ui.b98o.isChecked():
cards.append("98o")
if self.ui.bjto.isChecked():
cards.append("JTo")
if self.ui.btt.isChecked():
cards.append("TT")
if self.ui.bq7o.isChecked():
cards.append("Q7o")
[...]
Obviously I can't like code like that. I was looking for some widget "button matrix" like, but without luck. I will be grateful for advises.
All the buttons should be children of the same widget, probably the dialog itself. Just get a handle to that widget to get all the child buttons, then loop through them and if they're checked, included their text.
parent = dialog # or whatever
cards = [widget.text() for widget in parent.children() if isinstance(widget, QPushButton) and widget.isChecked()]
You may need to include some code in the if statement to exclude the "Get List" button, or any other pushbuttons in your dialog that could be set to "checked" but shouldn't be included in cards list.
As #Brendan suggested in the other question, you could loop through them in a single list comprehension. But one other approach is to connect each buttons toggled signal to a slot that allows them to register when they are checked.
# somewhere in your class
self.checkedList = set()
def buttonChecked(self, checked):
button = self.sender()
if checked:
self.checkedList.add(button)
else:
if button in self.checkedList:
self.checkedList.remove(button)
# when you create a new button
button.toggled.connect(self.buttonChecked)
This would let you always have a set of just the checked buttons, which are self reporting. Otherwise, you would have to track them under their parent and loop to find out which are checked each time.
Update
Here is a another version that combines #Brendans loop and my signal suggestion. This might help in a situation where your buttons are a bit more spread out across your UI as opposed to be all under a single parent... but first assuming them all under a parent...
parent = dialog
for widget in parent.children():
if isinstance(widget, QPushButton):
widget.toggled.connect(self.buttonChecked)
You could repeat this in your __init__() for all the locations of your buttons and get them all registered to the slot.