As anyone would know python will stop or pause at input(), this makes it hard to get input with a timeout, this is possible:
import tkinter as tk
class ExampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
def well():
whatis = entrybox.get()
if whatis == "": # Here you can check for what the input should be, e.g. letters only etc.
print ("You didn't enter anything...")
else:
print ("AWESOME WORK DUDE")
app.destroy()
global label2
label2 = tk.Button(text = "quick, enter something and click here (the countdown timer is below)", command = well)
label2.pack()
entrybox = tk.Entry()
entrybox.pack()
self.label = tk.Label(self, text="", width=10)
self.label.pack()
self.remaining = 0
self.countdown(10)
def countdown(self, remaining = None):
if remaining is not None:
self.remaining = remaining
if self.remaining <= 0:
app.destroy()
print ("OUT OF TIME")
else:
self.label.configure(text="%d" % self.remaining)
self.remaining = self.remaining - 1
self.after(1000, self.countdown)
if __name__ == "__main__":
app = ExampleApp()
app.mainloop()
My real question is why does the code pause at input and mainly what benefits are there from this?
Surely if we can get around this (for just about anything I presume) then it is silly to have the code hold up like that. All opinions welcome, give me your view.
Maybe there should be a built in function to disable the pause. This would make multithreading much easier, but the pause is handy when you have to test some variable that is created with the input:
input1 = input("enter a big number")
if input1 >= 8:
print("That is a big number")
else:
print("That is tiny...")
if this was run without a pause you would get an error input1 is not defined, so the pause is crucial. hope this is helpful.
One benefit? If your code comes across a variable that should have been set, but doesn't exist because the user hasn't entered a value yet, it would raise an error. For example:
legal_age = 21
age = int(input("Your age: "))
if age >= legal_age:
print("You can drink legally!")
else:
print("You can't drink yet!")
A basic sample, but none the less - how would Python use the age variable if it doesn't have a value yet, because it didn't pause to wait for an input?
Threads can be used fairly easily for processes that you want to happen behind an input, though.
Related
I participated in a local programming competition yesterday, where I was put on a team of four with members of varying skill levels. We were trying to make a text-based Zork style game, but since UI was one of the categories I was put on creating a GUI which I have no experience with in python. I used Tkinter and got everything mostly working inside the GUI itself, but can't figure out how to get the GUI to interact with the rest of the code.
I'm just including the relevant bits here, partially because the rest of the code is quite a mess, with no comments and lots of syntax errors, but I can post it if needed. Here's the code:
def pinput():
global e
player_input = e.get()
print(player_input)
def progress():
hallway_1()
def hallway_1():
global player_input
global prin
global e
prin.set("You are in a hallway. Yellow lockers line the sides of all walls and you see three doors. One leads into a Library, one to a History classroom, and one to a Math room. You can 1.) Search the hallway or 2.) Procede to one of the rooms.")
while player_input != "1" and player_input != "2": #Best guess at a way to get the code to wait for input, actually just causes code to freeze
pass
if player_input == "1": #Even if 1 is in the entry box beforehand, it never gets here
prin.set("You search through the lockers and find 1 health potion.\n Would you like to add it to your inventory? 1.) Yes 2.) No")
player_input = "999"
def player_attack():
global enemy_hp
enemy_hp -= player_strength
prin.set("You just attacked the " + enemy_name + "!")
def player_block():
global block
prin.set("You predict that your enemy is going to attack and decide to block")
block = 1
def player_use():
global player_item_use
global item_name
player_item_use = 100
prin.set("You decide to use one of your items. What do you use?")
while player_turn_input != 0 or 1 or 2 or 3 or 4 or 5 or 6 or 7 or 8 or 9 or 10 or 11 or 12 or 13 or 14 or 15 or 16 or 17 or 18 or 19 or 20 or 21 or 22 or 23 or 24 or 25 or 26:
player_item_use = e.get()
time.sleep(1)
item_name = Inventory[player_item_use]
item(item_name)
def run_away():
global player_hp
global lost_item_1
global lost_item_2
prin.set("You decide to run away from the " + enemy_name + "!\nThis causes you to lose 20 health points and two inventory items.")
player_hp -= 20
if backpack == True:
lost_item_1 = random.randint(1,27)
lost_item_2 = random.randint(1,27)
else:
lost_item_1 = random.randint(1,9)
lost_item_2 = random.randint(1,9)
RemoveInventory(lost_item_1)
RemoveInventory(lost_item_2)
library()
top = tk.Tk()
prin = tk.StringVar()
#path = "smalllibrary.png"
#tkimage = ImageTk.PhotoImage(Image.open(path))
#displayimage = tk.Label(top, image=tkimage).grid(row=0)
inv = tk.Text(top, width="24")
inv.grid(row=0, column=1)
for x in Inventory:
inv.insert("end", str(x) + str(invnum) + '\n')
invnum += 1
healthbar = tk.Label(top, text=("Health: " + str(player_hp))).grid(row=1, column=1)
text = tk.Label(top, bg="black", fg="white", textvariable=prin, justify="left", cursor="box_spiral").grid(row=1, sticky="w")
e = tk.Entry(top)
e.grid(row=2)
e.focus_set()
Attack = tk.Button(top, text ="Attack", activebackground="red", width="10", command = player_attack).grid(row=4, sticky="e")
Block = tk.Button(top, text ="Block", activebackground="red", width="10", command = player_block).grid(row=5, sticky="e")
Use = tk.Button(top, text ="Use Item", activebackground="red", width="10", command = player_use).grid(row=4, column=1, sticky="w")
Run = tk.Button(top, text ="Run", activebackground="red", width="10", command = run_away).grid(row=5, column=1, sticky="w")
Enter = tk.Button(top,text='Enter',command=pinput).grid(row=3)
Progress = tk.Button(top, text="Progress", activebackground="green",
prin.set("REDACTED")#This had my team's full names
top.mainloop()
So, there are a few problems. I added the "progress" button for testing purposes, ideally the game would start in Hallway 1. However, I couldn't figure out where to put the hallway_1() function for this to work. Before the top.mainloop() and the GUI wouldn't open. After it, and e.get() threw TCL errors.
So, I bound it to a button. However, now the code keeps getting stuck in the "while" loop even if 1 is inputted into the entry box before the code runs. I'm perplexed by this because Visual Studio reports player_input as being '1' in the Autos box, but it keeps running through the loop anyway.
You can't do a while loop like that in a GUI event handler. Until you return from the handler function, the GUI doesn't get to update its display, accept user input, or do anything else, which means player_input will never change once you get there. A handler function needs to do one thing, set up anything needed for future handlers, and return immediately.
It's hard to get your head around thinking in terms of the event loop and handler callbacks the first time you write a GUI, but it's absolutely essential; until you do, things seem bizarre and arbitrary.
So, the answer isn't to "wait for input", but to set up a handler that fires when that input is ready. (You can instead set up a handler on an after function so it fires every N milliseconds instead of firing on input, but that usually just makes things more complicated.)
One way to do this is to have the "Progress" button trigger "the next step", and keep track of some state that lets you determine what "the next step" is. An in fact, you already have that state: it's whether the player has already given correct input or not. So:
def pinput():
global player_input
player_input = e.get()
print(player_input)
def progress():
pinput()
if player_input != "1" and player_input != "2":
hallway_1_enter()
else:
hallway_1_doit()
def hallway_1_enter():
global player_input
global prin
global e
prin.set("You are in a hallway. Yellow lockers line the sides of all walls and you see three doors. One leads into a Library, one to a History classroom, and one to a Math room. You can 1.) Search the hallway or 2.) Procede to one of the rooms.")
def hallway_1_doit():
if player_input == "1": #Even if 1 is in the entry box beforehand, it never gets here
# etc.
This is obviously a bit of a clunky design, but it's the smallest change to your existing design that gets you past the first step. Once you get the hang of it, you should be able to improve it from there.
Also notice the global player_input I put in pinput. Your existing function didn't have that, so it was just creating a local variable with the same name, which then goes away immediately; the global never gets changed. Any function that wants to assign to a global needs the global statement.
I have a script that continually takes in text and outputs text (its a text based game)
I would like to run it through a tkinter GUI as opposed to the console
Python : Converting CLI to GUI
This question perfectly answers how to convert "print" into a GUI insert.
The problem is that my game obviously runs through a ton of loops, and that screws up the "app.mainloop()" because it either never runs (and then the GUI never shows up) or you run it first, and it doesn't let anything else run.
I suppose I could try and and stagger these loops somehow, but that seems very hackish. I could also try to modify my entire codebase to run inside the app.mainloop(), but what I really think I need is multiple threads. Problem is, I have no idea how to make that work.
There are a few other questions, but they either don't work or don't make much sense:
Tkinter with multiple threads
Run process with realtime output to a Tkinter GUI
Thanks.
Edit: extremely simplified code:
def moveNorth():
print('You have moved north')
def interpreter(command):
if command == 'go north':
moveNorth()
else:
print('invalid command')
def listener():
playerchoice = sys.stdin.readline().strip()
return playerchoice
if __name__ == '__main__':
print('Welcome')
while playing:
interpreter(listener())
I think you might be making it more complicated than it needs to be.
For Tkinter at least it is very simple change console interactions into a GUI interaction instead.
The simplest example I can give is to use an Entry field for user input and a Text widget for the output.
Here is a simple example of a console based game being moved to a GUI using Tkinter.
Console number guessing game:
import random
print("simple game")
print("-----------")
random_num = random.randint(1, 5)
print(random_num)
x = True
while x == True:
#Input for user guesses.
guess = input("Guess a number between 1 and 5: ")
if guess == str(random_num):
#Print answer to console.
print("You win!")
x = False
else:
print("Try again!")
Here is the Tkinter GUI example of the same game:
import tkinter as tk
import random
root = tk.Tk()
entry_label = tk.Label(root, text = "Guess a number between 1 and 5: ")
entry_label.grid(row = 0, column = 0)
#Entry field for user guesses.
user_entry = tk.Entry(root)
user_entry.grid(row = 0, column = 1)
text_box = tk.Text(root, width = 25, height = 2)
text_box.grid(row = 1, column = 0, columnspan = 2)
text_box.insert("end-1c", "simple guessing game!")
random_num = random.randint(1, 5)
def guess_number(event = None):
#Get the string of the user_entry widget
guess = user_entry.get()
if guess == str(random_num):
text_box.delete(1.0, "end-1c") # Clears the text box of data
text_box.insert("end-1c", "You win!") # adds text to text box
else:
text_box.delete(1.0, "end-1c")
text_box.insert("end-1c", "Try again!")
user_entry.delete(0, "end")
# binds the enter widget to the guess_number function
# while the focus/cursor is on the user_entry widget
user_entry.bind("<Return>", guess_number)
root.mainloop()
As you can see there is a bit more code for the GUI but most of that is the GUI design.
The main part that you need to change is the use of entry vs input for your answers and the use of insert vs print for your response. The rest is really just design stuff.
If you want to keep the questions on a continuous nature you can update the label with a new question or you could even use tkinters askstring function for each new question. There are many options.
the main thing is getting the value of the user answer, using that answer to test with the question, then printing the results to the text box.
I am fairly new in Tkinter GUI and I there are still quite a lot I don't understand.
I am really stuck with my program and I really need someone's guidance to show me why I am having issues with my program.
The problem I have it that I want to deposit a certain amount of money to the bank and calculate it with the current available balance, but when I enter the amount, the output I get is 'Invalid amount entered', even though I am entering the right number, such as 20 or 50 and so on.
Can someone please help me:
Here is the code:
class Demo4():
def __init__(self,master):
self.master=master
self.frame=Frame(self.master)
self.frame.grid()
self.master.minsize(width=400,height=120)
self.master.title('Deposit')
self.label_1=Label(self.frame,text='Deposit Amount:')
self.label_1.grid(row=1,padx=7,pady=5,sticky='we')
self.label_2=Label(self.frame,text='Balance:')
self.label_2.grid(row=2,padx=7,pady=5,sticky='e')
self.button_1=Button(self.frame,text='Calculate',width=8,height=3,
command=self.MakeDeposit)
self.button_1.grid(row=1,column=10,sticky='e'+'w',padx=7,rowspan=2)
self.entry_1=Entry(self.frame,width=40)
self.entry_1.grid(row=1,column=1, sticky='e'+'w',columnspan=3)
self.entry_2=Entry(self.frame,width=40)
self.entry_2.grid(row=2,column=1,sticky='e'+'w',columnspan=3)
self.logLabel=Label(self.frame,text='DEPOSIT',font="Helvetica 10 bold italic")
self.logLabel.grid(row=0,padx=5,pady=5)
self.e2=IntVar()
self.e1=IntVar()
I know that the first part of the code Class Demo4 is working because I tried to test it on its own. I think that the problem is the second part of MakeDeposit function.
def MakeDeposit(self):
self.e2=IntVar()
self.e1=IntVar()
num2 = self.e2.get()
balance = int(num2)
num1 = self.e1.get()
depositAmount = int(num1)
while depositAmount<=0:
self.errorLabel = Label(self.frame,text="That was not a valid amount").grid(row=3,columnspan=5)
return
if self.depositAmount>0:
self.textLabel = Label(self.frame,text=("Your new balance is:\n")).grid(row=3,columnspan=5)
self.newBalanceLabel = Label(self.frame,text=(balance+depositAmount)).grid(row=4,columnspan=5, pady=7)
Now, the problem I am encountering is that when I enter the first amount of the deposit and add the balance available to the deposit(as that is the only way I know how to code it) I get a dialog box, which says that the 'entered amount is invalid', which means that the while loop is not functioning properly. Unfortunately, I tried different variations and it still does not work.
Please help, I am really stuck.
I also tried to change the layout of the variables and names, but its still giving me the same error:
Here is the changes:
class Demo4():
def __init__(self,master):
self.master=master
self.frame=Frame(self.master)
self.frame.grid()
self.master.minsize(width=400,height=120)
self.e_2=IntVar()
self.e_1=IntVar()
self.master.title('Deposit')
self.label_1=Label(self.frame,text='Deposit Amount:')
self.label_1.grid(row=1,padx=7,pady=5,sticky='we')
self.label_2=Label(self.frame,text='Balance:')
self.label_2.grid(row=2,padx=7,pady=5,sticky='e')
self.button_1=Button(self.frame,text='Calculate',width=8,height=3,command=self.MakeDeposit)
self.button_1.grid(row=1,column=10,sticky='e'+'w',padx=7,rowspan=2)
self.entry_1=Entry(self.frame,width=40,textvariable=self.e_1)
self.entry_1.grid(row=1,column=1, sticky='e'+'w',columnspan=3)
self.entry_2=Entry(self.frame,width=40,textvariable=self.e_2)
self.entry_2.grid(row=2,column=1,sticky='e'+'w',columnspan=3)
self.logLabel=Label(self.frame,text='DEPOSIT',font="Helvetica 10 bold italic")
self.logLabel.grid(row=0,padx=5,pady=5)
def MakeDeposit(self):
self.entry_2=IntVar()
self.entry_1=IntVar()
num2 = self.entry_2.get()
balance = int(num2)
num1 = self.entry_1.get()
depositAmount = int(num1)
while depositAmount<=0:
self.errorLabel = Label(self.frame,text="That was not a valid amount").grid(row=3,columnspan=5)
return
if depositAmount>0:
self.textLabel = Label(self.frame,text=("Your new balance is:\n")).grid(row=3,columnspan=5)
self.newBalanceLabel = Label(self.frame,text=(balance+depositAmount)).grid(row=4,columnspan=5, pady=7)
This is my first app ever. It is working well but I would like to separate the UI concerns like getting input and creating labels, from the translation logic. I would then like to remove the output from the previous translation, i.e., only showing one translation on the screen at a time.
How can I separate the translation logic from my Tkinter GUI?
from Tkinter import *
import tkMessageBox
def start():
inputg = input.get()
if len(inputg) >= 2 and inputg.isalpha():
new_word_out = Label(text=(inputg[1:] + (inputg[0] + "ay")).lower().title()).pack()
out_message = Label(text="Cool! Try another!").pack()
# restart()
elif len(inputg) <= 1 and inputg.isalpha():
show_error(message="Whoops! I need 2 or more characters to translate! Try again!")
return
elif len(inputg) >= 1 and not inputg.isalpha():
show_error(message="Whoops! No numbers or symbols please! Try again!")
return
elif len(inputg) == 0:
show_error(message="It seems you haven't given me anything to translate!")
return
def show_error(message):
tkMessageBox.showerror(title="Error", message=message)
return
def quit():
ask_exit = tkMessageBox.askyesno(title="Quit", message="Are you sure you want to quit?")
if ask_exit > 0:
root.destroy()
return
root = Tk()
input = StringVar() # stores user input into this variable as a string.
root.title("The Pig Translator")
root.protocol("WM_DELETE_WINDOW", quit)
labeltitle1 = Label(text="Hello there! This is my Pig Latin Translator!").pack()
labeltitle2 = Label(text="Please enter a word to continue!", fg='darkgreen', bg='grey').pack()
original_entry = Entry(textvariable=input, bd=5, fg='darkgreen').pack()
translate_button = Button(text="Translate", command=start).pack()
root.bind('<Return>', lambda event: start()) # essentially binds 'Return' keyboard event to translate_button
root.mainloop()
There are many ways you can separate logic from GUI. generally I would recommend using classes and callback functions. Thus, I made a class that generates the gui. However, the translation is performed by external function called do_translation.
MyFrame does not know much about how do_translation. It only knows it returns translated_str, message and takes string as argument. do_translation does not relay on any gui as well. The do_translation takes only an input string, does what it wants, and returns translated string and message. The MyFrame take this function as a callback. You can make any other translation function, and as long as the input and output are same, it will work.
I rely here on a "Cool" in a massage which indicates that translation was ok. Its poor idea to make it relay on 'Cool' word, but did not want to change your code too much. Probably better to raise some error, or use message codes, etc.
from Tkinter import *
import tkMessageBox
class MyFrame(Frame):
def __init__(self, master, input_callback=None, **kwargs):
Frame.__init__(self, master)
self.set_input_callback(input_callback)
self.create_widgets()
self.pack()
def create_widgets(self):
self.input = StringVar() # stores user input into this variable as a string.
self.labeltitle1 = Label(text="Hello there! This is my Pig Latin Translator!")
self.labeltitle1.pack()
self.labeltitle2 = Label(text="Please enter a word to continue!", fg='darkgreen', bg='grey')
self.labeltitle2.pack()
self.original_entry = Entry(textvariable=self.input, bd=5, fg='darkgreen')
self.original_entry.pack()
self.translate_button = Button(text="Translate", command=self.start)
self.translate_button.pack()
self.new_word_out = Label(text='')
self.out_message = Label(text='')
def set_input_callback(self, some_fun):
self.input_callback = some_fun
def show_error(self, message):
tkMessageBox.showerror(title="Error", message=message)
return
def start(self):
inputg = self.input.get()
if self.input_callback:
translated_str, message = self.input_callback(inputg)
if 'Cool' in message:
self.new_word_out['text'] = translated_str
self.new_word_out.pack()
self.out_message['text'] = message
self.out_message.pack()
else:
self.show_error(message)
def do_translation(inputg):
translated_str = message = ''
if len(inputg) >= 2 and inputg.isalpha():
translated_str = (inputg[1:] + (inputg[0] + "ay")).lower()
message = "Cool! Try another!"
elif len(inputg) <= 1 and inputg.isalpha():
message = "Whoops! I need 2 or more characters to translate! Try again!"
elif len(inputg) >= 1 and not inputg.isalpha():
message = "Whoops! No numbers or symbols please! Try again!"
elif len(inputg) == 0:
message = "It seems you haven't given me anything to translate!"
return translated_str, message
def quit():
ask_exit = tkMessageBox.askyesno(title="Quit", message="Are you sure you want to quit?")
if ask_exit > 0:
root.destroy()
return
root = Tk()
root.title("The Pig Translator")
root.protocol("WM_DELETE_WINDOW", quit)
mf = MyFrame(root)
mf.set_input_callback(do_translation)
root.bind('<Return>', lambda event: start()) # essentially binds 'Return' keyboard event to translate_button
root.mainloop()
Hopefully this will be useful. I know, that there is not too much explanation what is happening here, but, don't have much time to write it. Your problem is very general.
Trying to get used to the tkinter gui but I'm running into a problem with setting up an input box. I wanted to make a simple number guessing program using Entry to input an integer and a button to submit the guess. I'm getting an str to int conversion error when I use int(GuessBox.get()) and I'm not sure what to do.
ValueError: invalid literal for int() with base 10: ''
from tkinter import *
import random
def makeAGuess():
guess = int(GuessBox.get())
print(guess)
if guess == Answer:
print("you got it!")
return False
elif guess > Answer:
print("Too High, try again")
return True
else :
print("Too low, try again")
return True
Answer = random.randint(1, 100)
main = Tk()
label = Label(main, text = "Guess a number")
label.pack()
GuessBox = Entry(master = main)
GuessBox.pack()
submitGuess = Button(master = main, text = "Submit Guess", command = makeAGuess())
submitGuess.pack()
main.mainloop()
You need to pass the function as an object, don't call it.
submitGuess = Button( master = main, text = "Submit Guess", command = makeAGuess )
Otherwise makeAGuess is called when the Button is created, but not passed any arguments.
With this change your code works perfectly for me.