I'm making a BSOD simulator with a randomly progressing percentage.
What i'm trying to do is :
Make a variable called percentage and initialise it with a value of 0
Make a label with the text f"{percentage}% completed"
Make a variable called percentageIsAHundred and initialise it with a value of False
Works good. Output : 0% completed
Now to add the random steps :
Make a loop as long as percentageIsAHundred is false
Change the value of percentageText to f"{percentage}% completed" (to update it)
We do a win.after() (I've tried sleep and threading- don't work) and put the values 1000, and a function called steps
Check if the percentage value is above 100, If it returns true we set percentageText to 100% completed (otherwise it might be 106% or 104%) and set percentageIsAHundred to true, breaking the loop.
In the steps function we:
Increment the percentage variable by a random value in between 0 and 20
Should work? Nope.
No windows open and no errors appear in console. Commenting out the code works and returns to normal.
I have seen that this behaviour occurs at the steps() function, Although I may be wrong.
# Importing modules
from tkinter import *
import os
import random
import threading
# Initialising Tkinter window
win = Tk()
# Creating reboot function
def reboot(sec=50):
os.system(f'echo "{sec} timer not confirmed"')
# Giving the window its iconic blue background.
win.config(bg="#0178d6")
# Making the window fullscreen
win.attributes("-fullscreen", True)
# Adding the emoticon ":("
emoticon = Label(win, text=":(", font="{Segoe UI} 100", bg="#0178d6", fg="#ffffff")
emoticon.place(x="50px", y="100px")
# Adding the text
text1 = Label(win, text="A problem has occurred and your PC needs to restart. We're just collecting some error information, then we'll restart for you.", font="{Segoe UI} 20", bg="#0178d6", fg="#ffffff")
text1.place(x="50px", y="259px")
# Adding the progress percentage
percentage = 0
percentageText = Label(win, text=f"{percentage}% complete", font="{Segoe UI} 20", bg="#0178d6", fg="#ffffff")
percentageText.place(x="50px", y="320px")
percentageIsAHundred = False
________________________________________________________
# This is the problematic code- Removing it seems to run the window normally but including it causes tkinter to not run with 0 errors
def steps(percentage=percentage) :
percentage = percentage + random.randint(1, 20)
win.after(1000, steps)
while percentageIsAHundred == False :
percentageText.config(text=f"{percentage}% complete")
if percentage >= 100 :
percentageText.config(text="100% complete")
percentageIsAHundred = True
________________________________________________________
# Setting the mainloop
win.mainloop()
! My title may not be right because I can't think clear right now. !
The percentage in steps() is local to that function.
If you want it to increase the value of the global percentage, you should mark it at such:
def steps():
global percentage
percentage += random.randint(1, 20)
win.after(1000, steps)
percentageText.config(text=f"{percentage}% complete")
if percentage >= 100:
percentageText.config(text="100% complete")
win.quit()
Also note that as a rule you should be careful with while and for loops in event handlers and after functions. If they take too long, they will inhibit event processing.
Also, your while loop is not indented, so it in not part of steps but part of the main module. In this case, since your your while loop runs before the mainloop, it prevents the tkinter mainloop from starting and your window from appearing. :-)
Related
If you use Root.bind to get a key input and add a function it will execute, it will execute it, make a small pause, and then keep rapidly executing it but how to do it without it making a pause, might work if there is a way to detect if it was clicked (not released) and then start executing it and when the button is lifted it will stop executing it? (not using another import, it dont work idk why)
from tkinter import *
A = 0
def fun(event):
global A
if event.keysym == 'space':
A += 1
print(A)
root = Tk()
root.bind("<Key>", fun)
root.mainloop()
I think I understand the question. You want to press and hold the space bar and have something executing while the bar is held down and stop once you release the key.
The <Key> binding detects key down and then key down for every repeat.
The <KeyRelease> binding detects a key release for each key repeat and when the key is actually released.
In the code below fun_on executes after each Key event and fun_off after each KeyRelease event. The on_after function in fun_off is cancelled if fun_on runs before the time expires.
import tkinter as tk
A = 0
cycle = False
def fun_off( event ):
global timer
def on_after():
""" This executes 1 ms after it's triggered.
If a <Key> event is detected during the millisecond it's cancelled.
See fun_on """
global A, cycle
A = 0
cycle = False
timer = root.after( 1, on_after ) # More milliseconds may be
# needed for some hardware
def do_cycle(): # This executes as long as cycle is True
global A
if cycle:
A += 1
print( A )
root.after( 10, do_cycle ) # Set ms delay as required.
def fun_on(event):
""" Cancels the after ID from fun_off.
My auto repeat executes key down 0.1 ms after key up.
This will cancel the after function unless the key is actually released"""
global cycle
root.after_cancel( timer )
if not cycle:
cycle = True
do_cycle()
root = tk.Tk()
timer = root.after( 0, lambda: None ) # Makes timer a validafter id.
root.bind( "<KeyRelease-space>", fun_off )
root.bind( "<Key-space>", fun_on )
root.mainloop()
Using tkinter, I'm doing an emergent window to remind me to drink water. At this point it works but now I want the section of def trinke_wasser to repeat every hour. I tried with the time module but in the end, I didnĀ“t know where to write it.
Here is my code:
from tkinter import *
from PIL import Image
from PIL import ImageTk
fenstern = Tk()
fenstern.title("Warnung")
fenstern.geometry("900x600")
datei = Image.open('wasser_pic.png')
bild = ImageTk.PhotoImage(datei)
img = Label(fenstern, image = bild)
img.place(x=0, y=0)
def choice(option):
pop.destroy()
def trinke_wasser():
global pop
fenstern.wm_state('iconic')
pop = Toplevel(fenstern)
pop.title('popup')
pop.geometry("900x600")
back= Label(pop, image = bild)
back.place(x=0, y=0)
rhamen = Frame(pop, bg = "white")
rhamen.pack(pady = 5)
yes = Button(rhamen, text = "YES", command = lambda: choice ("yes"), bg = "orange")
yes.grid(row=0, column=1)
die_Taste = Button(fenstern, text = "Beginnen", command = trinke_wasser )
die_Taste.pack(pady=100)
fenstern.mainloop()
With the time library, there is a sleep function which waits for a number of specified seconds. So if we calculate the number of seconds in an hour or 3600. We can call the sleep function which will wait for exactly 1 hour.
import time
time.sleep(3600)
So if you put the sleep function at the end of your trinke_wasser function. It will tell you to drink water, wait an hour and do it again, over and over
In your current program, you just execute the trinke_wasser() function once upon pressing a key. What you want to do is to open a scheduler function that calls the function once every hour.
But you will have to allow this function to be terminated for good. So, you need a global boolean variable that may be turned off to end the loop. (you could also just end the program execution by force, but that's not nice)
So, what you want to do is to import the time library by adding import time at the beginning of the script.
After fenstern is defined, just add an attribute to this window that is True as long as you don't make it false: fenstern.running = True
Then, you can make your scheduler function:
def nerv_mich(delay = 3600):
while fenstern.running:
trinke_wasser()
time.sleep(3600)
This function will run forever and ask you to drink water ever 3600 seconds.
You just have to adjust the command of the first button:
die_Taste = Button(fenstern, text = "Beginnen", command = nerv_mich)
Now, you just have to turn it off by using a second button just after the definition of yes
fertig = Button(rhamen, text = "Fertig jetzt!", command = lambda: choice ("fertig"), bg = "orange")
yes.grid(row=2, column=1)
Finally, you have to change the function choice() to really end the whole thing after the option passed to it is "fertig":
def choice(option):
pop.destroy()
if option=="fertig": fenstern.running=False
That should work. You can also use fentstern.destroy() to completely end your program at this point. To be honest, you probably don't really need this initial window.
I would like to filter dataset with a delay.
I have this basic layout:
import tkinter as tk
def filter(*args):
print('entry callback', 'changed %s' % str(args))
print('limit result set with', filter_input.get())
if __name__ == "__main__":
master = tk.Tk()
filter_input = tk.StringVar(value='') # filter mode
# filter_input.trace('w', filter) # simple trace triggers immediately
# filter_input.trace_variable('w', filter) # trace_variable as well
filter_input.trace_add('write', filter) # trace_add should replace previous (deprecated)
entry_field = tk.Entry(master, textvariable=filter_input)
entry_field.pack()
tk.mainloop()
I started with main Tkinter python page
I went through the main widget documentation
There are a bunch of more documentation around the globe, but I can't find anywhere any mention about a delay implemented. Can anyone help me to find out, if there is possible to call the filter function after some defined amount of time? Maybe another wrapper function could help?
EDIT:
It looks like I need to clear out the desired functionality.
This is somehow what I am after but I need a delay instead of "user-stops-typing" activity.
If you just want to call the function with a delay then use after(ms,func) to call the function after ms milliseconds. So your function could be like:
def filter(*args):
# print(f'entry callback changed {str(args)}')
print(f'limit result set with {filter_input.get()}')
master.after(1000,filter) # Call this function every 1000 millisecond or 1 second
if __name__ == "__main__":
master = tk.Tk()
....
filter() # Call the function initially
tk.mainloop()
So you can also get rid of StringVar here as you are using after() and nothing else. If you want to end this after() loop, then use after_cancel(id).
EDIT:
If you want to see the input with just a delay once each time the window is open, then try this:
first = True
def show():
global first
if first and entry_field.get():
first = False
master.after(3000,lambda: print(entry_field.get()))
if __name__ == "__main__":
master = tk.Tk()
......
entry_field.bind('<Key>',lambda e: show())
EDIT 2: This will call the function also when the box is cleared and typed again:
first = True
def show():
global first
if entry_field.get():
if first:
first = False
master.after(3000,lambda: print(entry_field.get()))
else:
first = True
Well i am a bit of newb at python, and i am getting hard to make a thread in Tkinter , as you all know using while in Tkinter makes it Not Responding and the script still running.
def scheduler():
def wait():
schedule.run_pending()
time.sleep(1)
return
Hours = ScheduleTest()
if len(Hours) == 0:
print("You need to write Hours, Example: 13:30,20:07")
if len(Hours) > 0:
print("Scheduled: ", str(Hours))
if len(Hours) == 1:
schedule.every().day.at(Hours[0]).do(Jumper)
print("Will jump 1 time")
elif len(Hours) == 2:
schedule.every().day.at(Hours[0]).do(Jumper)
schedule.every().day.at(Hours[1]).do(Jumper)
print("Will jump 2 times")
elif len(Hours) == 3:
schedule.every().day.at(Hours[0]).do(Jumper)
schedule.every().day.at(Hours[1]).do(Jumper)
schedule.every().day.at(Hours[2]).do(Jumper)
print("Will jump 3 times")
while True:
t = threading.Thread(target=wait)
t.start()
return
scheduler()
i have tried to do something like this but it still makes tkinter not responding
Thanks in advance.
When to use the after method; faking while without threading
As mentioned in a comment, In far most cases, you do not need threading to run a "fake" while loop. You can use the after() method to schedule your actions, using tkinter's mainloop as a "coat rack" to schedule things, pretty much exactly like you would in a while loop.
This works in all situations where you can simply throw out commands with e.g. subprocess.Popen(), update widgets, show messages etc.
It does not work when the scheduled process takes a lot of time, running inside the mainloop. Therefore time.sleep() is a bummer; it will simply hold the mainloop.
How it works
Within that limitation however, you can run complicated tasks, schedule actions even set break (-equivalent) conditions.
Simply create a function, initiate it with window.after(0, <function>). Inside the function, (re-) schedule the function with window.after(<time_in_milliseconds>, <function>).
To apply a break- like condition, simply rout the process (inside the function) not to be scheduled again.
An example
This is best illustrated with a simplified example:
from tkinter import *
import time
class TestWhile:
def __init__(self):
self.window = Tk()
shape = Canvas(width=200, height=0).grid(column=0, row=0)
self.showtext = Label(text="Wait and see...")
self.showtext.grid(column=0, row=1)
fakebutton = Button(
text="Useless button"
)
fakebutton.grid(column=0, row=2)
# initiate fake while
self.window.after(0, self.fakewhile)
self.cycles = 0
self.window.minsize(width=200, height=50)
self.window.title("Test 123(4)")
self.window.mainloop()
def fakewhile(self):
# You can schedule anything in here
if self.cycles == 5:
self.showtext.configure(text="Five seconds passed")
elif self.cycles == 10:
self.showtext.configure(text="Ten seconds passed...")
elif self.cycles == 15:
self.showtext.configure(text="I quit...")
"""
If the fake while loop should only run a limited number of times,
add a counter
"""
self.cycles = self.cycles+1
"""
Since we do not use while, break will not work, but simply
"routing" the loop to not being scheduled is equivalent to "break":
"""
if self.cycles <= 15:
self.window.after(1000, self.fakewhile)
else:
# start over again
self.cycles = 0
self.window.after(1000, self.fakewhile)
# or: fakebreak, in that case, uncomment below and comment out the
# two lines above
# pass
TestWhile()
In the example above, we run a scheduled process for fifteen seconds. While the loop runs, several simple tasks are performed, in time, by the function fakewhile().
After these fivteen seconds, we can start over again or "break". Just uncomment the indicated section to see...
I've hit a dead end at the moment - firstly when you click one of the dynamic buttons, the function call is made, and it does return the name of the button clicked. However the fEndDay function it's supposed to call as well doesn't appear to run.
EDIT: The day is now running. Just relaunched Liclipse and it started working. No explanation. However, the button wait issue remains.
I'm also a little stuck at the moment. In essence I want:
While current day < total days....
Run a daily event, updating the screen objects.
Take a choice via button click.
Increase the current day.
However, the day loop stops the screen from displaying (i.e. processing loop). I guess if there's code that pushes the object display up and sits in an infinate loop, which is broken by the button click, that would do. Other ideas? Current code below.
#!/usr/bin/python
# Reminder to self - lots to add. Include a function to reset the text content
# and populate with day number, score summary etc as a template. We can then
# add to it.
#################### IMPORT MODULES WE NEED ############################
import time # For sleep delays
from random import randint # for random numbers
from _ast import While # while loops
import tkinter as Tkinter # this one handles windows and buttons etc
from tkinter import * # skip the tkinter prefix (constants etc)
# import tkmessagebox # Python 2 alternative!
from tkinter import messagebox as tkMessageBox # Python 3
import sys # for quit when added ie sys.exit()
from functools import partial # So we can create lists of buttons & commands
import time # threading support - check events while waiting
import concurrent.futures # threading - think i'll be needing all this
################## CREATE A NEW CLASS (CONTAINER) ############################
class CrazyCoder (Tkinter.Tk):
# When the class is created, the function fInitialise is run, below.
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.fInitialize() # State here any functions to run on creation
################################ FUNCTION ################################
# Set up variables etc...
def fInitialize(self):
########################### VARIABLES ###############################
# Overkill here probably but will revisit later! Listed / initialised
# here to simplify searching later on!
# Could pass most of theses but will keep it simple and update globally
# to start with
self.vDayNumber = 1 # What the current day is
self.vFinalDay = 10 # The last day of our game
self.vPlayerName = '' # To hold the players name
self.vGameName = '' # To hold the game name
self.vChoice = '' # To hold the user choices clicked
self.vRandom = 0 # To hold random numbers!
self.vGameplay = 0 # SCORES: Current gameplay score
self.vGraphics = 0 # SCORES: Current graphics score
self.vSound = 0 # SCORES: current sound score
self.vBugs = 0 # SCORES: current bug score'
self.vBackColor = 'grey' # The background colour of our app
self.vDynamic_Buttons = [] # To hold button objects each "screen"
self.vEntryBox = [] # To hold text entry box objects
self.vTextEntry = '' # Stores user text entry value temporarily
self.vAvailableButtons = [] # To hold a list of AVAILABLE buttons/event
########################## APP SETUP ################################
self.title('Crazy Coder') # set window title
self.geometry("500x500") # set screen size
self.configure(background=self.vBackColor) # set background colour
# Add a "Title box"
self.vTitle = Tkinter.Label(self,text='Crazy Coder')
self.vTitle.configure(background=self.vBackColor)
self.vTitle.pack(side=TOP,padx=10,pady=10)
# Add a picture box (gif supported)
self.vImage = PhotoImage(file="PUG.gif")
self.vPicture=Label(self,image=self.vImage)
self.vPicture.image=self.vImage
self.vPicture.pack()
# Add the main text box
self.vMessageText = '''
This is where your day number goes
Scores go here too
Event details go here too'''
self.vMessage = Tkinter.Label(self,text=self.vMessageText)
self.vMessage.configure(background=self.vBackColor)
self.vMessage.pack(side=TOP,padx=10,pady=10)
# While loop does not work - there is no concept of
# "display current buttons / screen and wait for click event"
#while self.vDayNumber <= self.vFinalDay:
self.vChoice = '' # Clear it ready to take a user choice each day
print('DEBUG: On Main screen, starting now')
self.vRandom = randint(1,100)
if self.vDayNumber == 1:
self.fWelcomeScreen() # Set up the welcome screen
elif self.vRandom >= 0:
self.fEvent1() # Kick off event 1
############################# FUNCTION #################################
# Sets the message on the main text box to whatever you put in brackets
def fSetText(self,TextMessage):
global vMessageText
self.vMessageText = TextMessage
self.vMessage['text'] = self.vMessageText # This updates the text box
############################# FUNCTION #################################
# Sets the image on the main picture box to whatever you put in brackets
def fSetImage(self,ImageName):
global vImage
self.vImage = PhotoImage(file=ImageName) # Example "PUG2.gif"
self.vPicture['image']=self.vImage # This updates the image box
############################# FUNCTION #################################
# Add a new Entry box to our screen. Supports multiple uses
def fAddEntryBox(self):
self.vNewBox = Entry(width=20)
self.vEntryBox.append(self.vNewBox)
self.vNewBox.pack(side=TOP,padx=10,pady=10)
self.vNewBox.focus()
############################# FUNCTION #################################
# Remove the Entry Boxes
def fDeleteEntryBoxes(self):
for each_box in self.vEntryBox:
each_box.destroy()
############################# FUNCTION #################################
# Read from the requested box number, cutting off the Enter at the end
def fReadEntryBox(self,BoxNumber): #BoxNumber 0 is the first box, 1 next
global vTextEntry
vTextEntry=self.vEntryBox[BoxNumber].get()
############################# FUNCTION #################################
# Handles the the day passing by
def fEndDay(self):
global vPlayerName, vDayNumber
self.vDayNumber = self.vDayNumber + 1
print("This print isn't running either!")
############################# FUNCTION #################################
# A simple step to take a choice from the user - used for button code below
def fMakeChoice(self,value):
global vChoice, vDayNumber
self.fEndDay()
self.vChoice = value
print('Just Clicked:',self.vChoice, "and it's day ", self.vDayNumber)
print('But fEndDay should have just run and increased it to 2!')
############################# FUNCTION #################################
# Add buttons to the screen, based on vAvailableButtons
def fAddButtons(self):
global vAvailableButtons # Shouldn't need this here but...
for each_button in self.vAvailableButtons:
# Important: the Lambda section takes the CURRENT value of the variable
# and stores is in the command string. Without it, all the buttons
# when clicked would do the same as the last button created! daft eh.
vNewButton = Tkinter.Button(self, text=each_button, command=lambda v=each_button: self.fMakeChoice(v))
self.vDynamic_Buttons.append(vNewButton)
vNewButton.pack(side= BOTTOM, padx = 10, pady = 10)
############################# FUNCTION #################################
# Clear the buttons out ie before drawing new ones
def fDeleteButtons(self):
for each_button in self.vDynamic_Buttons:
each_button.destroy()
############################# FUNCTION #################################
# Pop up message box
def fMessage(self,Message):
self.tkMessageBox.showinfo("News", Message)
#*********************************************************************#
#********************* EVENTS SECTION ***************************#
#*********************************************************************#
# We'll define a function here for each "daily event"
# The section will be responsible for:
# 1) Updating the screen image (if necessary)
# 2) Updating the text box content
# 3) Adding entry boxes and buttons as required
# 4) Ending the day (there will be one decision per day for now)
############################# EVENT #################################
# The welcome screen
def fWelcomeScreen(self):
global vAvailableButtons
self.vTextMessage = '''
Yawn. You wake up.
Whats your name?
'''
self.fSetText(self.vTextMessage)
time.sleep(1) # delays for 1 second
self.fAddEntryBox() # Add an entry box for a name
self.vAvailableButtons = ['Ok']
self.fAddButtons() # vChoice set on click, and day ended
############################# EVENT #################################
# A random event
def fEvent1(self):
global vAvailableButtons
self.vTextMessage = '''
This is an event. What will you do?
'''
self.fSetText(self.vTextMessage)
time.sleep(1) # delays for 1 second
self.vAvailableButtons = ['Ok']
self.fAddButtons() # vChoice set on click, and day ended
# Start the program
if __name__ == "__main__":
app = CrazyCoder(None)
app.mainloop()
The fact that you want to run some function once a day is no different than wanting an animation to run 30 frames per second. You effectively want an "animation" that runs 1 fpd (one frame per day).
The way to accomplish that is to create a function that should run once a day.
def do_once_a_day():
...
Next, create a function that will call that function once a day:
def call_daily(self):
self.do_once_a_day()
if self.vDayNumber <= self.vFinalDay:
root.after(1000*60*60*24, self.call_daily)
This will cause do_once_a_day to be called once every 24 hours. You can then do whatever you want inside that function.