Dynamically Creating Functions and Binding Buttons in Tkinter - python

I am trying to assign values to buttons which return their value when clicked (more precisely, they print it.) The only caveat is that the buttons are created dynamically, using a for loop.
How can I assign id's (and other variables) to buttons which have been created with a for loop?
Example Code:
#Example program to illustrate my issue with dynamic buttons.
from Tkinter import *
class my_app(Frame):
"""Basic Frame"""
def __init__(self, master):
"""Init the Frame"""
Frame.__init__(self,master)
self.grid()
self.Create_Widgets()
def Create_Widgets(self):
for i in range(1, 11): #Start creating buttons
self.button_id = i #This is meant to be the ID. How can I "attach" or "bind" it to the button?
print self.button_id
self.newmessage = Button(self, #I want to bind the self.button_id to each button, so that it prints its number when clicked.
text = "Button ID: %d" % (self.button_id),
anchor = W, command = lambda: self.access(self.button_id))#Run the method
#Placing
self.newmessage.config(height = 3, width = 100)
self.newmessage.grid(column = 0, row = i, sticky = NW)
def access(self, b_id): #This is one of the areas where I need help. I want this to return the number of the button clicked.
self.b_id = b_id
print self.b_id #Print Button ID
#Root Stuff
root = Tk()
root.title("Tkinter Dynamics")
root.geometry("500x500")
app = my_app(root)
root.mainloop()

The problem is that you are using the last value of self.button_id when you call the command once the buttons are created. You have to bind the current value of the local variable for each lambda with lambda i=i: do_something_with(i):
def Create_Widgets(self):
for i in range(1, 11):
self.newmessage = Button(self, text= "Button ID: %d" % i, anchor=W,
command = lambda i=i: self.access(i))
self.newmessage.config(height = 3, width = 100)
self.newmessage.grid(column = 0, row = i, sticky = NW)

Related

solution communication between two windows

I want to have two tkinter windows. A button should be in the first window, and a reaction text should be in the second window.
My questions:
Must the second window have no modal?
How do I make the second window movable?
How can I give information to second window via callback function?
Thanks in advance for answers and advice!
Here is some code that may help you:
from tkinter import *
class App:
def __init__(self):
self.window1 = Tk()
self.window2 = Toplevel()
self.button = Button(self.window1, bd = 5, text = "Click Me!", command = self.update)
self.button.pack()
self.label = Label(self.window2, bd = 5, text = "Button has not been clicked.")
self.label.pack()
def update(self):
self.label.config(text = "Button has been clicked!")
self.window2.update()
app = App()
Explanation:
The first line imports tkinter
In the next line, we create a class. At the bottom of the code, we create an object using that class. This is useful because when the object is created, the functions in the class are already defined, so the function definition can be after when it is called.
After we declare our class, in __init__, we write code that will run when an object is created from that class. The code creates two windows. One contains a button, and the other one contains a label. The button has a command parameter to run the class function, update.
In update, we change the label text and update the window.
I have not next questions. My problems solution is here:
import tkinter as tk
class ViewOnMoon(tk.Toplevel):
def __init__(self, parent = None, draw = None):
tk.Toplevel.__init__(self, parent)
self.transient(parent)
self.title('View')
self.minsize(height = 300, width = 300)
fr_canvas = tk.Frame(self)
fr_canvas.place(relx=0.23, rely=0.01, anchor="nw")
self.canv_w = 200
self.canv_h = 200
self.canvas = tk.Canvas(fr_canvas, bg='white', width = self.canv_w, height=self.canv_h)
self.canvas.grid(column = 0, row = 0)
return
class GuiMoonMove(tk.Frame):
def __init__(self, master):
mon_h = 600
mon_w = 1250
tk.Frame.__init__(self, master)
self.frame = tk.Frame(master, width=1000, height=200, bd=2)
self.master.title('Move')
self.master.minsize(height = mon_h, width = mon_w)
fr_canvas = tk.Frame(self.master)
fr_canvas.place(relx=0.23, rely=0.01, anchor="nw")
fr_button = tk.Frame(self.master)
fr_button.place(relx=0.02, rely=0.06, anchor="nw")
self.canv_h = 600
self.canv_w = 950
self.lbl_view = tk.BooleanVar()
chb_view_on_moon = tk.Checkbutton(fr_button, text="Pohled na Měsíc", variable = self.lbl_view, \
onvalue=True, offvalue=False,command = self.callback)
chb_view_on_moon.grid(column= 0, row= 4,pady = 10)
self.canvas = tk.Canvas(fr_canvas, bg='white', width = self.canv_w, height=self.canv_h)
self.canvas.grid(column = 0, row = 0)
def callback(self,*args):
if self.lbl_view.get()==True:
self.view_on_moon = ViewOnMoon(parent = self.master)
else:
self.vom.destroy()
if __name__=="__main__":
root = tk.Tk()
app = GuiMoonMove(master = root)
app.mainloop()

