In my new program, the user gives a non-negative integer number to the program in order to calculate the factorial of this number. But sometimes when the number is large, like 25637, it takes some time to calculate. I want if this time is over than 2 seconds to print a processing message to the Python's shell. Unfortunately I am a new guy to tkinter and I don't know how to do that.
Please can you show me the solution of that I ask in my code in order to understand it?
This is my code:
#! usr/bin/python
# Filename: x_Factorial!_GUI.py
import sys, warnings
if sys.version_info[0] < 3:
warnings.warn("System FAILURE, Python 3.x is required in order to execute this program",
RuntimeWarning)
else:
version_info_num = str(sys.version_info[0]) + "." + str(sys.version_info[1]) + "." + str(sys.version_info[2])
print("System SUCCESS, You are currently executing this program on Python's version: {}".format(version_info_num))
import tkinter as tk
from tkinter import ttk
class GUI(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
#Window_Creation
scr_xloc = int(self.winfo_screenwidth() / 2 - 800 / 2)
scr_yloc = int(self.winfo_screenheight() / 2 - 600 / 2 - 30)
self.geometry("800x600+{}+{}".format(scr_xloc, scr_yloc))
self.minsize(width = 800, height = 600)
self.title("x_Factorial!")
self.configure(bg = "#F06428")
#Window_Controls
self.bind("<Configure>", self.Resize_Window)
self.bind("<F11>", self.Toggle_Fullscreen)
self.state_mode = False
self.bind("<Escape>", self.Quit)
# Widgets_Creation
self.User_Line_Text = tk.StringVar()
self.User_Line_Text.set("Please enter a non-negative integer number:")
self.User_Line = tk.Entry(bg = "#FFFFFF", font = ("Comic Sans MS", 10, "bold"), fg = "#000000",
justify = "left", relief = "flat", textvariable = self.User_Line_Text)
self.User_Line.place(width = 400, height = 40, x = 200, y = 250)
self.User_Line.bind("<Button-1>", self.Clear_Text)
self.User_Line.bind("<Key-Return>", self.Processing)
self.Output_Box = tk.Text(bg = "#FFFFFF", font = ("Comic Sans MS", 10, "bold"), fg = "#000000",
relief = "flat")
self.Output_Box.place(width = 600, height = 240, x = 100, y = 320)
def Clear_Text(self, event):
self.User_Line.delete("insert", "end")
def Processing(self, event):
user_input = str(self.User_Line.get())
user_input = int(float(user_input))
import math
factorial_num = math.factorial(user_input)
self.Output_Box.delete("1.0", "end")
self.Output_Box.insert("1.0", str(user_input) + "! = " + str(factorial_num))
def Resize_Window(self, event):
self.win_width = int(event.width)
self.win_height = int(event.height)
def Toggle_Fullscreen(self, event):
self.state_mode = not self.state_mode
self.attributes("-fullscreen", self.state_mode)
def Quit(self, event):
self.destroy()
if __name__ == "__main__":
App = GUI()
App.mainloop()
The simpler way is to print the processing message just before the call to factorial -- maybe something like:
print('calculating factorial of %d. . . ' % user_input, end='')
and once you have the answer:
print(factorial_num)
However, this will always print.
If you only want to print after two seconds have elapsed then you will need to start a timer thread, have it print after two seconds, or signal it to abort instead if you get your answer back first. Something like:
from threading import Thread
import time
class Timer(Thread):
abort = False
def run(self):
time.sleep(2)
if not self.abort:
print('Processing...')
and then around the math.factorial call:
timer = Timer()
timer.start()
factorial_num = math.factorial(user_input)
timer.abort = True
Unfortunately, time.sleep is not guaranteed to wake up exactly after two seconds, and on my system in doesn't wake up until after the math.factorial call is complete, making it useless for me.
Edit
A little more debugging and I discovered the problem is not the math.factorial function -- it can calculate 173,000 in about two seconds on my 5-year-old i7. The problem is trying to get that number as text in the tkinter window, which is 831,053 digits long (and took 46 seconds to popuulate).
Related
I'm making a reaction time game. description: Tkinter GUI, after random time a box with random color, size, font, place appears. openpyxl to save the game data if player wants to, threading for counting the seconds used, DateTime to put inside excel sheet for game data, random to choose place, size, font, color, text in box.
the intended game is that you get a box that appears after some time, you click, that box as fast as you can, it loops over that 3 times, when 3 times is finished you get a stats GUI with the time each of the 3 times, average time, save btn, play again btn.
error: the error is that the game counts wrong, if you try the game you will see that when the stats GUI come up all the time will be incremented by 0.01, example:
time 1: 1.15
time 2: 1.16
time 3: 1.17
if someone could point out the error that would be amazing.
code:
from tkinter import *
from tkinter import messagebox
from time import sleep
import openpyxl
import datetime
import random
import threading
def run_game():
root = Tk()
root.title("Reaction Time Game")
root.iconbitmap("C:/Users/Axelr/PycharmProjects/PC01/main/Self built/Reaction Time Game/icon.ico")
root.geometry("1400x1000")
root.configure(bg="orange")
workbook = openpyxl.load_workbook(filename="time_data.xlsx")
sheet = workbook.active
time_used_list = []
first_run = True
def clicked3():
global keep_counting
keep_counting = False
root.destroy()
root2 = Tk()
root2.title("Reaction Time Game Completed")
root2.iconbitmap("C:/Users/Axelr/PycharmProjects/PC01/main/Self built/Reaction Time Game/icon.ico")
root2.geometry("700x570")
root2.configure(bg="lightblue")
def save():
current_datetime = datetime.datetime.now()
current_datetime = current_datetime.strftime("%Y-%m-%d | %H:%M")
place = sheet.max_row + 1
sheet[f"A{place}"] = round(time_used_list[0], 2)
sheet[f"B{place}"] = round(time_used_list[1], 2)
sheet[f"C{place}"] = round(time_used_list[2], 2)
average = sum(time_used_list) / len(time_used_list)
sheet[f"D{place}"] = round(average, 2)
sheet[f"E{place}"] = current_datetime
workbook.save(filename="time_data.xlsx")
print("Saved time data")
def run_another():
root2.destroy()
run_game()
print("list of times:")
for time in time_used_list:
print(time)
title_label = Label(root2, text="Results", font=("Arial", 20, "bold"), bg="orange")
title_label.pack(padx=20, pady=20)
time1_label = Label(root2, text=f"Time used 1: {round(time_used_list[0], 2)} seconds", font=("Arial", 20), bg="orange")
time1_label.pack(padx=20, pady=20)
time2_label = Label(root2, text=f"Time used 2: {round(time_used_list[1], 2)} seconds", font=("Arial", 20), bg="orange")
time2_label.pack(padx=20, pady=20)
time3_label = Label(root2, text=f"Time used 3: {round(time_used_list[2], 2)} seconds", font=("Arial", 20), bg="orange")
time3_label.pack(padx=20, pady=20)
average_time = sum(time_used_list) / len(time_used_list)
average_label = Label(root2, text=f"Average Time Used: {round(average_time, 2)} seconds", font=("Arial", 20), bg="orange")
average_label.pack(padx=20, pady=20)
save_btn = Button(root2, text="Save", font=("Arial", 20), command=save, bg="orange")
save_btn.pack(padx=20, pady=20)
play_btn = Button(root2, text="Play Again", font=("Arial", 20), bg="orange", command=run_another)
play_btn.pack(padx=20, pady=20)
root2.mainloop()
def clicked():
global keep_counting, times_clicked, first_run
keep_counting = False
if times_clicked == 2:
clicked3()
else:
times_clicked += 1
btn.destroy()
print(first_run)
print(times_clicked)
first_run = False
start(first_run)
def time_thread():
global time_used
time_used = 0.00
while keep_counting:
sleep(0.01)
time_used += 0.01
time_used_list.append(time_used)
def start(first):
global keep_counting, times_clicked, first_run
if first:
times_clicked = 0
first_run = False
color_list = ["red", "green", "yellow", "cyan", "white", "blue", "magenta"]
random_color = random.choice(color_list)
random_x = random.randint(100, 600)
print(f"random_x = {random_x}")
random_y = random.randint(100, 600)
print(f"random_y = {random_y}")
random_width = random.randint(5, 15)
random_height = random.randint(5, 20)
print(f"random_width = {random_width}, random_height = {random_height}")
font_list = ["Helvetica", "Garamond", "Frutiger", "Bodoni", "Times", "Futura"]
random_font = random.choice(font_list)
random_font_size = random.randint(10, 18)
print(f"Font = {random_font}, font_size = {random_font_size}")
text_list = ["!", "?", "/", "+", "=", "<", ">", "%", "&", "(", ")", "-", "|", ";", ":", "[", "]", "{", "}", "^",
"#", "#"]
random_text = random.choice(text_list)
print(f"Random text = {random_text}")
random_time = random.randint(3, 5)
print(f"Random time = {random_time}")
thread = threading.Thread(target=time_thread)
keep_counting = True
def btn_create():
global btn
btn = Button(root, text=random_text, font=(random_font, random_font_size), bg=random_color, width=random_width, command=clicked)
btn.place(x=random_x, y=random_y)
thread.start()
time_wait = random_time * 1000
root.after(time_wait, btn_create)
start(first_run)
root.mainloop()
run_game()
please help me.
Your time_thread assumes that a sleep of 0.01 seconds always takes 0.01 seconds. That is patently false. Sleep merely guarantees you will wait for AT LEAST 0.01 seconds. On Windows, for example, the scheduler interval is about 0.016 seconds, so you can never sleep less than that. You can fix that by changing your timing thread to just snapshot the time before and after.
def time_thread():
start = time.time()
while keep_counting:
time.sleep(0.01)
time_used_list.append(time.time()-start)
HOWEVER, even that has a huge flaw. The problem here is that, because of Python's interpreter lock, it's quite possible for keep_counting to be set False and then True again before the time_thread gets a chance to run, in which case it will just keep running. You end up with all three time_threads counting at once.
You don't need that loop. Just capture the starttime when you display the button, and capture the end time when it is clicked. Eliminate keep_counting and the time thread altogether. This eliminated the need for importing threading at all.
So:
def run_game():
root = Tk()
root.title("Reaction Time Game")
root.geometry("1400x1000")
root.configure(bg="orange")
starttime = 0
time_used_list = []
first_run = True
def clicked3():
root.destroy()
root2 = Tk()
...
def clicked():
global times_clicked, first_run
time_used_list.append(time.time()-starttime)
if times_clicked == 2:
clicked3()
...
random_time = random.randint(3, 5)
print(f"Random time = {random_time}")
def btn_create():
global btn
btn = Button(root, text=random_text, font=(random_font, random_font_size), bg=random_color, width=random_width, command=clicked)
btn.place(x=random_x, y=random_y)
starttime = time.time()
time_wait = random_time * 1000
root.after(time_wait, btn_create)
As a rule, don't count elapsed time using a thread. Just grab a start time and an end time and subtract.
So, I'm working on a timer using Python and Tkinter's GUI. To do so, I intend to convert the minutes into seconds (as seen in my code) and use Python's time.sleep command to count down (not yet implemented).
from tkinter import *
import time
countdown = Tk()
countdown.geometry('500x300')
minute = Text(countdown, height = 0.01, width = 5)
minute.place(x=100, y=100)
minlabel = Label(text = "Input Minutes", font = ("MS Sans Serif", 10), bg = 'Light Blue')
minlabel.place(x=85, y = 80)
def go(event):
minseconds = int(minute.get("1.0", END))*60
print(int(minseconds))
countdown.bind('<Return>', go)
countdown.mainloop()
However, when I convert it to minutes, it works the first time (I.E, when I input 3, 180 returns), but any time after that I get this:
ValueError: invalid literal for int() with base 10: '3\n3\n\n'
Any idea what could be causing this? And why it works the first time but then stops working? Thanks :)
The problem is every time Enter is pressed a newline character is entered into the Text widget named minute and they mess up the conversion to int.
The simplest way I can think of to avoid that would be to just clear the widget in the event handler function:
def go(event):
minseconds = int(minute.get("1.0", END))*60
print(int(minseconds))
minute.delete('1.0', END) # <---- ADDED.
so actually the error is quite simple. You created a Text widget, which is just adding lines when you press Enter (corresponding to the \n characters). Just make your text widget bigger (instead of height = 0.01 maybe 2) and you will see it.
To do it more elegantly, you might try Entry() instead of Text(). See the example.
import time
import tkinter as tk
def go(event):
minseconds = int(min_entry.get())
seconds = minseconds*60
print(int(seconds))
countdown = tk.Tk()
countdown.geometry('500x300')
minlabel = tk.Label(text = "Input Minutes", font = ("MS Sans Serif", 10), bg = 'Light Blue')
minlabel.place(x=85, y = 80)
min_entry = tk.Entry()
min_entry.place(x=100, y=100)
countdown.bind('<Return>', go)
countdown.mainloop()
Furthermore try to put your functions always on top of your code, as they have to be defined before you call them. This might be causing trouble as you code gets bigger.
Hope you get a better understanding from tkinter by this :)
Check this update function. Import the module at top
import messagebox as msg
def go(event):
try:
minseconds = int(minute.get("1.0", END))*60
print(int(minseconds))
minute.delete("1.0", END)
except Exception:
msg.showerror("Error","invalid input provided.")
minute.delete("1.0", END)
But I would suggest to go with Entry With a StringVar()
from tkinter import *
from tkinter import messagebox as msg
countdown = Tk()
countdown.geometry('500x300')
minute=StringVar()
minut = Entry(countdown,textvariable=minute, width = 5)
minut.place(x=100, y=100)
minlabel = Label(text = "Input Minutes", font = ("MS Sans Serif", 10), bg = 'Light Blue')
minlabel.place(x=85, y = 80)
def go(event):
try:
minseconds = int(minute.get())*60
print(int(minseconds))
minute.set("")
except ValueError:
msg.showerror("Error","Invalid input provided.")
countdown.bind('<Return>', go)
countdown.mainloop()
Hello I am trying to make a simple script which on key press x starts a timer of 45 sec after that when it reaches 10 sec color text changes to red and when countdown comes 0 I want to destroy the timer gui but not the program, and when i press x again program starts doing stuff again and repeats the process
So far I managed this I tried all day I also tried adding on keypress but it become so complicated and didn't worked so I am asking help here
from tkinter import *
root = Tk()
root.geometry("112x55")
root.overrideredirect(True)
root.lift()
root.wm_attributes("-topmost", True)
root.wm_attributes("-disabled", True)
root.wm_attributes("-transparentcolor", "white")
root.resizable(0, 0)
seconds = 45
def timer():
global seconds
if seconds > 0:
seconds = seconds - 1
mins = seconds // 60
m = str(mins)
if mins < 10:
m = '0' + str(mins)
se = seconds - (mins * 60)
s = str(se)
if se < 10:
s = '0' + str(se)
time.set(m + ':' + s)
timer_display.config(textvariable=time)
# call this function again in 1,000 milliseconds
root.after(1000, timer)
elif seconds == 0:
seconds.delete("1.0","end")
frames = Frame(root, width=500, height=500)
frames.pack()
time = StringVar()
timer_display = Label(root, font=('Trebuchet MS', 30, 'bold'))
timer_display.place(x=0, y=0)
timer() # start the timer
root.mainloop()
As you used wm_attributes('-transparentcolor', 'white') (but you never set the background color of root and the timer_display to white, so it don't have effect) and overrideredirect(True), that means you want the window totally transparent and borderless. However this has side effect that you may never get the window focus back after the window loses focus.
Also you used wm_attributes('-disabled', True), then you can't have any key press event triggered. It should not be used.
Suggest to use wm_attributes('-alpha', 0.01) to simulate the transparent effect without the above issues.
Below is an example:
from tkinter import *
root = Tk()
root.geometry("+0+0")
root.overrideredirect(True)
root.wm_attributes("-topmost", True)
root.wm_attributes("-alpha", 0.01)
root.resizable(0, 0)
seconds = 45
def countdown(seconds):
if seconds > 0:
mins, secs = divmod(seconds, 60)
timer_display.config(text="{:02d}:{:02d}".format(mins, secs),
fg='red' if seconds <= 10 else 'white')
root.after(1000, countdown, seconds-1)
else:
root.wm_attributes('-alpha', 0.01) # make the window "disappear"
def start_countdown(event):
root.wm_attributes('-alpha', 0.7) # make the window "appear"
countdown(seconds)
timer_display = Label(root, font=('Trebuchet MS', 30, 'bold'), bg='black')
timer_display.pack()
root.bind('x', start_countdown)
root.bind('q', lambda e: root.destroy()) # provide a way to close the window
root.mainloop()
NOTE #1: If the window loses focus, you can still click somewhere near the top-left corner of the screen to resume window focus, although you cannot see the window.
NOTE #2: If you want system-wise key handler, tkinter does not support it. You need to use other module, like pynput.
If you want to destroy the timer GUI, You'll need to make a class like this:
class Timer(Frame):
def __init__(self, master):
super().__init__(master)
# Paste the code you want to run here, make sure you put "self." before it.
# For example:
def clicked():
print('Clicked!')
self.myButton = Button(self, text="Click me!", border=0, width=25, height=1, command=self.clicked)
self.logbtn.grid(columnspan=2)
self.pack()
if seconds == 0:
self.destroy() # by using self.destroy(), you tell it to delete the class.
else:
# You can put whatever you want it to do if it's not 0 here.
tm = Timer(root)
You can bind functions to keystroke events with root.bind(event, callback).
If you are using Linux or Mac, root.overrideredirect(True) will
prevent your application from receiving keystroke events. You can read
more here: Tkinter's overrideredirect prevents certain events in Mac and Linux
Example:
def keydown(e):
print(f"Key pressed: ")
print("Key code:", e.keycode)
print("Key symbol:", e.keysym)
print("Char:", e.char)
def keyup(e):
print(f"Key '{e}' released")
root.bind("<KeyPress>", keydown)
root.bind("<KeyRelease>", keyup)
root.focus_set()
Alternatively, you can also bind to specific keys with <Key-KEYSYM>, e.g. <Key-space> for the spacebar. A list with all keysyms can be found here
Some more events are listed here
Implementation example
Here is an example with a custom CountdownLabel class that is derived from tkinter.Label and automatically binds to the spacebar key event.
app.py
from countdown import CountdownLabel
from tkinter import Frame, StringVar, Tk, Button
root = Tk()
root.geometry("120x60")
root.lift()
root.wm_attributes("-topmost", True)
root.resizable(0, 0)
# Not supported on Linux and MacOS
# root.overrideredirect(True)
# root.wm_attributes("-disabled", True)
# root.wm_attributes("-transparentcolor", "white")
timer_display = CountdownLabel(root, 10, 5)
timer_display.pack(fill="both", expand=True)
timer_display.configure(background="white")
timer_display.configure(font=('Trebuchet MS', 26, 'bold'))
timer_display.focus_set()
root.mainloop()
countdown.py
from tkinter import Label
class CountdownLabel(Label):
# context : A reference to the Label in order to change the text and
# to close it later on
# duration: Total time in seconds
# critical: Length of the last timespan before the countdown finishes
# in seconds
def __init__(self, context, duration, critical):
super().__init__(context)
self.duration = duration
self.critical = critical if duration >= critical else duration
self.update_ui()
self.bound_sequence = "<Key-space>"
self.bound_funcid = self.bind(self.bound_sequence, self.get_handler())
# Returns a function for the event binding that still has access to
# the instance variables
def get_handler(self):
# Gets executed once when the counter starts through handler() and calls
# itself every second from then on to update the GUI
def tick():
self.after(1000, tick)
self.update_ui()
self.duration -= 1
# Gets executed when time left is less than <critical> (default = 10s)
# Sets the font color to red
def change_font_color():
self.configure(foreground="red")
# Destroys itself after the countdown finishes
self.after((self.critical + 1) * 1000, lambda : self.destroy())
def handler(event):
self.unbind(self.bound_sequence, self.bound_funcid)
self.bound_funcid = -1
self.bound_sequence = None
self.after((self.duration - self.critical) * 1000, change_font_color)
tick()
return handler
# Updates the displayed time in the label
def update_ui(self):
mm = self.duration // 60
ss = self.duration % 60
self.config(text="%02d:%02d" % (mm, ss))
def change_binding(self, sequence):
if self.bound_funcid > 0:
self.unbind(self.bound_sequence, self.bound_funcid)
self.bound_sequence = sequence
self.funcid = self.bind(self.bound_sequence, self.get_handler())
I'm new to python coding and I have been working on a project which could click on an image based on a chosen color. I have been using a program which loops the search 50 times when I click the start button. However, I have been trying to implement a stop button, but the problem is that my code freezes when the loop is running. Any ideas?
I have heard to try threading but it seems very complicated and I have been unable to follow any tutorials properly in relation to my code. By the way, the image searched has been testing images I've been using stored inside the program files.
from imagesearch import *
import pyautogui
import tkinter as tk
from tkinter import *
from tkinter.ttk import *
import time
import threading
# ---Defined Programs---
def run():
global enterColor
enterColor = str(enterColorField.get())
program(enterColor)
def program(color):
whitePos = imagesearch_numLoop(str(color) + ".PNG", 0, 50)
pyautogui.moveTo(whitePos[0] + 20, whitePos[1] + 10)
pyautogui.click()
def stop():
print("Placeholder")
# ---Main Runner---
window = tk.Tk()
window.geometry("250x250")
window.configure(background="#181b54")
app = tk.Frame(window)
app.grid()
enterColorLabel = tk.Label(window, text="Enter Color:", bg="#181b54", fg="white")
enterColorLabel.place(x=10, y=50)
enterColorField = Combobox(window)
enterColorField['values'] = ("Black", "White")
enterColorField.current("0") # set the selected item
enterColorField.place(x=10, y=70)
submitButton = tk.Button(window, text="Start", bg="#66ff00", command=run)
submitButton.place(x=10, y=130)
stopButton = tk.Button(window, text="Stop", bg="red", command=stop)
stopButton.place(x=50, y=130)
window.mainloop()
#---New Python Script---
import cv2
import numpy as np
import pyautogui
import random
import time
def imagesearch_numLoop(image, timesample, maxSamples, precision=0.8):
pos = imagesearch(image, precision)
count = 0
while pos[0] == -1:
print(image+" not found, waiting")
count = count + 1
if count>maxSamples:
break
pos = imagesearch(image, precision)
return pos
Whenever clicking start, the whole code freezes. I can't even (x) out.
Here's a hopefully simple multiprocessing recipe that will work for you. We'll have three main functions. The first will be an example loop that you would put your processing inside. I included arguments in the function to show you that it's possible to pass args and kwargs while using multiprocessing.
def loop(a, b, c, d):
# Will just sleep for 3 seconds.. simulates whatever processing you do.
time.sleep(3)
return
Next is a function we will use to queue the multiprocessing process.
def queue_loop():
p = multiprocessing.Process(target = loop,
args = (1, 2),
kwargs = {"c": 3, "d": 4})
# You can pass args and kwargs to the target function like that
# Note that the process isn't started yet. You call p.start() to activate it.
p.start()
check_status(p) # This is the next function we'll define.
return
Then, you may be interested in knowing the status of your process throughout its execution. For example it is sometimes desirable to disable certain buttons while a command is being run.
def check_status(p):
""" p is the multiprocessing.Process object """
if p.is_alive(): # Then the process is still running
label.config(text = "MP Running")
mp_button.config(state = "disabled")
not_mp_button.config(state = "disabled")
root.after(200, lambda p=p: check_status(p)) # After 200 ms, it will check the status again.
else:
label.config(text = "MP Not Running")
mp_button.config(state = "normal")
not_mp_button.config(state = "normal")
return
Throwing this all together into one snippet:
import tkinter as tk
import multiprocessing
import time
def loop(a, b, c, d):
# Will just sleep for 3 seconds.. simulates whatever processing you do.
time.sleep(3)
return
def queue_loop():
p = multiprocessing.Process(target = loop,
args = (1, 2),
kwargs = {"c": 3, "d": 4})
# You can pass args and kwargs to the target function like that
# Note that the process isn't started yet. You call p.start() to activate it.
p.start()
check_status(p) # This is the next function we'll define.
return
def check_status(p):
""" p is the multiprocessing.Process object """
if p.is_alive(): # Then the process is still running
label.config(text = "MP Running")
mp_button.config(state = "disabled")
not_mp_button.config(state = "disabled")
root.after(200, lambda p=p: check_status(p)) # After 200 ms, it will check the status again.
else:
label.config(text = "MP Not Running")
mp_button.config(state = "normal")
not_mp_button.config(state = "normal")
return
if __name__ == "__main__":
root = tk.Tk()
mp_button = tk.Button(master = root, text = "Using MP", command = queue_loop)
mp_button.pack()
label = tk.Label(master = root, text = "MP Not Running")
label.pack()
not_mp_button = tk.Button(master = root, text = "Not MP", command = lambda: loop(1,2,3,4))
not_mp_button.pack()
root.mainloop()
The result is that when you click the "Using MP" button, the command buttons will be disabled and the process will be started without freezing your UI. Clicking the "Not MP" button will start the function like 'normal' and will freeze your UI as you noticed in your own code.
A simple answer is you cannot use while loop in GUI design.
But you can use the method .after(delay, callback=None) instead.
Here is an example:
from tkinter import *
root = Tk()
def loop():
print("Hi!")
root.after(1000, loop) # 1000 is equal to 1 second.
root.after(1000, loop) # This line is to call loop() in 1 second.
root.mainloop()
This is the code for the function I'm using to start the main part of the program, however I want some sort of loop or something which creates ten questions, but waits for an input from the Entry box before moving onto the next question.
Any ideas?
def StartGame():
root = Tk()
root.title("Maths Quiz - Trigonometry and Pythagoras' Theorem | Start The Game")
root.geometry("640x480")
root.configure(background = "gray92")
global AnswerEntry
TotScore = 0
Count = 0
AnswerReply = None
WorkingArea = Text(root, width = 70, height = 10, wrap = WORD).place(x = 38, y = 100)
n = GetRandomNumber()
Angle,Opposite,Adjacent,Hypotenuse = Triangle()
Question,RealAnswer = QuestionLibrary(Opposite,Adjacent,Hypotenuse,Angle,n)
AskQuestion = Label(root, text = Question, wraplength = 560).place(x = 48, y = 300)
PauseButton = ttk.Button(root, text = "Pause").place(x = 380, y = 10)
HelpButton = ttk.Button(root, text = "Help", command = helpbutton_click).place(x = 460, y = 10)
QuitButton = ttk.Button(root, text = "Quit", command = root.destroy).place(x = 540, y = 10)
AnswerEntry = Entry(root)
AnswerEntry.place(x = 252, y = 375)
SubmitButton = ttk.Button(root, text = "Submit", command = submit_answer).place(x = 276, y = 400)
TotScore,AnswerReply = IsAnswerCorrect(Answer,RealAnswer)
ScoreLabel = ttk.Label(root, text = TotScore).place(x = 38, y = 10)
AnswerReplyLabel = ttk.Label(root, text = AnswerReply).place(x = 295, y = 440)
root.mainloop()
I want the loop to start after the AnswerReply = None
You don't want a loop. The only really important loop inside a GUI should be the mainloop(), handling signal and executing callbacks.
Example:
try:
import Tkinter as Tk
except ImportError:
import tkinter as Tk
class QAGame(Tk.Tk):
def __init__(self, questions, answers, *args, **kwargs):
Tk.Tk.__init__(self, *args, **kwargs)
self.title("Questions and answers game")
self._setup_gui()
self._questions = questions[:]
self._answers = answers
self._show_next_question()
def _setup_gui(self):
self._label_value = Tk.StringVar()
self._label = Tk.Label(textvariable=self._label_value)
self._label.pack()
self._entry_value = Tk.StringVar()
self._entry = Tk.Entry(textvariable=self._entry_value)
self._entry.pack()
self._button = Tk.Button(text="Next", command=self._move_next)
self._button.pack()
def _show_next_question(self):
q = self._questions.pop(0)
self._label_value.set(str(q))
def _move_next(self):
self._read_answer()
if len(self._questions) > 0:
self._show_next_question()
self._entry_value.set("")
else:
self.quit()
self.destroy()
def _read_answer(self):
answer = self._entry_value.get()
self._answers.append(answer)
def _button_classification_callback(self, args, class_idx):
self._classification_callback(args, self._classes[class_idx])
self.classify_next_plot()
if __name__ == "__main__":
questions = ["How old are you?",
"What is your name?"]
answers = []
root = QAGame(questions, answers)
root.mainloop()
for q,a in zip(questions, answers):
print "%s\n>>> %s" % (q, a)
We only have a Label, an Entry and a Button (I did not care about layout!, just pack()).
Attached to the button is a command (aka callback). When the button is pressed, the answer is read and the new question is assigned to the label.
Usage of this class is understandable from the example in the `if name == "main" block. Please note: the answers-list is filled in place, the questions-list is kept unchanged.
I don't know Tk, but is there no any signals of input text changed? There should be for sure. Just check if this signal occured and then move onto new question, because it means that someone typed something in input box.