Okay, so I'm just trying to get some clarification on why my code is not working like I thought it would.
I am building a GUI, and I want to display text on a Label with a text variable. I have already made a function that updates the Label when the function is called, but of course that is not my problem.
My problem stems from me trying to implement a "print one letter at a time" type of label. While it prints to the terminal in the way I want it to, the label widget only updates after the whole function has finished (visually its the same as just printing the whole string instead of printing a letter at a time).
So what am I missing, what do I not understand? Can you guys help me? Let me post some code so you guys can see where my error is.
I tried both of these independently and they both game me the same result, which was not what I desired.
def feeder(phrase):
"""Takes a string and displays the content like video game dialog."""
message = ""
for letter in phrase:
time.sleep(.15)
message += letter
information.set(message)
#print message
def feeder2(phrase):
"""Same as feeder, but trying out recursion"""
current.index += 1
if current.index <= len(phrase):
information.set(phrase[:current.index])
time.sleep(.1)
feeder2(current.status())
I'm not positive if I need to post more code, so you guys can understand better, but if thats the case, I will do that.
Those 2 functions are used in this function
def get_info():
"""This function sets the textvariable information."""
#information.set(current)
feeder2(current.status())
Which in turn is used in this function
def validate():
""" This function checks our guess and keeps track of our statistics for us. This is the function run when we press the enter button. """
current.turn += 1
if entry.get() == current.name:
if entry.get() == "clearing":
print "Not quite, but lets try again."
current.guesses -= 1
if entry.get() != "clearing":
print "Great Guess!"
current.points += 1
else:
print "Not quite, but lets try again."
current.guesses -= 1
print current
get_info()
entry.delete(0, END)
current.name = "clearing"
The UI will update every time the event loop is entered. This is because painting is done via events (also known as "idle tasks" because they are done when the UI is otherwise idle).
Your problem is this: when you write a loop and do time.sleep, the event loop will not be entered while that loop is running, so no redrawing will occur.
You can solve your problem in at least a couple different ways. For one, you can just call update_idletasks which will refresh the screen. That will solve the repainting, but because you are sleeping the UI will be unresponsive during your loop (since button and key presses aren't "idle tasks").
Another solution is to write a function that takes a string, pulls one character off the string and adds it to the widget. Then it arranges for itself to be called again via the event loop. For example:
import Tkinter as tk
class App(tk.Tk):
def __init__(self,*args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.label = tk.Label(self, text="", width=20, anchor="w")
self.label.pack(side="top",fill="both",expand=True)
self.print_label_slowly("Hello, world!")
def print_label_slowly(self, message):
'''Print a label one character at a time using the event loop'''
t = self.label.cget("text")
t += message[0]
self.label.config(text=t)
if len(message) > 1:
self.after(500, self.print_label_slowly, message[1:])
app = App()
app.mainloop()
This type of solution guarantees that your UI stays responsive while still running your code in a loop. Only, instead of using an explicit loop you add work to the already running event loop.
Related
I'm trying to create a button in Tkinter that only appears (becoming visible) when it finds the word "hello" in a textbox.
I could imagine using Threads, and Global variables but i do not know how to code it,
I've imagined something like :
import Tkinter as *
from threading import Thread
window = Tk()
active = True
def check_for_word():
global active
textbox1 = textbox.get("1,0", "end")
while active == True:
if "hello" in textbox1:
button.pack()
else:
button.pack_forget()
save_button = Button(window)
textbox = scrolledtext.ScrolledText(window)
textbox.pack()
threading = Thread (target=check_for_word)
threading.start()
window.mainloop()
this is something I would suspect to work but ends up not, the button either doesn't show at all like the code isn't even running, or the thread doesn't work properly. So am I doing something wrong, if so, can you help me, please? Thank you!
You don't need to make use of threads to do this, you can use tkinter event bindings instead.
def check_for_word():
if "hello" in textbox.get("1.0", "end"):
save_button.pack()
else:
save_button.pack_forget()
save_button = Button(window)
textbox = scrolledtext.ScrolledText(window)
textbox.bind("<KeyRelease>", lambda event:check_for_word())
textbox.pack()
To make a binding, you use widget.bind. In this case, the widget is textbox and it's binding to <KeyRelease>, which is when the user releases a key. It then calls check_for_word when a key is released. (The lambda part is to ignore the event parameter). check_for_word then does what it did before.
You have to put the textbox1 assignment inside the while loop and before the if condition, otherwise it will check the value one time before entering the loop and will keep checking always the same value.
I also want to point out that the in operator is case sensitive and return True if it find even just a substring inside the variable you are checking and not just the precise single word (but I'm not shure if this is intensional).
For the while loop you don't necessarily need a global variable, you could just use while True: if you want it to continuously check the condition (if you want the button to disappear after the user cancel the word).
I am trying to get a hand-on experience on appJar with python3 for GUI programming. I am trying to add an action to take place when button is pressed. it works OK but it ends strangely.
Part of the code is below. the function "pressed" is getting called whenever the button is pressed. it works fine. However, when the count reaches 0, the number 0 is not pressed (although, as per the code, the number 0 should be printed on the label named "lb1").
when the button is pressed and the count variable =1, the count variable should get decremented by one, then the label text should be updated with the new count number. then, it checks if the counter =0 and if true, it exits the code.
Now, the form get terminated before updating the label with the new value. with some troubleshooting, I found the label value is only updated upon the termination of the form although the line of code that updates it is executed already.
Anyone can shed some light on this?
from appJar import gui
count=10
def pressed(btnName):
global count
count-=1
win.setLabel("lb1","Count= "+ str(count))
if count==0:
win.stop()
I think what you're seeing is expected behaviour.
When count reaches 0 - the label update is queued to happen, but then the next line of code stops the GUI, this will happen just a few milliseconds later - so the GUI will be gone before there is a chance to update what is being shown on the screen.
If you want to introduce a slight delay, between the label updating and the GUI closing, you could use the .after() function.
For example:
if count == 0:
win.after(500, win.stop)
This will delay 500 milliseconds before calling the stop function.
Alternatively, if you want the GUI to stay open, displaying Count= 0, and only close the next time the user presses the button, change your original code to only call win.stop() when count == -1
So, the full code might look like:
from appJar import gui
count=10
def pressed(btnName):
global count
count-=1
win.setLabel("lb1","Count= "+ str(count))
if count == 0:
win.after(500, win.stop)
win = gui()
win.addLabel("lb1", "empty")
win.addButton("PRESS", pressed)
win.go()
I am building a GUI in Python and using threads to generate multiple countdown timers that work independently. I have a minimized module for testing which i am including bellow. I want the button to start the count down and then when its clicked again stop it, then reset so that it can be started on the next click. Also reset on its own once time runs out. The only problem is that after it stops, I cannot get it to restart. I get the "cannot start a thread twice" error. I have been trying to use a conditional loop to get the thread to exit its self but it hasn't been working. I would really appreciate some insight
There are actually two things that i want this program to be able to do.
1) run all the way through the timer then automatically reset so that it can be restarted
2) be stopped in the middle of a countdown, have it automatically reset so that it can be restarted
I think that solving these issues will be valuable for the community to see because it is an example of a real world solution to the issue that a lot of people talk about on the forum which is how to get around the no restarting threads.
__author__ = 'iKRUSTY'
'''
there are two things i would like this code to be able to do
1) run all the way through the timer and then reset so that it can be restarted
2) be stopped before completing and then reset so that it can be restarted
'''
from tkinter import *
import time
import os
import threading
#Variables
global FRYER_ONE_TIME_VAR # holds the value for changing label text to update timer
global BASKET_ONE_TARGET_TIME #this is a user input that will determine the length of the countdown
global timerOneStayAlive #this is the value that i am attempting to use so that the thread closes after it is set to false
timerOneStayAlive = FALSE #initializes to false because the the invert function changes it to true once the button is clicked
FRYER_ONE_TIME_VAR=" " #used to pass time between functiuons
#Font Profiles
SMALLEST_FONT = ("Verdana", 9)
SMALL_FONT = ("Verdana", 10)
LARGE_FONT = ("Verdana", 12)
LARGEST_FONT = ("Verdana", 18)
class timer():
global BASKET_ONE_TARGET_TIME
BASKET_ONE_TARGET_TIME = 5 #Just setting it manually for now
def __init__(self):
self.s = 0 #these values are used to convert from seconds to a minute:second format
self.m = 0 #these values are used to convert from seconds to a minute:second format
def SetTime(self, seconds):
self.seconds=seconds #this is a counter that will be used to calculate remaining time
def TimerReset(self):
self.seconds = BASKET_ONE_TARGET_TIME #resets counter to target time
def StartCountdown(self, FryerLabel): #takes a label as an argumet to tell it where to display the countdown
global timerOneStayAlive
print("StartCountdown Started!") #USED FOR TROUBLE SHOOTING
self.seconds = BASKET_ONE_TARGET_TIME #set start value for seconds counter
self.seconds=self.seconds+1 #makes the full time appear upon countdown start
while self.seconds > 0:
FRYER_ONE_TIME_VAR = self.CalculateTime() #Calculate time reduces the counter by one and reformats it to a minute:second format. returns a string
FryerLabel.config(text=FRYER_ONE_TIME_VAR) #Update Label with current value
print(self.seconds) #USED FOR TROUBLE SHOOTING
time.sleep(1)
# reset label with default time
if self.seconds == 0: #Reset once counter hits zero
print("resetting time") #USED FOR TROUBLE SHOOTING
self.seconds = BASKET_ONE_TARGET_TIME + 1
FRYER_ONE_TIME_VAR = self.CalculateTime()
FryerLabel.config(text=FRYER_ONE_TIME_VAR)
break
print("TimerStayAlive before invert: ", timerOneStayAlive) #USED FOR TROUBLE SHOOTING
timerOneStayAlive = invert(timerOneStayAlive) #inverts the value back to FALSE so that Ideally the loop breaks
# and the thread completes so that it can be called again
print("TimerStayAlive after invert: ", timerOneStayAlive) #USED FOR TROUBLE SHOOTING
print("end startcountdown") #USED FOR TROUBLE SHOOTING
def CalculateTime(self):
#print("CalculateTime has been called")
lessThanTen=0
self.seconds = self.seconds - 1
self.m, self.s = divmod(self.seconds, 60)
if self.s<10:
lessThanTen=1
#create time String Variables
colonVar=':'
minutesString = str(self.m)
secondsString = str(self.s)
#insert variables into string array
timeArray = []
timeArray.append(minutesString)
timeArray.append(colonVar)
if lessThanTen == 1:
timeArray.append("0")
timeArray.append(secondsString)
#prepare for output
self.timeString = ''.join(timeArray)
return self.timeString
def invert(boolean):
return not boolean
def BasketOneButtonClicked():
print("button clicked") #USED FOR TROUBLE SHOOTING
global timerOneStayAlive
timerOneStayAlive = invert(timerOneStayAlive) #Changes from FALSE to TRUE
if timerOneStayAlive == TRUE:
print("timerOneStayAlive: ", timerOneStayAlive) #USED FOR TROUBLE SHOOTING
basketOneThread.start()
updateButtonStatus() #changes text of button depending on whether timerOneStayAlive is TRUE or FALSE
else:
print("timerOneStayAlive: ", timerOneStayAlive) #USED FOR TROUBLE SHOOTING
return
def updateButtonStatus():
global timerOneStayAlive
if timerOneStayAlive == FALSE:
basketOneStartButton.config(text="Start")
if timerOneStayAlive == TRUE:
basketOneStartButton.config(text="Stop")
def BasketOneThreadComShell(): # I used this so that i can ideally call multiple functions with a single thread
'''
This is where i think the problem may be. this is what is called when the thread is initialized and theoretically
when this completes the thread should come to a halt so that when the button is reset, the thread can be called again
I think that the function is completing but for some reason the thread keeps on running.
'''
global timerOneStayAlive
print("ComShell Started") #USED FOR TROUBLE SHOOTING
while timerOneStayAlive:
basketOneTimer.StartCountdown(countdownLabelBasket1)
updateButtonStatus()
if timerOneStayAlive == FALSE: #redundant because while loop should do it. i just tried it because i couldnt get the process to end
break
print("Threadshell has ended") #USED FOR TROUBLE SHOOTING
return
print("after return check") #USED FOR TROUBLE SHOOTING
root = Tk()
'''
the following is all just building the GUI Stuff
'''
Container = Frame(root)
Container.grid(row=1, column=0, padx=10, pady=10)
countdownContainerBasket1 = Label(Container, width=10, height=5)
countdownContainerBasket1.grid(row=2, column=0, sticky=NSEW)
countdownLabelBasket1 = Label(countdownContainerBasket1, text="Basket 1", background="white", anchor=CENTER, width=10, height=6, font=LARGE_FONT, padx=20)
countdownLabelBasket1.pack()
basketOneTimer = timer()
basketOneTimer.SetTime(5)
basketOneStartButton = Button(Container, text="Start", font=LARGE_FONT, command=BasketOneButtonClicked)
basketOneStartButton.grid(row=3, column=0, padx=10, pady=10)
basketOneThread = threading.Thread(target=BasketOneThreadComShell) #this is where the thread is initialized. start() is called in BasketOneButtonClick
print(threading.active_count) #tried to use this to see if the thread was exiting but it didnt help
root.mainloop()
You'll probly want to give this a run just to see what the GUI looks like and what i mean when i say timer. Try letting it run all the way through and resetting to get an idea of what im talking about.
Let me know if there is any other info that would be helpful, and keep in mind this is my first week with python so im no expert. Thanks all
The python community is very lucky to have a style guide that is very well accepted https://www.python.org/dev/peps/pep-0008/. It is much harder for me to quickly understand the problems you are talking about due to your non pep8 naming and formatting.
Other threads cannot interact with your widgets, only the tkinter mainloop can do that. You can use the after method provided by tkinter to run a function after a given time period in the mainloop.
see this example
Mutli-threading python with Tkinter for an example!
To be clear threading with tkinter may work, but it is unreliable and you will get unexpected behavior that is hard to debug. Don't do it.
Suggestions
the tkinter after method would be called to start a function that indefinitely checks a queue for functions to run. It then schedules these functions to be run by tkinter using the after method
I would have a timer class with start, stop and pause methods.
When started it would count up/down and simply restart itself on completion. Each timer instance you create would also need a reference to a tkinter button.
to update the button you put a function on the queue that may look like
tkinter_queue.put(lambda: but.config(text=i))
now the function that checks the queue will update your button timers
Also as you are in your first week of python programming you can be sure that most problems you encounter will have answers here if not elsewhere. Please do your research before hand. Gui programming and threading are not the easiest topics so even more reason to do your research.
I am looking for a solution to emulate the behavior of the UI of an electronic component and the user interaction (which should be pushing buttons) with LEDs reporting an internal state of the electronic component.
I am using python and the tKinter module to do so.
My code runs and my GUI window displays correctly. However, when I push several times on buttons the behavior is not as expected.
I have 4 possible state for each LED (OFF, ON, (Blinking) SLOW, (Blinking) FAST).
I have 4 buttons which can have an impact on the state. Each button has an interaction function defined in the widget class I have defined, and each of this function, once called, redefines the internal state of the widget.
In order to control the blinking of the LED, I use a single loop and the self.after( ..) function. This function is the following:
def toggleLeds(self):
for led in [self.ledTxIP, self.ledRxIP, self.ledTxRS, self.ledRxRS, self.ledPower, self.ledRun, self.ledStatus, self.ledConfig]:
if (((led[1] == "SLOW") and (self._FastBlinking == 0)) or (led[1] =="FAST")):
bg = led[0].cget("background")
bg = "green" if bg == "black" else "black"
led[0].configure(background=bg)
elif((led[1] == "OFF") and (self._update == 1)):
led[0].configure(background="black")
self._update = 0
elif (self._update == 1):
led[0].configure(background="green")
self._update = 0
self._FastBlinking = (self._FastBlinking + 1)%2
self.update_idletasks()
self.after(self._FastBlinkTime, self.toggleLeds)
This one is called recursively through the self.after function, and at the end of the interaction function I have defined for each button.
Here is how I have defined a single LED:
self.ledTxIP = [tk.Label(self, width=1, borderwidth=2, relief="groove"),"OFF"]
And here is an example of the button interaction function:
def pushMode(self):
if (re.search("Reset",self.state) == None):
if (self.clickModCnt == 0):
self.state = "Status"
self._stateTimer = int(time.gmtime()[5])
elif (self.clickModCnt == 1):
if(int(time.gmtime()[5]) - self._stateTimer < 3):
self.state = "Config"
else:
self.state = "RunMode"
else:
self.state = "RunMode"
self.clickModCnt = (self.clickModCnt + 1)%3
self._update = 1
self.updateLedState()
If anybody has an advice on this, it would be more than welcome.
I don't know why this didn't jump out at me sooner, but I think the problem is listed in your own question text, referring to the toggleLeds method:
This one is called recursively through the self.after function, and at the end of the interaction function I have defined for each button.
When the program initially runs, I'm assuming that you call toggleLeds somewhere to kick off the initial pattern for the LEDs. That sets up a single recursive loop via the self.after call at the end of the method. However, if you also call that same method every time you click a button to change state, you're setting up a new loop with every button click, and each new loop may or may not be in sync with your initial loop.
There are a couple ways that I can think of to handle this possible conflict. One is to avoid making new calls to toggleLeds, but that way there could be a delay between the button click and the new LED pattern. If you don't mind that delay, that's probably the best solution.
If you want the light/blink pattern to change immediately, you need to interrupt the current loop and start a new one with the new light/blink states. According to the Tkinter reference produced by New Mexico Tech, the after method:
...returns an integer “after identifier” that can be passed to the .after_cancel() method if you want to cancel the callback.
Here's how you could take advantage of that. First make sure that you're storing that identifier when calling the after method:
self.after_id = self.after(self._FastBlinkTime, self.toggleLeds)
Then change your toggleLeds method definition to accept an optional "interrupt" argument, and to cancel the existing after loop if that argument is True:
def toggleLeds(self, interrupt=False):
if interrupt:
self.after_cancel(self.after_id)
# Existing code follows
Finally, pass True to that argument when calling the method after a button has been clicked:
# Existing button processing code here
self.toggleLeds(interrupt=True)
With these changes in place, each button click would cancel the current after cycle and start a new one, preventing more than one cycle from running at once, which should keep the LEDs in sync.
This is my code:
from Tkinter import*
from random import radint
Characters=[Percy,Annabeth,Leo,Chuck,Sarah]
Used = []
def callback():
selected = None
end = len(Characters)-1
rando = randint(0,end)
selected = Characters[rando]
for i in Characters:
if i in Used:
print 'This has already been used'
else:
Characters[rando]()
Used.append(Characters[rando])
game = Tk()
game.geometry('50x50+700+100')
Button1 = Button(game,text = '1',command =lambda:callback() )
Button1.pack(side=LEFT)
game.mainloop()
What is supposed to happen is, the program runs, 1 button is in the window, you click the button,it randomly selects one of the values from the list 'Characters' (all of which are procedures), executes the procedure and then adds selected value to the list 'Used', then when you click the button again, it checks if that value has already been used, and if it has, it prints 'This has already been used' if it has not, it executes.
But what is happening, is that it will run the same procedure multiple times, and then when I close the root window (game) it re-executes all of the procedures that were executed when I hit my button.I have been working at this for an hour and cannot figure it out.
for i in Characters: goes through all of the items in the list Characters each time it is run.
Suppose you start up your app, and click the button. This is what happens:
for i in Characters:
i = Percy
if i in Used:
... #i is not in Used
else:
... #execute Percy() and add it to Used
i = Annabeth
if i in Used:
... #i is not in Used
else:
... #execute Annabeth() and add it to Used
...you get the picture. So the 1st time it is run, all of the characters are executed, and added to Used.
You should probably change that code to something like this:
def callback():
# code ...
rando = randint(0, end)
if Characters[rando] in Used:
print "This has already been used"
else:
Characters[rando]()
Used.append(Characters[rando])
Your problem is that the very first time you click the button it will add all of the characters to the list. It may appear it happens when your program ends, but it's actually happening all at once.
Assuming you want to process a single instance of Character on each click, you need to add a break statement to terminate your loop after appending something to the list.
for i in Characters:
if i in Used:
print 'This has already been used'
else:
Characters[rando]()
Used.append(Characters[rando])
break