How to connect functions to dynamically created QPushButtons in PyQt5 [duplicate] - python

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.

Related

receiving selected choice on Tkinter listbox

I'm trying to build a listbox using Tkinter and receive the selected option by clicking it.
import Tkinter as tk
from Tkinter import *
root = tk.Tk()
lst=Listbox(root, height=30, width=50)
lst.insert(1, "hy")
lst.insert(2, "hello")
lst.insert(3, "hey")
lst.pack()
sel = lst.curselection()
print sel
root.mainloop()
However, when I run the code it prints me an empty tuple before I pressed any choise.
Does someone know how to get the selected choise after I press one and not right after I run it?
Thanks a lot :)
You are getting the selection about a millisecond after creating the widget, well before the user has a chance to see the UI much less interact with it.
GUI programs are event based, meaning that things happen in response to events. Events are things like clicking buttons, inserting data into input widgets, and selecting items from listboxes.
You need to do one of two things: create a button or other widget which will get the selected item, or configure it so that a function is called whenever an item is selected.
No matter which solution you use, you will need a function that ultimately calls the curselection method of the listbox to get a list of indices. You can then call the get method to get the selected item or items.
Here's a function definition that will print the selected item, or print "no selection" if nothing is selected. So that it can be resused without modification. we'll define it to take the listbox as an argument.
Note: this example assumes the widget only supports a single select, to keep it simple:
def print_selection(listbox):
selection = listbox.curselection()
if selection:
print(f"selected item: {listbox.get(selection[0])}")
else:
print("nothing is selected")
Using a button
To call this from a button is straight-forward. We just create a button after we create the listbox, and use the command attribute to call the function. Since the function we wrote earlier needs a parameter, we'll use lambda to create a temporary function for the button.
button = tk.Button(root, text="Print Selected Item", command=lambda: print_selection(lst))
button.pack()
Calling the function when the selection is made
To call the function whenever the user changes the selection, we can bind a function to the <<ListboxSelect>> event. We'll create a separate function for this, and then pull the widget from the event object that is automatically passed to the function.
def print_callback(event):
print_selection(event.widget)
lst.bind("<<ListboxSelect>>", print_callback)
First of all, the reason you are getting an empty tuple is because you have executed the statements:
sel = lst.curselection()
print(sel)
before you have executed the root.mainloop()
Secondly, your setup for listbox fails to include a StringVar variable to hold your list.
Once the variable has been defined, you should be able to use the .insert statements to add your list items one at a time, or you can initialize the StringVar variable using a .set('hy', 'hello', 'hey') command.
To provide a return of a selected variable, you must incorporate an event handler to determine the list position selected onclick or some other triggering method.
For a pretty clear explanation of these characteristics check here

Tkinter - Selecting an item from a Treeview using single click instead of double click (Callback on Treeview item selection)

When you want to select an item in a Treeview, you usually use the double-click:
def print_element(event):
print(my_treeview.selection()[0])
my_treeview.bind("<Double-1>", print_element)
Today I tried to do the same but using a single click instead:
my_treeview.bind("<Button-1>", print_element)
But it wouldn't work. The output was just an empty tuple.
I started to search online for an explanation... why is it not working?
EDIT:
My goal was actually to do something every time a treeview item was selected.
I proposed a solution myself using the identify() function of Tkinter
Another user proposed to use the Tkinter callback <ButtonRelease-1> which is much more appropriate
Finally, a third user focused his answer on using the Tkinter callback <<TreeviewSelect>>, which is for sure the best option
The reason it doesn't work the way you expect is because your custom single-click binding happens before the default behavior. So, when your single-click is processed, that happens before an item is selected. The second time you click, your function will print the previously selected item.
If you want to have a function called when an item is selected, you should bind to <<TreeviewSelect>>, which will fire immediately after the user selects an item with a single click or via the keyboard.
The default behavior of a treeview supports selecting multiple items at once, so the following code will print out the text of all of the selected items as a list, even if only a single item is selected. You can, of course, modify this to only print out the first selected item if you so desire.
def print_element(event):
tree = event.widget
selection = [tree.item(item)["text"] for item in tree.selection()]
print("selected items:", selection)
tree.bind("<<TreeviewSelect>>", print_element)
It is because the selection is not set yet when the <Button-1> (it is the same as <ButtonPress-1>, i.e. when the mouse button 1 is pressed and not released) event callback is called.
You should bind on <ButtonRelease-1> or <<TreeviewSelect>> instead as the selection is set when the event callback is being executed.
Why it doesn't work
When you click an item in a treeview, that item is still not in a SELECTED status in the moment the callback is activated. You are changing the status in that very moment.
Using a double-click, the first click change the status, and at the second click you are activating your callback, so the status has already been changed.
How can it work
Kudos to this website
In short,
def print_element(event):
print(my_treeview.identify('item', e.x, e.y))
my_treeview.bind("<Button-1>", print_element)
This time, print_element() will check the coordinates of the mouse, and will discover the selected item check what is under the mouse. Nice and clean!

