Multiple After on Tkinter made the GUI frozen - python

recently im working using Python with Tkinter gui. The problem is , i want to show some lines by changing its state from hidden to normal within some interval, let's say it 1s.
So , the line will show up 1 by 1 within 1 second.
When i tried, the commandline works perfectly(i print some text on windows cmd) But, the gui frozen until the end of the computation then all of the lines showed up(not 1 by 1), idk why? im new with python :(
here's my dummy code
def delay():
allline=mainframe.find_withtag('line')
for i in allline:
tags=mainframe.gettags(i)
print(tags[0])
root.after(1000, mainframe.itemconfigure(tags[0],state='normal'))
......
mainframe.create_line((50,50,100,100),...,tags=('line1','line'),state='hidden')
mainframe.create_line((150,150,100,100),...,tags=('line2','line'),state='hidden')
let's say i have a button which trigger delay function.
Thanks for ur help! sorry for my bad english :)

Rather than using a for loop like this, get all the lines into a list and call a function which removes the first line from the list, shows the line and then have the function call itself using after. Next time the function is called the next item is removed from the list, line made visible etc etc. This way tkinter has time to update the screen.
import tkinter as tk
lines = []
def showNextLine():
global lines
try:
nextLine = lines.pop(0)
canvas.itemconfigure(nextLine,state='normal')
root.after(1000,showNextLine)
except IndexError:
pass
#No more items in list.
root = tk.Tk()
canvas = tk.Canvas()
canvas.grid()
canvas.create_line((50,50,100,100),fill='red',tags=('line1','line'),state='hidden')
canvas.create_line((50,60,100,110),fill='blue',tags=('line2','line'),state='hidden')
canvas.create_line((50,70,100,120),fill='yellow',tags=('line3','line'),state='hidden')
canvas.create_line((50,80,100,130),fill='green',tags=('line4','line'),state='hidden')
lines = list(canvas.find_withtag('line'))
print(type(lines),lines)
root.after(1000,showNextLine)
root.mainloop()

Related

Create a function that ends mainloop and starts new one in tkinter

I'm writing my first GUI program today using Tkinter and I have stumbled onto a problem. I am trying to make a game that starts with an introduction window that closes after you press a button, then opens a new window where you can choose one of two modes. Unfortunately, I just can't get it running. It looks a little something like this.
#These are the functions that I defined to make it work
def start():
root.destroy()
def Rules_mode_1():
root.destroy
rules1 = Tk()
understood1 = Button(rules1, text="I understood", command="Start_game_mode_1")
understood.pack()
rules1.mainloop
# I haven't added rules 2 yet cause I couldn't get it to work with rules 1 so I haven't even #bothered but it would be the same code just switching the 1 for a 2. But really it isn't even
#necessary to have 2 different rule functions because the rules are the same but I couldn't think
#of another way to go about it. if you have an idea let me know
def Start_game_mode_1():
rules1.destroy #<----- THIS IS WHERE THE PROBLEM LIES. JUST DOESN'T RUN
gamemode1 = Tk()
#Here I would have the game
gamemode1.mainloop()
#now same here don't have gamemode 2 yet cause it just doesn't work yet
#This is where it really starts
root = Tk()
startbutton = Button(root, text="Start", command=start)
startbutton.pack
root.mainloop
root = Tk()
def mode():
mode1 = Button(root, command=Rules_mode_1)
mode1.pack
mode2 = #Buttonblablabla
mode()
root.mainloop()
Now I've been trying around for hours, trying to give the mainloops different names. For example giving the
rules1.mainloop
#the name
root.mainloop
but that obviously didn't work. I tried it with dozens of helper function and with the lambda expression and did hours of research but just can't seem to fix it. Does anybody have any ideas? Please be respectful and keep in mind it's my first time using Tkinter.
Thank you for your help!
After the comments didn't really help me I just tried things out for hours and in case anybody ever is having a a similar problem and reads this: The rules1 variable is inside a function, and therefore only local, which means it can't be destroyed in another function. I fixed it by making it a global, like:
def Rules_mode_1():
root.destroy
global rules1
rules1 = Tk()
understood1 = Button(rules1, text="I understood", command="Start_game_mode_1")
understood.pack()
rules1.mainloop
After that I could destroy the mainloop in the next function.