Passing an object in lambda in tkinter button callback

I am trying to use lambda to create callbacks for tkinter buttons.
There are multiple buttons and each callback needs to pass an object inside it. Following code is what I am doing and is running fine
var0 = tk.StringVar()
label = tk.Label(top, bg = "White",height = 2, width = 12,textvariable=var0, justify="right")
def b0Callback(var):
var.set(var.get()+"0")
return
# creating a label which will print value of the any of the 0-9 button pressed
# creating a button 0
b0 = tk.Button(numFrame0, height = 1, width = 4, bg = "grey", text =
"0",command = lambda: b0Callback(var0))
#there are more buttons like that
var0 is used to update a label. Above code is working fine but I have to create callback for 0 to 9 and I have to just repeat above definition. So I tried using following example from this tutorial
def myfunc(n):
return lambda a : a * n
mydoubler = myfunc(2)
mytripler = myfunc(3)
print(mydoubler(11))
print(mytripler(11))
Using it I did following
def Callback(n):
return lambda var.set(var.get()+n)
b0Callback = Callback("0")
This shows error invalid index in the return line at var.set
Is there any way to pass var0 in this case to avoid this error?
Maybe its only me, but I don't see a reason for using lambda if you just want to add a number to the label text.
Lets make a function for it that gets your StringVar() as a variable and adds some number to it:
def button_callback(str_var, number):
str_var.set(str_var.get() + str(number))
To run this command we simply put it in the code as a lambda function, otherwise it will run upon initialization (because we are providing a function instead of a reference).
So to run it within a button we declare it like this:
my_button = Button(root, text='Some text here', command=lambda: button_callback(my_string_var, 5))
The '5' could be potentially changed to any other number.
I have now solved the problem, here is the final code:
I have also changed the number of buttons to 300 and added code to arrange them all in a nice grid, just for fun. (You can change this to however many you want by changing for number in range(1, whatever).
import tkinter as tk
class Window(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.var0 = tk.StringVar()
self.var0.set('0')
# creating buttons and adding them to dictionary
self.buttons = {}
for number in range(1, 301):
self.buttons.update({'button' + str(number): tk.Button(self, height=1, width=4, bg="grey", text=number)})
label = tk.Label(self, textvariable=self.var0, font='none 50')
label.grid(column=0, row=0)
for button in self.buttons:
self.buttons[button].bind('<Button-1>', lambda event, num=button[6:]: self.Callback(event, num))
self.buttons[button].grid(column=(int(button[6:]) % 10), row=(int(button[6:]) / 10) + 1)
def Callback(self, event, num):
self.var0.set(num)
self.update()

Clearing specific widgets in tkinter

I am attempting to have a function in python that clears the screen upon a button being pressed. I am aware of grid_remove but am unsure of how to use it. Also is there a way to clear everything from a specific function, ie both "hi" and "clear"?
from tkinter import *
class Movies:
def __init__(self, master):
hi = Label(text = "Hello")
hi.grid(row = 0, column = 0)
clear = Button(text = "Click", command=self.clear)
clear.grid(row = 1, column = 0)
def clear(self):
hi.grid_remove()
root = Tk()
gui = Movies(root)
root.geometry("100x200+0+0")
root.mainloop()
You could use the built in winfo_children method if you're just wanting to toggle hiding / showing all of the widgets in whatever parent holds the widgets. Small example:
from tkinter import *
class Movies:
def __init__(self, master):
self.master = master
self.state = 1
for i in range(5):
Label(self.master, text='Label %d' % i).grid(row=0, column=i)
self.magic_btn = Button(self.master, text='Make the Magic!',
command=self.magic)
self.magic_btn.grid(columnspan=5)
def magic(self):
self.state = not self.state
for widget in self.master.winfo_children(): #iterate over all child widgets in the parent
#Comment out to clear the button too, or leave to toggle widget states
if widget != self.magic_btn: #or some other widget you want to stay shown
if self.state:
widget.grid()
else:
widget.grid_remove()
print(self.state)
root = Tk()
gui = Movies(root)
root.mainloop()

Creating entry and buttons linked in tkinter

I'm creating a GUI where I need to create certain number of entries and buttons in Tkinter. I'd like to create all these in a for loop. As actions, when I press any of the button, it should transfer the value of the Entry to the callback of the button next to it.
This is what I've done so far but it's not working yet.
n=0
self.button = []
self.entFreq = []
for calVal in calibration:
lbl = Label(self.calFrame)
lbl.configure(text = "Set amplitud to " + calVal)
lbl.configure(background=self.bg_App, fg = "white")
lbl.grid(row=n, column=0)
self.entFreq.append(Entry(self.calFrame, width=10))
self.entFreq[n].grid(row=n, column=1, padx = 10)
#Construction Button send frequency
self.button.append(Button(self.calFrame, text="Cal", borderwidth=0, relief="groove", command = lambda n=self.entFreq[n].get(): self.get_val(n)))
self.button[n].configure(bg="#FFF3E0")
self.button[n].grid(row=n, column=2)
n+=1
def get_val(self, var):
print "Got this:", str(var)
I'm just getting blank in the var function. How to link those two?
You're putting too much code into your lambdas. You only need to pass in n, and get_val can do the rest of the work:
self.button.append(Button(..., command=lambda n=n: self.get_val(n)))
...
def get_val(self, n):
value = self.entFreq[n].get()
print "Got this:", value
You might want to consider defining a class for this set of label, entry and button, since they are designed to work together and you're making several sets.
You could, for example,pass in a label and a function to call when the user clicks the button. For example:
class LabelEntry(object):
def __init__(self, parent, text, command):
self.command = command
self.label = Label(parent, text=text)
self.entry = Entry(parent)
self.button = Button(parent, text="Cal", command=self.call_command)
def call_command(self):
value = self.entry.get()
self.command(value)
You would use it something like this:
def some_function(self, value):
print "the value is", value
...
for calVal in calibration:
le = LabelEntry(frame,
text="Set aplitud to " + calVal,
command=self.some_function)
le.label.grid(...)
le.entry.grid(...)
le.button.grid(...)

Problems in Python getting multiple selections from Tkinter listbox

This is the same problem I posed earlier today and which a couple of you tried to help me with, but I can't get it to work. All I want to do is to populate "ichose" with the multiple selections I make when I click on the listbox.
import Tkinter as tk
from Tkinter import *
global ichose
class App(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self,master)
self.master=master
self.grid()
self.ichose = ()
self.l = Listbox(self, height=10, selectmode=EXTENDED)
# Selectmode can be SINGLE, BROWSE, MULTIPLE or EXTENDED. Default BROWSE
self.l.grid(column=0, row=0, sticky=(N,W,E,S))
self.l.bind("Double-Button-1", self.entered)
s = Scrollbar(self, orient=VERTICAL, command=self.l.yview)
s.grid(column=0, row=0, sticky=(N,S,E))
self.l['yscrollcommand'] = s.set
for i in range(1,101):
self.l.insert('end', 'Line %d of 100' % i)
def entered(self, event):
self.ichose = self.selection_get()
self.ichose = ('hello')
root=tk.Tk()
root.title('Listbox Problem')
root.geometry('200x200')
app=App(root)
root.mainloop()
print app.ichose
Whatever I do, "ichose" comes out as an empty tuple ().
It's clear that the function "entered" is never called because I never see the test string 'hello'.
I also don't know what the various options are as in "Double-Button-", "<>" etc. Where can I find a list and explanation of each one?
If somebody could please just modify my program so the "print ichose" works, I'd be really grateful. You can see from my program that I don't really know what I'm doing but am keen to learn. Thank you.
I've finally found the answer to my own question. This is REALLY useful if you want to capture multiple responses from a listbox. I've commented a lot. Hope it helps!
import Tkinter as tk
from Tkinter import *
class App(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self,master)
self.master=master
self.grid()
self.ichose = []
self.l = Listbox(self, height=10, selectmode=MULTIPLE)
# Selectmode can be SINGLE, BROWSE, MULTIPLE or EXTENDED. Default BROWSE
self.l.grid(column=0, row=0, sticky=(N,W,E,S))
s = Scrollbar(self, orient=VERTICAL, command=self.l.yview)
s.grid(column=0, row=0, sticky=(N,S,E))
self.l['yscrollcommand'] = s.set
for i in range(1,101):
self.l.insert('end', 'Line %d of 100' % i)
# Create Textbox that will display selected items from list
self.selected_list = Text(self,width=20,height=10,wrap=WORD)
self.selected_list.grid(row=12, column=0, sticky=W)
# Now execute the poll() function to capture selected list items
self.ichose = self.poll()
def poll(self):
items =[]
self.ichose = []
# Set up an automatically recurring event that repeats after 200 millisecs
self.selected_list.after(200, self.poll)
# curselection retrieves the selected items as a tuple of strings. These
# strings are the list indexes ('0' to whatever) of the items selected.
# map applies the function specified in the 1st parameter to every item
# from the 2nd parameter and returns a list of the results. So "items"
# is now a list of integers
items = map(int,self.l.curselection())
# For however many values there are in "items":
for i in range(len(items)):
# Use each number as an index and get from the listbox the actual
# text strings corresponding to each index, and append each to
# the list "ichose".
self.ichose.append(self.l.get(items[i]))
# Write ichose to the textbox to display it.
self.update_list()
return self.ichose
def update_list(self):
self.selected_list.delete(0.0, END)
self.selected_list.insert(0.0, self.ichose)
root=tk.Tk()
root.title('Listbox Multi-Capture')
root.geometry('200x340')
app=App(root)
root.mainloop()
print app.ichose
# ----------------[ Listbox EXAMPLE ]----------------
self.sarcCountries = (
"Bangladesh",
"India",
"Pakistan",
"Nepal",
"Bhutan",
"Sri Lanka",
"Afghanistan"
)
self.listData = StringVar(value = self.sarcCountries)
self.listbox = Listbox(
master = self,
height = 10,
listvariable = self.listData,
selectmode = MULTIPLE,
selectbackground = "#BC80CC"
)
self.listbox.bind("<<ListboxSelect>>", self.OnListboxSelectionChanged)
self.listbox.pack(fill = BOTH, expand = 0, padx = 10, pady = 10)
# ----------------[ Listbox EXAMPLE ]----------------
def OnListboxSelectionChanged(self, val):
# NOTE: If your listbox's select mode is MULTIPLE, then you may use this portion of code
selections = val.widget.curselection()
print("---------------------------")
if (selections != ()):
for index in selections:
print(self.sarcCountries[int(index)])
print("---------------------------")

Categories