Bokeh: Handling callbacks for unknown (at compile-time) number of buttons

I've created multiple buttons at runtime and stored them in a list.
keys = []
keys.append(Button(label="-- Parent --"))
for key in node_obj.children.keys():
keys.append(Button(label=key))
Note that the number of children of node_obj may vary, so the number of buttons is not always the same. I'm trying to create callbacks for all the buttons and did it like this:
def test_fn(button):
print(button.label)
for button in keys:
button.on_click(lambda : test_fn(button))
but it always prints the label of the last button in the list. How can I modify it such that the label of the button that was clicked is printed?
This is a result of the way Python works. When the lambda is actually executed it uses the value of button from the outer scope—which is the last value of the loop. You will need to use the standard library functools.partial function to "bake in" each different button ahead of time:
from functools import partial
def test_fn(button):
print(button.label)
for button in keys:
button.on_click(partial(test_fn, button=button))

How to Use a Setter Function to Check Whether a Function Parameter has Changed - Python

Here is my code:
def DisplayFiles(which):
contentLabel = tk.Label(window, text = '\n'.join(files[indexlist[which]][1:]))
contentLabel.place(x = 150, y = 80)
I am using Tkinter and am trying to display files with the above function when a button is pressed. The variable "which" is the string name of a button. "indexlist" is a dictionary holding indexes for button names (I dynamically created them). My problem is trying to display files for two different buttons. When I click one button, the function above displays the files. But when I click another button, the label displays over the previous one. I am working on a destroy() method, but I need to know how to check when the parameter "which" is changed. Help would be appreciated!
Also, the values of indexlist, and files are not the problem. I just want to find a way to check when the function parameter is changed. Thanks!
Don't make a new Label every time, just update the old Label.
# make an empty Label
contentLabel = tk.Label(window)
contentLabel.place(x = 150, y = 80)
def DisplayFiles(which):
# update the Label contents
contentLabel.config(text = '\n'.join(files[indexlist[which]][1:]))

How can I get the button id when it is clicked? [duplicate]

This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 5 years ago.
I don't get how to reference the button being clicked in tkinter.
My code:
for file in files:
btn = Button(root, text=file).pack()
Now e.g. 50 buttons are generated from files which is the source.
However when I click any button, only the LAST button is being referenced, but not the button I really want to use/click.
In JavaScript we use this to reference the object we really clicked, however I couldn't find any solution in Python for this.
This can be done with something like the below:
from tkinter import *
root = Tk()
files = [] #creates list to replace your actual inputs for troubleshooting purposes
btn = [] #creates list to store the buttons ins
for i in range(50): #this just popultes a list as a replacement for your actual inputs for troubleshooting purposes
files.append("Button"+str(i))
for i in range(len(files)): #this says for *counter* in *however many elements there are in the list files*
#the below line creates a button and stores it in an array we can call later, it will print the value of it's own text by referencing itself from the list that the buttons are stored in
btn.append(Button(root, text=files[i], command=lambda c=i: print(btn[c].cget("text"))))
btn[i].pack() #this packs the buttons
root.mainloop()
So what this does is create a list of buttons, each button has a command assigned to it which is lambda c=i: print(btn[c].cget("text").
Let's break this down.
lambda is used so that the code following isn't executed until the command is called.
We declare c=i so that the value i which is the position of the element in the list is stored in a temporary and disposable variable c, if we don't do this then the button will always reference the last button in the list as that is what i corresponds to on the last run of the list.
.cget("text") is the command used to get the attribute text from a specific tkinter element.
The combination of the above will produce the result you want, where each button will print it's own name after being pressed, you can use similar logic to apply it to call whatever attribute or event you need.

Categories