Tkinter: Grab content of a ScrolledText text pad

all. I'm working on a simple Notepad-like program that saves files and closes the program when the escape key is pressed. I mention this because it is in this method that the program runs into problems. textpad is a ScrolledText object.
This line:
`contents = self.textPad.get(self, 1.0, END)`
results in the following error:
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1535, in __call__
return self.func(*args)
File "todopad.py", line 24, in save_and_quit
contents = self.textPad.get(self, 1.0, END)
AttributeError: Event instance has no attribute 'textPad'
I know this is the problem, because the program executes and terminates without issue when this line is commented out. Although I don't understand the error at all.
This has been a very long-winded way of asking: How can I retrieve the contents of a ScrolledText text pad and save it to a variable or directly write it to a file? And also an explanation about the error message?
Thank you in advance.
EDIT: As requested, here is the code for the entire thing.
import sys
import Tkinter
from Tkinter import *
from ScrolledText import *
root = Tkinter.Tk(className = "TodoPad");
textPad = ScrolledText(root, width = 80, height = 20)
def start_and_open():
textFile = open('/home/colin/documents/prog/py/todopad/todo', 'r')
contents = textFile.read()
textPad.insert('1.0', contents)
textFile.close()
def save_and_quit(self):
textFile = open('/home/colin/documents/prog/py/todopad/todo', 'w')
#contents = self.textPad.get(self, 1.0, END) # The line in question
#textFile.write(contents)
textFile.close()
root.destroy()
textPad.pack()
root.bind('<Escape>', save_and_quit)
root.after(1, start_and_open)
root.mainloop()
Since I have posted the whole thing I may as well explain the rationale behind everything. It's supposed to be a fast little thing that opens a to-do list and displays what's already on the list in the text box. I make whatever edits I like, then it saves before closing when I hit escape, problem being is that it doesn't like closing because of the line that I mentioned previously in my post.
First of all, kudos on identifying the problem.
Placing the Widget
To answer what is going wrong: you need to actually place the widget into the window frame. You have a choice between .grid() and .pack(). The first allows you to pick exactly where you want it to go, the second puts in a (technically) default location.
Right now, the instance of your widget is not preset, so your program has no idea where to pull the value from. You have to set a location. i would recommend using .grid(), but for the example .pack() will work as well.
textPad = ScrolledText(root, width = 80, height = 20)
textPad.pack()
Try this, and see if it works. This should fix it, but I could be wrong.
Do NOT just do
textPad = ScrolledText(root, width = 80, height = 20).pack()
The pack() function returns a NULL and will nullify your widget.
Your Issue With Self
Finally, why are you using self at all? You are not using any classes--you need to globalize the variable. The error that is thrown is a result of your program not knowing what class you are pulling the self instance from. Remove the self variables from the program, and put this into the function:
global textPad
This will make it global and all functions will be able to use it.
This should solve all the problems you have right now. However, give it a try and report what happens.
Here are some resources on global variables, getting input from widgets, and saving to files;
http://www.python-course.eu/python3_global_vs_local_variables.php
http://effbot.org/tkinterbook/text.htm
http://www.afterhoursprogramming.com/tutorial/Python/Writing-to-Files/
Happy coding, and best of luck!!

Python: Is it possible to create an tkinter label which has a dynamic string when a function is running in background?

