How to use a Python generator function in tkinter? - python

Generator functions promise to make some code easier to write. But I don't always understand how to use them.
Let's say I have a Fibonacci generator function fib(), and I want a tkinter application that displays the first result. When I click on a button "Next" it displays the second number, and so on. How can I structure the app to do that?
I probably need to run the generator in a thread. But how do I connect it back to the GUI?

You don't need a thread for this particular problem. You just need to instantiate the generator, then use next to get a new value each time the button is pressed.
Here's a simple example:
import tkinter as tk
def fibonacci_sequence():
'''Generator of numbers in a fibonacci sequence'''
a,b = 1,1
while True:
yield a
a,b = b, a+b
def do_next():
'''Updates the label with the next value from the generator'''
label.configure(text=next(generator))
root = tk.Tk()
label = tk.Label(root, text="")
button = tk.Button(root, text="Next", command=do_next)
label.pack(side="top")
button.pack(side="bottom")
# Initialize the generator, then us it to initialize
# the label
generator = fibonacci_sequence()
do_next()
root.mainloop()

Not expert in tkinter but one way is to use function StringVar to be able to update its value and using text box for output.
Try this out, hope it gives you a clue.
import tkinter as tk
fibo_list = [1,1]
def fibo():
fibo_list.append(fibo_list[-1] + fibo_list[-2])
variable.set(fibo_list[-1])
show_fibo_value()
def show_fibo_value():
text_box.insert(index='end',chars=variable.get()+'\n')
root = tk.Tk()
variable = tk.StringVar()
text_box = tk.Text()
text_box.pack()
button = tk.Button(root, text="Next", command=fibo)
button.pack()
root.mainloop()

Related

Make Buttons on Tkinter automatically go to next line when reach border of the frame

I'm trying to make a tkinter script that will print a lot of buttons with functions like this.
from tkinter import *
def printbutton(x:int):
print(x)
root = Tk()
root.geometry('500x500')
for x in range(1,100):
Button(root, text = str(x), command = lambda:printbutton(x)).pack(side="left")
root.mainloop()
But it print all button in a line and expanded outside of my GUI. I want my button automatically go to the next line for convenient, but I don't know how to do it.
Firstly, I would suggest use grid() instead of pack() for more clarity in such cases.
Next, to pass the value of the button in function, bind your printbutton(b) with your button itself. This is because at the end of for loop, the value of x is 99 and this x will be then passed with every button.
Now, to print buttons in a new line after you reach up to the width of the window, I used a simple logic, you can edit the value of width variable according to your window and button size.
So, here's what you are looking for:
from tkinter import *
def printbutton(btn):
print(btn["text"])
root = Tk()
root.geometry('500x500')
r,c = 0,0
width = 21
for x in range(1,100):
b = Button(root, text = str(x))
b.grid(row=r, column=c, sticky=N+S+E+W) #use grid system
b["command"] = lambda b=b: printbutton(b) #now b is defined, so add command attribute to b, where you attach your button's scope within a lambda function
#printbutton(b) is the function that passes in the button itself
c+=1
if c==width:
r+=1
c=0
root.mainloop()

tkinter button reffer self atribute

