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.
Related
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. :-)
So I want to create a simple UI in Jupyter notebook where:
A counter "number" is incremented every second
If the checkbox "pause" is checked, "number" is not incremented
If the button "trigger" is pressed, "number" is decremented, regardless of pause status
So far I tried a few variants of the code below but it doesn't work; it seems like the widget values aren't updating when running the while loop. Is there a way to fix it or another way to do this? Thanks!
import ipywidgets as widgets
import time
from IPython.display import display, clear_output
btn = widgets.Button(description = "Trigger")
pause = widgets.Checkbox(value = False, description = "Paused?")
number = widgets.Label("0")
wid = widgets.VBox([btn, number, pause])
display(wid)
def triggered(b):
number.value = str(int(number.value) - 1)
btn.on_click(triggered)
while True:
time.sleep(1)
while (pause.value == True):
time.sleep(3)
number.value = str(int(number.value) + 1)
As mentioned by ac24, the trick is to run your counter function in a different thread. You can use the threading library to do that. Below, I defined a function counter and launched it in a different thread. That way, the user is still able to interact with the widgets while the counter function is running.
An important thing to keep in mind is that once you launched your thread there isn't many elegant ways to kill it. This means that it's better to set up a total_duration variable rather than using while True.
See code below:
import ipywidgets as widgets
import time
from IPython.display import display, clear_output
import threading
btn = widgets.Button(description = "Trigger")
pause = widgets.Checkbox(value = False, description = "Paused?")
number = widgets.Label("0")
wid = widgets.VBox([btn,number,pause])
display(wid)
def triggered(b):
number.value = str(int(number.value) - 1)
btn.on_click(triggered)
def counter(number,pause,total_duration):
for t in range(total_duration):
if not pause.value:
time.sleep(1)
number.value = str(int(number.value) + 1)
elif pause.value:
time.sleep(3)
total_duration=60
thread = threading.Thread(target=counter, args=(number,pause,total_duration,))
thread.start()
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
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.
I am new to Python and am having trouble with this piece of code:
while true:
rand = random.choice(number)
print(rand)
enter_word = input("Write something: ")
time.sleep(5)
I want to be able to input words in the console while, at the same time, have random numbers appear in the console. But a new number only appears once I input a word. What is the best way to make both these commands run at the same time?
Do I need to make a thread or is there something simpler I can do?
And if I need to make a thread can you please give a little help on how I would create it?
Thanks in advance
This can be achieved by using the multiprocessing module in python, please find the code below
#!/usr/bin/python
from multiprocessing import Process,Queue
import random
import time
def printrand():
#Checks whether Queue is empty and runs
while q.empty():
rand = random.choice(range(1,100))
time.sleep(1)
print rand
if __name__ == "__main__":
#Queue is a data structure used to communicate between process
q = Queue()
#creating the process
p = Process(target=printrand)
#starting the process
p.start()
while True:
ip = raw_input("Write something: ")
#if user enters stop the while loop breaks
if ip=="stop":
#Populating the queue so that printramd can read and quit the loop
q.put(ip)
break
#Block the calling thread until the process whose join()
#method is called terminates or until the optional timeout occurs.
p.join()
To wait for input and to display some random output at the same time, you could use a GUI (something with an event loop):
#!/usr/bin/env python3
import random
from tkinter import E, END, N, S, scrolledtext, Tk, ttk, W
class App:
password = "123456" # the most common password
def __init__(self, master):
self.master = master
self.master.title('To stop, type: ' + self.password)
# content frame (padding, etc)
frame = ttk.Frame(master, padding="3 3 3 3")
frame.grid(column=0, row=0, sticky=(N, W, E, S))
# an area where random messages to appear
self.textarea = scrolledtext.ScrolledText(frame)
# an area where the password to be typed
textfield = ttk.Entry(frame)
# put one on top of the other
self.textarea.grid(row=0)
textfield.grid(row=1, sticky=(E, W))
textfield.bind('<KeyRelease>', self.check_password)
textfield.focus() # put cursor into the entry
self.update_textarea()
def update_textarea(self):
# insert random Unicode codepoint in U+0000-U+FFFF range
character = chr(random.choice(range(0xffff)))
self.textarea.configure(state='normal') # enable insert
self.textarea.insert(END, character)
self.textarea.configure(state='disabled') # disable editing
self.master.after(10, self.update_textarea) # in 10 milliseconds
def check_password(self, event):
if self.password in event.widget.get():
self.master.destroy() # exit GUI
App(Tk()).master.mainloop()
I want to be able to input words in the console while, at the same time, have random numbers appear in the console.
#!/usr/bin/env python
import random
def print_random(n=10):
print(random.randrange(n)) # print random number in the range(0, n)
stop = call_repeatedly(1, print_random) # print random number every second
while True:
word = raw_input("Write something: ") # ask for input until "quit"
if word == "quit":
stop() # stop printing random numbers
break # quit
where call_repeatedly() is define here.
call_repeatedly() uses a separate thread to call print_random() function repeatedly.
you have to run two concurrent threads at the same time in order to get rid of such blocking. looks like there are two interpreters that run your code and each of them executes particular section of your project.