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

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.

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

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

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.

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:]))

Why is only the last element of a list being accessed? [duplicate]

This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 5 years ago.
I am attempting to make a turn-based combat system, and I ran into a problem while trying to get buttons to access elements of a list relatively (the button in a list of buttons accesses an element of the same index in another list). However when clicked, each button only accesses the last element of the list. Here is simplified sample code that exhibits the same problem:
from tkinter import *
root = Tk()
my_list = ['index 0', 'index 1', 'index 2']
buttons = []
for i in range(len(my_list)):
buttons.append(Button(root, text='Button '+str(i), command=lambda: print(my_list[i])))
buttons[i].pack()
I understand that since I am doing this in a loop, the last element is probably being accessed by all buttons because it is the most recent value of i, but I can't figure out another way to accomplish my goal. To be more specific and draw from the example code above, my_list represents a list of actions that a character can take. Each button in buttons corresponds to one of those actions (clicking the button sets the action you take at the end of the turn). Normally, I could probably assign the actions more explicitly, but since there are multiple fighters I opted to use a loop and do it implicitly since my_list changes frequently to reflect the actions of each different fighter.
I am not very clear on why this is happening, and I would also like to know how I can accomplish my goal of allowing each button to access its corresponding element in my_list since it probably cannot be done in this way. Thank you for reading and let me know if I can clarify anything for you.
Use functools.partial instead of lambda:
from functools import partial
# ...
buttons.append(Button(root, text='Button '+str(i), command=partial(print, my_list[i])))

How to determine which button is pressed out of Button grid in Python TKinter? [duplicate]

This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 6 months ago.
I am writing a GUI with Python TKinter where I have a grid of some 24 buttons which I have created using a loop (not individually). Is there any way in which I can get the Text of the button that I pressed.
Since it is in loop, a callback function even with lambda is not helping me. I don't wish to write separate code for, what happens when each different button is pressed. I just need to know the text of the corresponding button so that I could initiate another generic function which works with that text only.
ps: I am able to do the same task but with List and curselection() and don't want it this way.
self.num = 11
for r in range(0,5):
for c in range(0,3):
R = r; C = c
resNum = "Ch0"+str(self.num);
self.button1_rex = tk.Button(self.frame, text = resNum,font=("Helvetica", 14), justify = CENTER,width = 20, command = self.new_window)
self.button1_rex.grid(row=R, column=C, sticky=W)
self.num = self.num+1
self.new_window is the function that opens a new window and needs to do other functionalities based on the button number (like "Ch011" etc)
The simplest way is just to, when you are constructing the button, bind the name to the command, either using functools.partial or a lambda.
Using functools.partial:
self.button1_rex = tk.Button(..., command=functools.partial(self.new_window, resNum))
Using a lambda:
self.button1_rex = tk.Button(..., command=lambda r=resNum: self.new_window(r))
For more infomation about the lambda, see What is a lambda (function)? and Python tkinter creating buttons ... arguments.

Categories