A have a Grid of 38 buttons (num0-num38).
I'm trying to pass the button attribute (text) to the print function.
Code:
def collect_num(num):
print(num)
num0 = tk.Button(buttons, text="0", padx=10, pady=5, fg="white", bg="green", command=collect_num(thisItem.text))
Is there something similar to thisItem.text in Python?
So basically, I would like to print the button name (text="0" - text="38") when the correspondent button is pressed.
Any help with that is appreciated.
Since you asked for an example, take a look here:
from tkinter import *
root = Tk()
MAX_BUTTON = 25 # Maximum number of buttons to be made
def collect_num(num):
print(num) # Print the number
btns = [] # A list to add all the buttons, to make them reusable later
for i in range(MAX_BUTTON): # Looping 25 times or maximum number of buttons, times
btns.append(Button(root,text=i,width=10)) # Creating a button and adding it to list
btns[-1]['command'] = lambda x=btns[-1].cget('text'): collect_num(x) # Changing the command of the button
btns[-1].pack() # Packing it on
root.mainloop()
Here cget() returns the current properties of the widget.
It is highly recommended to do this, instead of creating buttons one by one manually. There is a principle in software engineering called DRY which means Don't Repeat Yourself, which usually means if your repeating yourself, there will possibly be a way to not repeat your self and reduce the code written. And this looping and creating a widget and using lambda to make the command is quiet used frequently when needed to make more than one widget with a pattern.
you need pass the button text using partial(<function>, *args) function
from functools import partial
def collect_num(num):
print(num)
# create the button first and add some specs like `text`
button = tk.Button(
buttons, text="0", padx=10, pady=5, fg="white", bg="green"
)
# add command after creation with the above function and button's name
button.config(command=partial(collect_num, button["text"]))
with the help of this stack question:
text = button["text"]
this is the method with which you can get the text.
or
text = button.cget('text')
or (with the help of #TheLizzard)
text = button.config("text")[-1]

How to delete checkboxes dynamically in tkinter?

I am making a sort of todo app using tkinter. For this I want to generate checkbox dynamically and I have successfully done this using a function but I also want to delete those checkboxes when user press clear button. How can this be done.
name=Stringvar()
ent=Entry(root,textvariable=name).pack()
def clear(ent):
ent.pack_forget()
def generate():
k=name.get()
c=Checkbutton(root,text=k)
c.pack()
btn1=Button(root,text="Submit",command=generate)
btn1.pack()
btn2=Button(root,text="Clear",command=clear)
btn2.pack()
I want to delete the checkbox but I cannot do so as function clear does not read c.pack_forget()
It is very simple to do just store all the objects of Checkbutton created in the generate() function like so
First you need a List.
Tip: Use Dictionary if you need to store more information about the object.
Append each Checkbutton created. (List.append(c)..)
Then pack_forget() the Checkbutton from the List with the help of for loop. If you are not planning to use those Check buttons in future then use destroy() instead of pack_forget() .
Here is the code:
from tkinter import *
root = Tk()
name = StringVar()
check_box_list = []
ent=Entry(root,textvariable=name).pack()
def clear():
for i in check_box_list:
i.pack_forget() # forget checkbutton
# i.destroy() # use destroy if you dont need those checkbuttons in future
def generate():
k=name.get()
c=Checkbutton(root,text=k)
c.pack()
check_box_list.append(c) # add checkbutton
btn1=Button(root,text="Submit",command=generate)
btn1.pack()
btn2=Button(root,text="Clear",command=clear)
btn2.pack()
mainloop()
If you want to remove each one separately instead of clear all then try this.
from tkinter import *
root = Tk()
name = StringVar()
check_box_list = []
ent=Entry(root,textvariable=name).pack()
def clear():
for i in check_box_list:
if i.winfo_exists(): # Checks if the widget exists or not
i.pack_forget() # forget checkbutton
# i.destroy() # use destroy if you dont need those checkbuttons in future
def generate():
k=name.get()
f = Frame(root)
Checkbutton(f, var=StringVar(), text=k).pack(side='left')
Button(f, text='✕', command=f.destroy).pack(side='left')
check_box_list.append(f) # add Frame
f.pack()
btn1=Button(root,text="Submit",command=generate)
btn1.pack()
btn2=Button(root,text="Clear All",command=clear)
btn2.pack()
mainloop()

Updating a label from an entry field on button push with tkinter in Python 3.5.2

I am trying to create a window with a line label, an entry field, a current value label, and an "Update Value" button.
Here is an example:
This is what I have so far. I can get the entered value to print to console, but I can't seem to work out how to get an entered value and change the currentValue Label to reflect that value by pressing the button:
from tkinter import*
main=Tk()
#StringVar for currentValue in R0C2
currentValue = StringVar(main, "0")
#Called by the setValues button, looks for content in the entry box and updates the "current" label
def setValues():
content = entry.get()
print(content)
#This kills the program
def exitProgram():
exit()
#Title and window size
main.title("Title")
main.geometry("350x200")
#Descriptions on the far left
Label(main, text="Duration (min): ").grid(row=0, column=0)
#Entry boxes for values amidship
entry=Entry(main, width=10)
entry.grid(row=0, column=1)
#Displays what the value is currently set to.
currentValue = Label(textvariable=currentValue)
currentValue.grid(row=0,column=2)
#Takes any inputted values and sets them in the "Current" column using def setValues
setValues=Button(text='Set Values',width=30,command=setValues)
setValues.grid(row=9, column=0, columnspan=2)
#Red button to end program
exitButton=Button(main, text='Exit Program',fg='white',bg='red',width=30, height=1,command=exitProgram)
exitButton.grid(row=20, column = 0, columnspan=2)
main.mainloop()
There are a couple of problems with your code.
Firstly, you are overwriting the setValues function with the setValues Button widget, and similarly, you are overwriting the currentValue StringVar with the currentValue Label.
To set a StringVar, you use its .set method.
Don't use plain exit in a script, that's only meant to be used in an interactive interpreter session, the proper exit function is sys.exit. However, in a Tkinter program you can just call the .destroy method of the root window.
Here's a repaired version of your code.
import tkinter as tk
main = tk.Tk()
#StringVar for currentValue in R0C2
currentValue = tk.StringVar(main, "0")
#Called by the setValues button, looks for content in the entry box and updates the "current" label
def setValues():
content = entry.get()
print(content)
currentValue.set(content)
#This kills the program
def exitProgram():
main.destroy()
#Title and window size
main.title("Title")
main.geometry("350x200")
#Descriptions on the far left
tk.Label(main, text="Duration (min): ").grid(row=0, column=0)
#Entry boxes for values amidship
entry = tk.Entry(main, width=10)
entry.grid(row=0, column=1)
#Displays what the value is currently set to.
currentValueLabel = tk.Label(textvariable=currentValue)
currentValueLabel.grid(row=0,column=2)
#Takes any inputted values and sets them in the "Current" column using def setValues
setValuesButton = tk.Button(text='Set Values',width=30,command=setValues)
setValuesButton.grid(row=9, column=0, columnspan=2)
#Red button to end program
exitButton = tk.Button(main, text='Exit Program',fg='white',bg='red',width=30, height=1,command=exitProgram)
exitButton.grid(row=20, column = 0, columnspan=2)
main.mainloop()
BTW, it's a Good Idea to avoid "star" imports. Doing from tkinter import * dumps 130 names into your namespace, which is unnecessary and creates the possibility of name collisions, especially if you do star imports from several modules. It also makes the code less readable, since the reader has remember which names you defined and which ones came from the imported module(s).
In my opinion the easiest way to do this would be using an object orientated method. This way you could declare a button with a command that calls a def which runs self.label.configure(text=self.entry.get()).
This can be seen below:
import tkinter as tk
class App:
def __init__(self, master):
self.master = master
self.label = tk.Label(self.master)
self.entry = tk.Entry(self.master)
self.button = tk.Button(self.master, text="Ok", command=self.command)
self.label.pack()
self.entry.pack()
self.button.pack()
def command(self):
self.label.configure(text=self.entry.get())
root = tk.Tk()
app = App(root)
root.mainloop()
The above creates a label, entry and button. The button has a command which calls a def within the class App and updates the value of the label to be the text contained within the entry.
This all works very smoothly and cleanly and more importantly is drastically easier (in my opinion) to read and update in the future.
From your code you are setting the 'currentValue', which is a StringVar:
#StringVar for currentValue in R0C2
currentValue = StringVar(main, "0")
to an object Label further down in your code. You cannot do this!
#Displays what the value is currently set to.
currentValue = Label(textvariable=currentValue) ** this line is wrong
currentValue.grid(row=0,column=2)
You should name the label something different like:
#Displays what the value is currently set to.
lblCurrentValue = Label(textvariable=currentValue)
lblCurrentValue.grid(row=0,column=2)
Then in your "setValues" method you should use 'StringVar.set(value) to update the label like so:
def setValues():
content = entry.get()
currentValue.set(entry.get())------------------Here I set the value to the entry box value
print(content)
I tend to avoid stringVar and just use:
Label.config(text='*label's text*')
If you need more help I can post you my solution but try and solve it first becasue its the best way to learn. My tip is to make sure you are using correct naming conventions. In tkinter I tend to use lbl..., entryBox... etc before widgets so I know what they are and not to confuse them with variables.

Can't pass variables to function with Tkinter in python

I'm trying to take the textvariable from an entry(a) and then put it in a label in a new window(w).
from Tkinter import *
def abind(avar):
print avar
w=Toplevel()
at=Label(w, text=avar).pack()
w.mainloop()
app=Tk()
at=StringVar()
a=Entry(app,textvariable=at)
avar=at.get()
a.pack()
a.focus()
b=Button(app, command=abind(avar)).pack()
app.mainloop()
It either prints blank, if I take the avar out of the parantheses after abind, or opens a new window immeadiatley and doesn't display the button widget, if I leave the avar.
There are two main problems with your code:
with avar=at.get(), the avar variable has the value of the text variable at that point in time, i.e. it is just the empty string
with Button(app, command=abind(avar)), you are calling the function abind(avar) and using its result as a command, i.e. None
Also, by doing b=Button(...).pack(), b is the result of pack(), i.e. None. This is not related to your problem, but it's probably not what you intended, either. Try this:
b = Button(app, command=lambda: abind(at.get()))
b.pack()
This uses lambda to create a new anonymous function that will get the current value from at using at.get() and call abind with that value, setting the text of the Label accordingly.
If you want the Label to be updated as you type additional text into the Entry, try this:
def abind(avar):
...
at = Label(w, textvariable=avar) # use textvariable
at.pack # pack again
...
...
b = Button(app, command=lambda: abind(at)) # pass at itself
...

Categories

Resources