I have created a tkinter GUI for my python script. When I run the script, I want a dynamic string in one of the Label widgets on the GUI window, which will display:
"Working."
Then:
"Working.."
then
"Working..."
and then start from "Working." again until the script is completed.
(Actually I'd prefer a progress bar in this area)
Is it possible?
I wrote two simple scripts to help demonstrate how to do what you want. The first is using the label:
import tkinter as tk
root = tk.Tk()
status = tk.Label(root, text="Working")
status.grid()
def update_status():
# Get the current message
current_status = status["text"]
# If the message is "Working...", start over with "Working"
if current_status.endswith("..."): current_status = "Working"
# If not, then just add a "." on the end
else: current_status += "."
# Update the message
status["text"] = current_status
# After 1 second, update the status
root.after(1000, update_status)
# Launch the status message after 1 millisecond (when the window is loaded)
root.after(1, update_status)
root.mainloop()
The next one is using a progressbar:
import tkinter as tk
# You will need the ttk module for this
from tkinter import ttk
def update_status(step):
# Step here is how much to increment the progressbar by.
# It is in relation to the progressbar's length.
# Since I made the length 100 and I am increasing by 10 each time,
# there will be 10 times it increases before it restarts
progress.step(step)
# You can call 'update_status' whenever you want in your script
# to increase the progressbar by whatever amount you want.
root.after(1000, lambda: update_status(10))
root = tk.Tk()
progress = ttk.Progressbar(root, length=100)
progress.pack()
progress.after(1, lambda: update_status(10))
root.mainloop()
Note however that I couldn't do too much with the progressbar script because progressbars are a little tricky and need to be customized to your script exactly. I just wrote it to maybe shed a little light on the subject. The main part of my answer though is the label script.
Yes, it is possible. There are two ways to do it:
Whenever you want to update the label from your code you can call the_widget.configure(the_text). This will change the text of the label.
You can create an instance of a tkinter.StringVar, and assign it to the textvariable attribute of a label. Whenever you change the value of the variable (via the_variable.set(the_text), the label will automatically update.
Note that for either of these to work, the event loop needs to be able to process events (ie: you won't see anything if your function takes a long time to run and you never call update_idletasks or re-enter the event loop).

Python Tkinter coords function not moving canvas objects inside loop

I have a function that reads locations from a text file, parses them, then moves the respective objects to the locations listed on a tkinter canvas using the coords function. The data is being read from the file and parsed correctly but for some reason the coords function is only moving the objects to the last location listed in the file on the last iteration of the loop.
Do I need to update the canvas somehow after each iteration of my loop? Thanks!
Here is my code:
def playback():
fptr = tkFileDialog.askopenfilename()
filename = open(fptr,"rU")
if not filename:
return
stat.set('REPLAY IN PROGRESS')
gamestatus[0] = 2
for line in filename:
line = line.strip()
#Example line from input file: 'B:#,#,#,#|L:#,#,#,#|R:#,#,#,#'
line = line.split('|')
B_loc = line[0].split(':')[1].split(',')
L_loc = line[1].split(':')[1].split(',')
R_loc = line[2].split(':')[1].split(',')
#Converting strings to ints and lists to tuples to simplify code below
B_tup=(int(B_loc[0]),int(B_loc[1]),int(B_loc[2]),int(B_loc[3]))
L_tup=(int(L_loc[0]),int(L_loc[1]),int(L_loc[2]),int(L_loc[3]))
R_tup=(int(R_loc[0]),int(R_loc[1]),int(R_loc[2]),int(R_loc[3]))
#Moving objects to locations from input file
playingField.coords(pongball.ball,B_tup)
playingField.coords(leftpaddle.paddle,L_tup)
playingField.coords(rightpaddle.paddle,R_tup)
time.sleep(.02)
filename.close()
gamestatus[0] = 0
stat.set('-------Pong-------')
A very good rule of thumb in GUI development is to never call sleep. This freezes the GUI, and even if it's just for a few milliseconds it's still a bad practice.
The proper way to do animation in Tkinter is to write a function that displays a single frame, then reschedules itself using after. This allows the event loop to constantly service events while doing the animation.
For example, take the entire body of the for statement -- minus the sleep -- and put it into a method. Let's call this "refresh". Have this function re-schedule itself using after, like this:
def refresh():
line = get_next_line()
line = line.split('|')
B_loc = line[0].split(':')[1].split(',')
...
# call this function again in 20ms
root.after(20, refresh)
Now all you need to do is implement get_next_line as a function and you're set. This will automatically allow the GUI to redraw itself each time yo update the coordinates.
Of course, you'll need to put in checks for when input is exhausted, and you might want to have a flag the user can set via a button that requests that the animation stops, etc.

Python Tkinter Calculator wont evaluate text from entry widget

I am trying to make a simple calculator app using tkinter, but everytime I run the code below i get an error message saying
Traceback (most recent call last):
File "C:\Python33\Lib\site-packages\pythonwin\pywin\framework\scriptutils.py", line 326, in RunScript
exec(codeObject, __main__.__dict__)
File "C:\Users\csp\Python\Calculator App.py", line 17, in <module>
solved = eval(expression)
File "<string>", line 0
^
SyntaxError: unexpected EOF while parsing
CODE:
from tkinter import *
tk = Tk()
tk.title('Calculator')
inp = Entry(tk,text="Enter Expression Here",width=20)
inp.pack()
exit = False
def exitbtn():
global exit
exit = True
return exit
btn = Button(tk,text="Quit?",command=exitbtn)
btn.pack
canvas = Canvas(tk,width=200,height=200)
canvas.pack()
while not exit:
expression = inp.get()
solved = eval(expression)
canvas.create_text(100,100,text=expression,font=('Times', 15))
canvas.create_text(100,150,text=solved,font=('Times', 15))
if exit == True:
break
tk.destroy()
i am really new to Python and dont understand why the "solved = eval(expression)" line wont work. please help
So, the reason why eval is not working is because when you first start your program, expression is just an empty string. If you go to the python shell, and type in eval(''), you'll see the same error appear.
One solution would be to check if expression is an empty string or not, and do something like this:
expression = inp.get()
if expression != '':
solved = eval(expression)
else:
solved = '?'
However, even after you apply this fix, your program won't work, for unrelated reasons. The primary reason is that you never call tk.mainloop() (or whatever it's called), so the window will not show up.
This is because of your while loop -- what you wanted to do was to constantly check the input field and update your canvas whenever you get new input after running it through eval.
However, GUI programs, in general, don't work that way and require a different mindset and approach while writing them. Instead of writing loops to check and update program state, you write functions that will automatically be called whenever the program state changes (which are called events). It'll feel a bit backwards at first, but over time it'll help make your code cleaner and easier to manage.
You're actually already doing this in one part of your program -- with your exitbtn function. Now, you just need to convert your while loop into a similar function and bind it to the Entry object.
EDIT:
Here's some example code that does what you want:
import sys
from tkinter import *
# Create the GUI
tk = Tk()
tk.title('Calculator')
inp = Entry(tk, text="Enter Expression Here", width=20)
inp.pack()
btn = Button(tk, text="Quit?")
btn.pack()
canvas = Canvas(tk, width=200, height=200)
canvas.pack()
# Create callback functions
def end_program(event):
'''Destroys the window and ends the program without needing
to use global variables or a while loop'''
tk.destroy()
sys.exit() # Automatically ends any Python program
def update_canvas(event):
'''Gets the input, tries to eval it, and displays it to the canvas'''
expression = inp.get()
try:
solved = eval(expression)
except SyntaxError:
# The expression wasn't valid, (for example, try typing in "2 +")
# so I defaulted to something else.
solved = '??'
canvas.delete('all') # remove old text to avoid overlapping
canvas.create_text(100, 100, text=expression,font=('Times', 15))
canvas.create_text(100, 150, text=solved,font=('Times', 15))
# Bind callbacks to GUI elements
btn.bind('<Button-1>', end_program)
inp.bind('<KeyRelease>', update_canvas)
# Run the program
tk.mainloop()
Some things to note:
I moved your code for checking inp and writing to the canvas to the update_canvas function, and got rid of the while loop.
The update_canvas function will automatically be called whenever somebody lets go of a key while typing in the inp object (the <KeyRelease> event).
This can cause some problems -- this will mean your update_canvas function will be called while the user is in the process of typing text into your calculator. For example, what if the user types in 2 + 2 *? It's not a complete expression, so can't be parsed by eval.
To solve this, I just wrapped eval in a try-except to prevent any bad input from mucking up the program.
Similarly, end_program will be called whenever somebody left-clicks on the btn object (the <Button-1> event).

Categories

Resources