I'm working on creating a stopwatch that is given two values. It starts with value A and counts down to 0, then changes to value B, counts down to 0, then goes back to value A, counts down to 0 etc until I close the program (I'll probably add a pause button at some point) and overall it's working really well. However, when it updates the label with the new text, It seems to just be making a new text item and putting it as a layer on top of the previous. I can see then when I go from a single double-digit number to a single digit, and the sentence is shortened, the part of the old sentence that isn't covered can still be seen. So I'm hoping I'm just missing something incredibly simple. The newWindow.update() is what I thought would update the window but it does not appear to be doing that. Below is my snippet of code that handles the logic.
def countdown(timer_count,count_type):
counter = timer_count
count_type = count_type
while counter >= 0:
timer = tk.Label(newWindow, text=f"{count_type} for: {counter}")
timer.config(font=("TkDefaultFont",30))
timer.grid(row=0,column=2)
newWindow.update()
time.sleep(1)
counter -= 1
print(counter)
if count_type == "work":
count_type = "rest"
elif count_type == "rest":
count_type = "work"
return count_type
def interval():
counter_type = "work"
while True:
if counter_type == "work":
counter_type = countdown(int(exer_var.get()),counter_type)
elif counter_type == "rest":
counter_type = countdown(int(rest_var.get()),counter_type)
You are creating a new Label widget each time through your while loop instead of changing the text inside the while loop. That is why it is layering one widget on top of the other, so you need to create your widget, then run the while loop, and set the text to change in the timer.config inside the loop. You should also declare the font in the original tk.Label, no need to change that each trip through the loop. For "some_starting value" it would probably be text = counter
timer = tk.Label(newWindow, font=("TkDefaultFont",30), text="some_starting_value")
while counter >= 0:
timer.config(text=f"{count_type} for: {counter}")
timer.grid(row=0,column=2)
Its hard to say from your code where this is taking place, but here is how its usually done:
Make the label outside all functions in the main block.
timer = tk.Label(newWindow,font=("TkDefaultFont",30)) # Adding the font here itself
Then now inside the function just change its value using config:
def countdown(timer_count,count_type):
counter = timer_count
count_type = count_type
while counter >= 0:
timer.config(text=f"{count_type} for: {counter}") # Update the options for the created label.
timer.grid(row=0,column=2)
So now each time function/loop is running, new labels wont be created and overwritten, instead existing label will be configured.
On a side note, using time.sleep() and while loop is not the very best practice, even with update() it will still cause some kind of disturbance to GUI. Instead, re-arrange your code to use after(ms,func) method, which will not freeze the GUI. You can ask a new question on that if you face any trouble, later.
Related
I'm trying to make a mode for a simple game where you catch items as they fall down using tkinter.
In this mode, you have 60 seconds to catch as many items as you can. All the timer methods I've tried pause the whole program
...tried using an empty label, but the .after pauses the whole program
timerlabel = tkinter.Label(text="")
def timer():
global t, timerdisplay
while t > 0:
t -= 1
timerlabel.after(1000)
c.delete(timerdisplay)
timerdisplay = c.create_text(200, 12, text=t)
c.update()
any idea how to do this?
This is the better way to do it, specifically because after(n) freezes the program for the given time period. Create a function that accepts a number and displays that number. Then, it subtracts one and then reschedules itself to run one second in the future until the number becomes zero.
def timer(t):
global timerdisplay
c.delete(timerdisplay)
timerdisplay = c.create_text(200, 12, text=t)
if t >= 1:
c.after(1000, timer, t-1)
timer(timerdisplay, 10)
To optimize this, you can pass in the canvas item along with the number. You can also just reconfigure the text item rather than deleting and restoring it.
def timer(timerdisplay, t):
c.itemconfigure(timerdisplay, text=t)
if t >= 1:
c.after(1000, timer, timerdisplay, t-1)
timerdisplay = c.create_text(200, 12)
timer(timerdisplay, 10)
I work with tkinter. I want to change the name of the labels. By entering the characters in the field and hitting the button, the labels are renamed one by one. That is, the first time I enter the character "Hello", then that character is inserted in the label; It is then removed from the field. This time I have to enter a character for the next label. And so on until the end
(With the help of the for loop).
I did this but it only works for the first label and does not go to the next label):
win=Tk()
size=3
lbls=[]
frms=[]
def func():
for i in range(6,9):
lbls[i].configure(text=name.get())
for i in range(size):
for j in range(size):
frm=Frame(win,bd=2,relief="sunken")
frm.grid(row=i,column=j)
frms.append(frm)
lbl=Label(frm,bg="white",fg="red",width="5")
lbl.grid(row=0,column=0)
lbls.append(lbl)
name=Entry(win)
name.grid(row=4,column=0)
btn=Button(win,text="Start",font=("Arial",14),command=func)
btn.grid(row=3,column=0)
win.mainloop()
You need to use a counter and increase it inside the function. Basically, for loop wont wait for you to type in something and press the button:
counter = 0
def func():
global counter
lbls[counter].configure(text=name.get())
counter += 1 # Increase it by 1
if counter >= len(lbls): # If counter is greater than items in list, then reset
counter = 0
name.delete(0,'end') # Clear the entry
This will keep updating the text each time you press the button.
Some designing tip: Add columnspan for your button and entry
name.grid(row=4,column=0,columnspan=3)
btn.grid(row=3,column=0,columnspan=3)
I'm trying to learn python and while learning I've come across a bit of a problem.
import time
import pyautogui
def SendScript():
time.sleep(2)
with open('script.txt') as f:
lines = f.readlines()
for line in lines:
time.sleep(2)
pyautogui.typewrite(line.strip())
pyautogui.press('enter')
SendScript()
I'm trying to print something to the screen every second time the 'enter' key has been pressed, but I'm an extreme beginner so I really don't know how to do that. Could someone help me accomplish this task?
You could create a new boolean variable to track if the enter key has been pressed before. That way, every time the for loop iterates, the value of pressed switches and only when the value of pressed is True will it print something.
import time
import pyautogui
def SendScript():
pressed = False
time.sleep(2)
with open('script.txt') as f:
lines = f.readlines()
for line in lines:
time.sleep(2)
if pressed:
print("Something")
pressed = not pressed
pyautogui.typewrite(line.strip())
pyautogui.press('enter')
SendScript()
From a more step-back approach, you could do:
events=['event1', 'event2', 'event3', 'event4', 'event5', 'event6', 'event7', 'event8']
counter = 0
for event in events:
counter += 1
if counter % 2 == 0: # ie do stuff when divisible by 2, ie when its even
print('print what you want to be printed every second time')
else:
pass
Of course you are not looping through events like I do in this example. The point is counting the events and only doing stuff when this count is even.
As indicated in another answer already, a simple toggle can be implemented with a bool and then code which toggles it every time something happens:
thing = False
:
if happens(something):
thing = not thing
This is fine for toggling between two states. A more general approach which allows for more states is to use a numeric variable and a modulo operator:
times = 0
maxtimes = 12
:
if happens(something):
times += 1
if times % maxtimes == 1:
print("ding dong")
The modulo could be compared to 0 instead if you want to print on the 12th, 24th etc iterations instead of the first, the 13th, etc, or of course any other offset within the period if that's what you want.
Another useful trick is to flip-flop between zero and some other value.
value = 0
othervalue = 1234
:
if happens(something):
value = othervalue - value
Of course, you can flip-flop between any two values actually; subtract the current value from their sum to get the other one.
Needless to say, just toggling or flip-flopping isn't very useful on its own; you'd probably add some (directly or indirectly) user-visible actions inside the if happens(something): block too.
You could use a generator for this:
def everySecondTime():
while True:
yield "hi"
yield "not hi"
mygen = everySecondTime()
print(next(mygen))
print(next(mygen))
print(next(mygen))
print(next(mygen))
This prints
hi
not hi
hi
not hi
I'm sure it's clear to you how you could adapt this to do some other actions instead.
Whether this approach is better than just using a boolean is highly debatable, but I thought I'd leave it here so you could learn about generators (the yield keyword) if you want to.
So basically, I have text which is typed out character by character. with the code:
text = "test"
delta = 40
delay = 0
for i in range(len(text) + 1):
s = test_string[:i]
update_text = lambda s=s: canvas.itemconfigure(variable, text=s)
canvas.after(delay, update_text)
delay += delta
This is all inside of a function, lets call: def NewEvent(). What I want to do is create a text button with the text "Skip" which changes delta to a lower number, thus speeding up the animation upon click. I cant seem to figure it out, normally when you make text clickable, it has something along the lines of:
skipbutton = canvas.create_text((400,100), activefill="Medium Turquoise", text="Skip", fill="White", font=('Arial', 30), tags="skip")
canvas.tag_bind('skip', '<ButtonPress-1>', function)
The problem is, it needs to stay within the same function. So I thought of creating an if statement similar like this:
if delta is 40 and skip is ____:
delta = 10
However, I dont know what would come after- (skip is) for it to work, or even if this would work at all... Any help would be appreciated.
You are doing animation in a way that makes your problem very difficult to solve. The problem is that you are scheduling all of the frames of animation before you display the first frame. In order to change the speed you would have to cancel all of the pending jobs and recreate new jobs. This is not the proper way to do animation in Tkinter.
A better solution is to only have a single job active at one time. You do this by having a function that displays one frame of animation and then schedules itself to run again in the future.
The general structure looks like this:
def animate():
<draw one frame of animation>
if <there are more frames>:
root.after(delay, animate)
In your case, each "frame" is simply adding one character to a character string, and your condition at the end is to simply check to see if there are more characters.
A simple implementation is to pass a string into the animate function, have it pull the first character off of the string and append it to the display.
For example:
def update_text(text):
char = text[0]
remainder = text[1:]
current_string = canvas.itemcget(variable, "text")
new_string = current_string + char
canvas.itemconfigure(variable, text=new_string)
if len(remainder) > 0:
canvas.after(delay, update_text, remainder)
To start the animation, give it the string you want to display:
update_text("Hello, world")
This this function depends on a global variable for the delay, writing a function to speed the animation up or slow it down only requires that you modify this delay:
def speedup():
global delay
delay = int(delay/2)
You can call it from a button quite easily:
tk.Button(root, text="Speed up!", command=speedup)
I am trying to use a while loop to create object to populate a list of a user defined type until a certain condition is met. I want to assign a value to each object based on the number of iterations the loop has completed. For example:
class WalkingPeeps:
def___init___(self):
self.location = 0
def leftAt(self,time):
self.tleft = time
def changePos(self):
self.location += random.choice([1, -1])
objectList =[]
location_reached = False
time = 0
while not location_reached
objectList.append(WalkingPeeps())
for x in objectList:
x.tleft = time
if x.location == 20:
location_reached = True
time+=1
print("Person left at: ",x.tleft)
print("Person arrived at: ", time)
However, when it runs, it just set the time the object was created to one less than when the person reached 20. Any pointers? Hints? Thanks in advance.
In python, loops do not define their own scope. When you write
for x in objectList: ...
There variable x is created. At each step in the loop, the variable is updated. When the loop ends, the variable is not destroyed. Therefore, when you print x.tleft, you're printing the time on the last x, which by definition is 20, since you break the loop only when x.tleft == 20.
Furthermore, since you loop over every single element at each phase and update its time, you're setting each elements time to the most reccent time. Therefore, all elements have time == 20, when you terminate. What you mean, I believe, is to only update the last element
What I think you want to print, to check that your loop is working is,
for obj in objectList:
print( obj.tleft )
You would then see the expected behaviour
You also have many errors, including some syntax errors and some that make the code enter an infinite loop. This is the version I worked with, in good faith (try and make sure that the the only bugs in your code are the one's you're asking about!)
class WalkingPeeps: pass # None of the methods were relevant
objectList =[]
location_reached = False
time =0
while not location_reached:
objectList.append(WalkingPeeps())
x = objectList[-1]
x.tleft = time
# you need to check tleft, not location; location is never set
if x.tleft == 20:
location_reached = True
time+=1
print("Person left at: ",x.tleft)
print("Person arrived at: ", time)
for person in objectList: print(person.tleft)
A far more readable and concise version of this code would be:
class WalkingPerson:
def __init__(self,time=0):
self.time=time
objectList = [WalkingPerson(t) for t in range(20)]