Tkinter scale gets stuck each time a function is called - python

I have a problem where the tkinter Scale widget seems to get stuck whenever I run a seemingly big function.
This is the code:
from tkinter import Tk, Button, Frame, Scale
root = Tk()
slider = Scale(root, orient='horizontal')
slider.pack()
frame = Frame(root)
frame.pack()
num = 0
def buttons():
for widget in frame.winfo_children():
widget.destroy()
for i in range(50):
Button(frame, text='Button' + str(i)).pack()
def basic():
global num
slider.set(num)
num += 1
print(num)
if num <= 100:
slider.after(100, basic)
if __name__ == '__main__':
buttons()
basic()
root.bind('<space>', lambda x: buttons())
root.mainloop()
What I want my program to do is update the slider normally even when I press 'Space' (meaning calling the buttons() function)
If you watch closely each time you press Space the slider will get stuck a little.
Since I'm using the slider for an Mp3 player in order to show time elapsed, this loss of time is extremely important for example for audio files of 10 or so seconds since the slider falls behind a lot making it seem as if it's working wrong \
I'd also like to point out that destroying the buttons and then repacking them is necessary for me.
I suspect that this happens because the program has to go over the buttons() function something that takes time since it's creating 50 buttons. Or am I mistaken?
Can I avoid that lag?
PS: As I mentioned in my comment:
I normally have a button that renames a (button) which is a song and in order for them to alphabetically ordered after renaming i need to recall the function that draws them. If I only configure tha name of the button (and not redraw them), it will stay in place and not move down or up depending on its name, while on the actual directory the order will change leading to inappropriate behavior such as playing the same song
Here are some images for better understanding:
Thanks in advance!

Look at this code:
import tkinter as tk
def config_buttons():
# Get the `text` of the first button
starting_value = int(buttons[0].cget("text")) + 1
# Iterate over all of the buttons
for i, button in enumerate(buttons, start=starting_value):
# Change the button's `text` and `command` atributes
button.config(text=i, command=lambda i=i:print("Clicked %i"%i))
root = tk.Tk()
buttons = []
add_button = tk.Button(root, text="+1 on all buttons", command=config_buttons)
add_button.pack()
for i in range(50):
button = tk.Button(root, text=i, command=lambda i=i:print("Clicked %i"%i))
button.pack()
buttons.append(button)
root.mainloop()
When the add_button buttons is pressed, I iterate over all of the buttons and change their text and command attributes. As I am not creating new buttons, the function runs very fast.
You can implement something similar in your code. Basically, avoid creating new buttons and just update the ones you already have on the screen.

Related

Tkinter winfo_ismapped() method not working

