I have a question regarding QTableWidget at PyQt4. Say I have a QTableWidget, where I want to have an event connected to a cell click using:
table.cellClicked.connect(cellClick)
then, cellClick function does its job. The thing is: some cells contain a button, like so (coordinates 0,0 as an example):
button = QtGui.QPushButton()
table.setCellWidget(0,0, button)
Now comes my problem. The presence of the button in a cell (which takes up the entire cell space) completely disables the cellClicked - the cell doesn't get clicked, the action can't be detected. But I do want my button to fill the cell.
So, in short, my question is - how can I select (click) a QTableWidget cell when there's a button inside it?
The simple answer would seem to be:
button.clicked.connect(cellClick)
But then how would you know the row/column of the cell that was clicked? So the next iteration might be:
button = QPushButton()
table.setCellWidget(row, column, button)
button.clicked.connect(
lambda *args, row=row, column=column: cellClick(row, column))
Which is okay if the table is static. But what if the rows and columns can be rearranged? The row/column cached in the button's clicked handler could be invalidated in that case. So the next iteration might be:
index = QtCore.QPersistentModelIndex(table.model().index(row, column))
button.clicked.connect(
lambda *args, index=index: cellClick(index.row(), index.column()))
(NB: you can connect other slots to the button's clicked signal, but the order they will be invoked in is undefined)
Related
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
This question already has an answer here:
Passing extra arguments through connect
(1 answer)
Closed 4 years ago.
I am adding QPushButtons to every row in a QTableWidget dynamically.
When I click a button I want it to open a link based on data in the same row.
I can only get it to work if I first select the row, then click the button.
I want to be able to click the button without having to click the row first.
This is a snippet of what I have:
def populate_table(self):
instances = []
table = self.ui.ESC_tableWidget
table.setRowCount(0)
button_dict = {}
for esc_inst in instances:
button_dict[esc_inst.esc_id] = QtWidgets.QPushButton('Open link')
rowPosition = table.rowCount()
table.insertRow(rowPosition)
table.setItem(rowPosition , 0 , QtWidgets.QTableWidgetItem(esc_inst.esc_id))
.
.
.
esc_table.setCellWidget(rowPosition, 5 , button_dict[esc_inst.esc_id] )
for button in button_dict.values():
button.clicked.connect(self.open_link)
def open_link(self):
selected_esc_id = table.item(table.currentRow(), 0).text()
So I need to bypass the table.currentRow() function, because that returns the correct row number if a row IS selected. If I directly click a button without selecting the row first, the previously selected row number is returned.
I can only think of hacky solutions like creating an orderedDict or such, but it seems like such a trivial thing that I am sure I am missing something.
Any ideas how to overcome this?
You can have your button click callback receive some arguments that define what button it is. When attaching the callback to the buttons, create a separate lambda function for each button that calls your callback, but with an index:
for i, button in enumerate(button_dict.values()):
button.clicked.connect(lambda checked, i=i: self.open_link(i))
And then, in your callback:
def open_link(self, i):
selected_esc_id = table.item(i, 0).text()
You need the i=i-part in the lambda because, otherwise, it will just pass the last value of i to your callback. With that part included, every button will pass a different i to your callback method.
I have this code:
import tkinter as tk
def onselect(event):
print(event.widget)
root=tk.Tk()
Listbox = tk.Listbox(root)
Listbox2 = tk.Listbox(root)
Listbox.pack(anchor='e', fill='both', expand=True)
Listbox2.pack(anchor='e', fill='both', expand=True)
Listbox.insert('end', 'hello')
Listbox.insert('end', 'bay')
Listbox2.insert('end', 'yes')
Listbox2.insert('end', 'no')
Listbox.bind('<<ListboxSelect>>', onselect)
Listbox2.bind('<<ListboxSelect>>', onselect)
When I select for example any element from the first listbox I get:
.!listbox
But then if I try to select the second listbox I get:
.!listbox2
.!listbox
But I need to get only the second listbox, why is it getting the second and then the first?
How can I fix this?
I need to know what widget I am selecting (it would be better if it could give me the number of the widget: the first listbox 0 the second 1 and so on if more are created).
When you bind to <<ListboxSelect>>, your callback will get called once when the old selection is lost, and a second time when the new selection is made. The event is documented to fire when the selection changes, not just when it is set, and going from the selected state to an unselected state is considered a change.
Here's what's happening:
You select from the first listbox, there is no current selection so your callback is called once.
You select from the second listbox. Since you didn't set exportselection=False, you can only have one thing selected at a time. Therefore your callback will get called for the first listbox when the first listbox loses the selection, and then get called again for the second listbox when the second listbox gets the selection.
I need to know what widget I am selecting
You are given a reference to a widget. You first need to get the selection from that widget. If the selection is empty then it's safe to assume the callback was called because the item was deselected. If the selection is not empty, you can assume that the callback was because the item was selected.
If you want to be able to have something selected from both widgeta, set exportselection to False on both listboxes. Then, when you select something in the second, your callback will only be called once since the other listbox won't lose the selection.
I have been looking for a treeView widget option where I can select only a single row at a time by clicking it and then de-select it by clicking on it again. I found how to make it so that you can select only one row at a time, but I was unable to find an option that allowed u to click the selected row to de-select it.
Does anyone know how this can be done? Any help would be appreciated.
You can set a custom binding to deselect the item if it's currently selected. If your binding returns the string break, it will stop event propagation and thus prevent the default behavior for double-click.
...
self.tree = ttk.Treeview(...)
self.tree.bind("<1>", self.on_click)
...
def on_click(self, event):
selection = self.tree.selection()
item = self.tree.select_row(event.y)
if item in selection:
self.tree.selection_remove(item)
return "break"
Unfortunately, the only built-in pattern to toggle the selection state of a treeview item is by selecting a different item but you can do a hack of your own.
[I'm using PyQt4, but I think this Qt4 issue is not Python specific.]
I have a QTableWidget. In each row, the first column holds a button. When clicked, the row is removed.
To remove the row, I use removeRow(int row) method, which takes as argument the index of the row. When connecting the signal, I can't know the index of the row because it might change in the meantime (for instance if the first row is removed, all row indexes are changed).
The accepted answer here suggests to pass the callback an instance of a QTableWidgetItem in the line, then get the row number from this item at deletion time.
This would be nice, except none of the elements of the row is a QTableWidgetItem. The elements are the button itself and a few ComboBoxes.
I can't figure out a way around this.
Can I somehow fit one of my elements into a QTableWidgetItem? Should I add a QTableWidgetItem in some sort of hidden column?
Our current implementation uses indexAt(QtGui.qApp.focusWidget()) (see other answer to question mentioned above), which looks like a sorry workaround to me.
If I replace the button with a checkable QTableWidgetItem like this
rm_item = QtGui.QTableWidgetItem()
rm_item.setFlags(QtCore.Qt.ItemIsUserCheckable |
QtCore.Qt.ItemIsEnabled)
I have a QTableWidgetItem I can use to get back to the row index. But I don't know how to catch a "checked" or "clicked" event from it like I do with the button. All I found is the itemClicked signal of QTableWidget, but then I'd have to filter all the other widgets out.
There has to be something obvious I'm missing.
Edit
From what I read here, I could add both a QTableWidgetItem with setItem and a Button widget with setCellWidget to the same cell. This doesn't seem so natural to me, but apparently it works (can't test right now).
I guess I'll do that. Add the Button, plus a dummy QTableWidgetItem on the same cell to pass as a reference to the row.
Is this how it is meant to be?
Edit 2
Or maybe QTableWidget is not the proper Widget and I should be using a Layout, as suggested here.
It seems that using a layout rather than a table is possibly the most "correct" answer, but that may come with it's own difficulties, as seen in my answer to this question:
How to delete widgets from gridLayout
If you want to continue using a table, a somewhat cleaner solution than adding dummy items would be to use a persistent model index:
button = QtGui.QPushButton(text, self.table)
self.table.setCellWidget(row, column, button)
index = QtCore.QPersistentModelIndex(
self.table.model().index(row, column))
button.clicked.connect(
lambda *args, index=index: self.handleButton(index))
def handleButton(self, index):
print('button clicked:', index.row())
if index.isValid():
self.table.removeRow(index.row())
If I understand your question correctly:
def set_button(self, row, col):
# create a push button
btn = QtGui.QPushButton('Remove row')
# connect to action
btn.clicked.connect(self.remove_row)
# set in cell
self.setCellWidget(row, col, btn)
def remove_row(self):
# find what is clicked
clicked = QtGui.qApp.focusWidget()
# position
idx = self.indexAt(clicked.pos())
# remove this row
self.removeRow(idx.row())