I have a program in python which in which I use Listboxes, buttons and labels. So today I came conflicting with a problem. I wanted to make my listbox appear when a button is clicked and disappear when the same button is clicked again. How can I achieve this? I tried using the winfo_ismapped() method but didnt seem to work. I think I might have done something crazy. If so, please point it out and give me a corrected answer. Else please tell me a better way to do it.
My Code:
import tkinter as tk
from tkinter import *
root = tk.Tk()
root.geometry('500x500')
def showMenu():
overlay = Listbox(root, bg="green", height=22, width=58)
if overlay.winfo_ismapped() == 0:
overlay.place(x=0,y=35)
else:
overlay.placeforget()
button = tk.Button(root,text="place/remove", command=showMenu)
button.place(x=0,y=0)
root.mainloop()
Actually it comes when I press the button but hide after I press it again.
In the same way I have another issue with these labels too.
CODE:
import tkinter as tk
root = tk.Tk()
def placeFun():
successtext = tk.Label(root, text="Success", anchor='nw', bg="#212121", fg="#ff3300",font=("Consolas", 15, "bold"))
if successtext.winfo_ismapped() == 0:
successtext.place(x=0,y=50)
else:
succestext.forget()
button = tk.Button(root, text='place/rem', width=25, command=placeFun)
button.place(x=0,y=0)
root.mainloop()
Please Note: I want a professional way to handle this, I said it because, I know a way in which we use variables like:
globalvartimes = 0
def somefunc():
if times % 2 == 0:
show the listbox
global times
times += 2
else:
remove the listbox
times += 1
*This shows the listbox when times is even and remove it when it's odd.
These makes the code look non-professional and long.
The problem is every time showMenu() is called another Listbox is created. To fix that, create the Listbox outside of the function (so it's a global).
(I also noticed you misspelled the name of place_forget() method.)
import tkinter as tk
from tkinter import *
root = tk.Tk()
root.geometry('500x500')
def showMenu():
if overlay.winfo_ismapped(): # Placed?
overlay.place_forget()
else:
overlay.place(x=0,y=35)
overlay = Listbox(root, bg="green", height=22, width=58)
button = tk.Button(root,text="place/remove", command=showMenu)
button.place(x=0,y=0)
root.mainloop()
This looks like it is what is wrong with your Label example, too.
Note: If you want to write "professional" code, I suggest you read (and start following) the
PEP 8 - Style Guide for Python Code.

Delete all from TkInter Canvas and put new items while in mainloop

The goal is to achieve different "screens" in TkInter and change between them. The easiest to imagine this is to think of a mobile app, where one clicks on the icon, for example "Add new", and new screen opens. The application has total 7 screens and it should be able to change screens according to user actions.
Setup is on Raspberry Pi with LCD+touchscreen attached. I am using tkinter in Python3. Canvas is used to show elements on the screen.
Since I am coming from embedded hardware world and have very little experience in Python, and generally high-level languages, I approached this with switch-case logic. In Python this is if-elif-elif...
I have tried various things:
Making global canvas object. Having a variable programState which determines which screen is currently shown. This obviously didn't work because it would just run once and get stuck at the mainloop below.
from tkinter import *
import time
root = Tk()
programState = 0
canvas = Canvas(width=320, height=480, bg='black')
canvas.pack(expand=YES, fill=BOTH)
if(programState == 0):
backgroundImage = PhotoImage(file="image.gif")
canvas.create_image(0,0, image=backgroundImage, anchor=NW);
time.sleep(2)
canvas.delete(ALL) #delete all objects from canvas
programState = 1
elif(programState == 1):
....
....
....
root.mainloop()
Using root.after function but this failed and wouldn't show anything on the screen, it would only create canvas. I probably didn't use it at the right place.
Trying making another thread for changing screens, just to test threading option. It gets stuck at first image and never moves to second one.
from tkinter import *
from threading import Thread
from time import sleep
def threadFun():
while True:
backgroundImage = PhotoImage(file="image1.gif")
backgroundImage2 = PhotoImage(file="image2.gif")
canvas.create_image(0,0,image=backgroundImage, anchor=NW)
sleep(2)
canvas.delete(ALL)
canvas.create_image(0,0,image=backgroundImage2, anchor=NW)
root = Tk()
canvas = Canvas(width=320, height=480, bg='black')
canvas.pack(expand=YES, fill=BOTH)
# daemon=True kills the thread when you close the GUI, otherwise it would continue to run and raise an error.
Thread(target=threadFun, daemon=True).start()
root.mainloop()
I expect this app could change screens using a special thread which would call a function which redraws elements on the canvas, but this has been failing so far. As much as I understand now, threads might be the best option. They are closest to my way of thinking with infinite loop (while True) and closest to my logic.
What are options here? How deleting whole screen and redrawing it (what I call making a new "screen") can be achieved?
Tkinter, like most GUI toolkits, is event driven. You simply need to create a function that deletes the old screen and creates the new, and then does this in response to an event (button click, timer, whatever).
Using your first canvas example
In your first example you want to automatically switch pages after two seconds. That can be done by using after to schedule a function to run after the timeout. Then it's just a matter of moving your redraw logic into a function.
For example:
def set_programState(new_state):
global programState
programState = new_state
refresh()
def refresh():
canvas.delete("all")
if(programState == 0):
backgroundImage = PhotoImage(file="image.gif")
canvas.create_image(0,0, image=backgroundImage, anchor=NW);
canvas.after(2000, set_programState, 1)
elif(programState == 1):
...
Using python objects
Arguably a better solution is to make each page be a class based off of a widget. Doing so makes it easy to add or remove everything at once by adding or removing that one widget (because destroying a widget also destroys all of its children)
Then it's just a matter of deleting the old object and instantiating the new. You can create a mapping of state number to class name if you like the state-driven concept, and use that mapping to determine which class to instantiate.
For example:
class ThisPage(tk.Frame):
def __init__(self):
<code to create everything for this page>
class ThatPage(tk.Frame):
def __init__(self):
<code to create everything for this page>
page_map = {0: ThisPage, 1: ThatPage}
current_page = None
...
def refresh():
global current_page
if current_page:
current_page.destroy()
new_page_class = page_map[programstate]
current_page = new_page_class()
current_page.pack(fill="both", expand=True)
The above code is somewhat ham-fisted, but hopefully it illustrates the basic technique.
Just like with the first example, you can call update() from any sort of event: a button click, a timer, or any other sort of event supported by tkinter. For example, to bind the escape key to always take you to the initial state you could do something like this:
def reset_state(event):
global programState
programState = 0
refresh()
root.bind("<Escape>", reset_state)

Buttons to remove themselves in Tkinter

I'm sorry if this has been asked already, but I haven't been able to find it. I'm also just starting to learn programming so feedback is appreciated. :)
My end goal is to create an 8 by "x" grid of buttons that change their own color when pushed. I want to use this to make a grid I can upload to the POV toy I've built. This code creates a column of 8 buttons each with a callback passing itself as an argument. The idea being the callback function can do things to the button like change it's color, or delete it.
import Tkinter
def unpack(i):
buttons[i].pack_forget()
print i
top = Tkinter.Tk() buttons = [] for i in range(0, 8):
buttons.append(Tkinter.Button(top, text='Hello', command=lambda: unpack(i)))
for button in buttons:
button.pack()
top.mainloop()
When I do this I get a windows with column of 8 buttons, and when I click on one one gets deleted. When I click on a second nothing happens. In my command prompt I get the number 7 printed no matter which button I press. I suspect the problem is in the for loop that creates the buttons, but I have no idea how to fix it.
Thanks!
Pass the button object to the callback function instead of the index, because the index is change after the item deletion in the list.
import Tkinter
top = Tkinter.Tk()
for i in range(0, 8):
btn = Tkinter.Button(top, text='Hello')
btn['command'] = lambda b=btn: b.pack_forget()
btn.pack()
top.mainloop()
NOTE: To prevent late binding problem, I used default parameter in the above code.

python label not changing dynamically

I want to see continuously changing value of label in tkinter window. But I'm not seeing any of it unless I make a keyboard interrupt in MS-CMD while running which shows me the latest assigned value to label. Plz tell me ..What's going on & what's the correct code ??
import random
from Tkinter import *
def server() :
while True:
x= random.random()
print x
asensor.set(x)
app=Tk()
app.title("Server")
app.geometry('400x300+200+100')
b1=Button(app,text="Start Server",width=12,height=2,command=server)
b1.pack()
asensor=StringVar()
l=Label(app,textvariable=asensor,height=3)
l.pack()
app.mainloop()
The function server is called when you click the button, but that function contains an infinite loop. It just keep generating random numbers and sending these to asensor. You are probably not seeing any of it because the server function is run in the same thread as the GUI and it never gives the label a chance to update.
If you remove the while True bit from your code, a new number will be generate each time you click the button. Is that what you wanted to do?
Edit after comment by OP:
I see. In that case your code should be changed as follows:
import random
from Tkinter import Tk, Button, Label, StringVar
def server():
x = random.random()
print x
asensor.set(x)
def slowmotion():
server()
app.after(500, slowmotion)
app = Tk()
app.title("Server")
app.geometry('400x300+200+100')
b1 = Button(app, text="Start Server", width=12, height=2, command=slowmotion)
b1.pack()
asensor = StringVar()
asensor.set('initial value')
l = Label(app, textvariable=asensor, height=3)
l.pack()
app.mainloop()
I also introduced a new function, slowmotion, which does two things: 1) calls server, which updates displays the value, and 2) schedules itself to be executed again in 500ms. slowmotion is first ran when you first click the button.
The problem with your code was that it runs an infinite loop in the main GUI thread. This means once server is running, the GUI will not stop and will not get a chance to display the text you asked it to display.

Flashing Tkinter Labels

I'm a beginner programmer in python and have recently began using tkinter though I have come across a problem which I can't solve.
Basically I have two entry boxes.
Entry1 = message
Entry2 = no. of flashes
(This is just an example of what I need.)
All I need is a for loop for a label to pop up and flash entry1 as many times as entry2, yes I realize how to get the entry inputs but I have no idea how to get the label to continuously flash, I have tried pack_forget and .destroy methods for the label in a loop, but unfortunately it does not display as it almost instantly clears it from the screen again.
The basic idea is to create a function that does the flash (or half of a flash), and then use after to repeatedly call the function for as long as you want the flash to occur.
Here's an example that switches the background and foreground colors. It runs forever, simply because I wanted to keep the example short. You can easily add a counter, or a stop button, or anything else you want. The thing to take away from this is the concept of having a function that does one frame of an animation (in this case, switching colors), and then scheduling itself to run again after some amount of time.
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.label = tk.Label(self, text="Hello, world",
background="black", foreground="white")
self.label.pack(side="top", fill="both", expand=True)
self.flash()
def flash(self):
bg = self.label.cget("background")
fg = self.label.cget("foreground")
self.label.configure(background=fg, foreground=bg)
self.after(250, self.flash)
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()

Categories