I was wondering how you get the tkinter to notify the user if their input is invalid. When they input a negative integer or something that's not an integer, a dialog would pop up and say that their input is invalid. It would then let the user know and then it would let the user go back to the program. I've got the 2nd part working, but I'm getting some errors when I try to do the invalid input popup. It also pops up 2 windows as well which I have no idea why.
Code in question:
import tkinter
from tkinter import *
import tkinter as tk
class TimeConverterUI():
def __init__(self):
#main functions
self.root_window = Tk()
self.root_window.geometry('400x150')
self.root_window.title('Seconds Converter')
self.text()
self.quitValue=tk.Toplevel()
self.invalidinputDialog=tk.Toplevel()
self.calculate_button()
self.quit_button()
self.root_window.wait_window()
def text(self):
#label for seconds text along with the grid for it
row_label = tkinter.Label(
master = self.root_window, text = 'Seconds: ')
row_label.grid( row = 0,
sticky = tkinter.W)
self.secondsEntry = Entry(master = self.root_window)
self.secondsEntry.grid(row = 0, column = 1)
#label for converted time along with the grid
convert_label = tkinter.Label(
master = self.root_window, text = 'Converted Time(H:M:S): ')
convert_label.grid(row=1)
self.result = Entry(master= self.root_window)
self.result.grid(row = 1, column = 1)
def calculate_button(self):
#calculate button along with the placement
quit = Button(self.root_window, text = "Calculate", command = self.calculate)
quit.grid(row = 3, column = 0, columnspan = 3, pady=20,
sticky = tkinter.W)
def calculate(self):
try:
#divides the seconds into minutes
m,s = divmod(int(self.secondsEntry.get()),60)
#divides the minutes into hours and returns h:m:s format
h,m = divmod(m,60)
c= ("%d:%02d:%02d" % (h, m, s))
#after displaying the result, if the user wants to calculate again, deletes
#previous result and recalculates
self.result.delete(0,END)
self.result.insert(0,c)
except ValueError:
#if user enters an input that's not an integer, exception is placed
#d= 'Invalid Input'
self.invalidinputDialog()
def invalidinputDialog(self):
self.invalidValue = tk.Toplevel()
messageLabel = tk.Label(master=self.invalidValue,
text="Invalid Input.").grid(row=0,column=0)
invalidContinue = tk.Button(master=self.invalidValue, text='Close',
command = self.invalidValue.destroy).grid(row=1,column=1)
self.result.delete(0,END)
self.result.insert(0,d)
def quit_button(self):
#button for grid
quit = Button(self.root_window, text = "Quit", command = self.quitDialog)
quit.grid(row = 3, column = 3, columnspan = 3, pady=20,
sticky = tkinter.E)
def quitDialog(self):
self.quitValue = tk.Toplevel()
messageLabel = tk.Label(master=self.quitValue,
text="Are you sure you want to quit?").grid(row=0,column=0)
#closes both the main window and the message window
continueButton = tk.Button(master=self.quitValue,text='Continue',
command=self.root_window.destroy).grid(row=1,column=2)
#lets the user go back to previous screen if they cancel
cancelButton = tk.Button(master=self.quitValue,text='Cancel',
command=self.quitValue.destroy).grid(row=1,column=1)
def quit(self) -> bool:
#quits the program and shell is refreshed
self.root_window.destroy()
return True
if __name__ == '__main__':
convert=TimeConverterUI()
Here's where the problem lies.
def calculate(self):
try:
#divides the seconds into minutes
m,s = divmod(int(self.secondsEntry.get()),60)
#divides the minutes into hours and returns h:m:s format
h,m = divmod(m,60)
c= ("%d:%02d:%02d" % (h, m, s))
#after displaying the result, if the user wants to calculate again, deletes
#previous result and recalculates
self.result.delete(0,END)
self.result.insert(0,c)
except ValueError:
#if user enters an input that's not an integer, exception is placed
#d= 'Invalid Input'
self.invalidinputDialog()
def invalidinputDialog(self):
self.invalidValue = tk.Toplevel()
messageLabel = tk.Label(master=self.invalidValue,
text="Invalid Input.").grid(row=0,column=0)
invalidContinue = tk.Button(master=self.invalidValue, text='Close',
command = self.invalidValue.destroy).grid(row=1,column=1)
self.result.delete(0,END)
self.result.insert(0,d)
If you are looking to simply tell the user the input was invalid you can do
from tkinter import messagebox
messagebox.showinfo("Title", "Input invalid.")
Messagebox does need to be imported separately from tkinters main library.
That being said you need to import tkinter only once. You are currently importing tkinter 3 times with:
import tkinter
from tkinter import *
import tkinter as tk
instead you should use:
import tkinter as tk
you can use this for most of tkinter methods. You just need to use the prefix tk. on your widgets.
examples tk.Entry(), tk.Label(), tk.Text() and so on.
As for your extra blank windows that are opening they are coming from your __init__ portion of your class.
class TimeConverterUI():
def __init__(self):
self.quitValue=tk.Toplevel() # causing an empty toplevel window to open in init
self.invalidinputDialog=tk.Toplevel() # causing an empty toplevel window to open in init
You do not need to set up the toplevel ahead of time. You can simply create it in a method when you need one.
Related
I'm trying to take a number from inputtxt (which is Text widget) , I need to test the input ; if it's not a number the program should gives the user another chance to enter the correct input. I did this ; the problem is the warning message shows but the program proceeds . I need a way to give the user another chance without quitting the program.
import tkinter
from tkinter import *
from tkinter import ttk
from PIL import ImageTk, Image
from tkinter import messagebox
import subprocess
from tkinter import filedialog
window = Tk()
window.title("Test")
window.geometry('650x400')
frame = Frame(window)
# Gets both half the screen width/height and window width/height
positionRight = 350
positionDown = 100
# Positions the window in the center of the page.
window.geometry("+{}+{}".format(positionRight, positionDown))
inputtxt2 = Text(window, height = 1, width =24,bg = "light yellow")
inputtxt2.pack()
inputtxt2.place(x=325, y=108)
def run_button():
num_of_compare_points= inputtxt2.get('1.0', 'end-1c').strip().replace("\n","")
if not (num_of_compare_points.isnumeric()):
messagebox.showwarning("Warning","Please Enter a Valid Integer Value for Group Size!")
print (int(num_of_compare_points)+1)
runButton = tkinter.Button(window, text ="Run",font = "Times", command = run_button)
runButton.pack()
runButton.place(x=260, y=320)
window.mainloop()
It's not really clear what your problem is. If the data is invalid, you can just return from the function without doing anything.
def run_button():
num_of_compare_points= inputtxt2.get('1.0', 'end-1c').strip().replace("\n","")
if not (num_of_compare_points.isnumeric()):
messagebox.showwarning("Warning","Please Enter a Valid Integer Value for Group Size!")
return
print (int(num_of_compare_points)+1)
I was trying to create a QRcode generating app in python with tkinter that you enter color and then text and it generates a QRcode but it closes instantly upon startup. (the problem did not happen when I didn't have the color part), (in the future I'm also planning to add a save QR as png button)
here's what I have:
from tkinter import *
from tkinter import messagebox
import pyqrcode
from tkinter import colorchooser
import re
def choose_color():
x = 0
# variable to store hexadecimal code of color
color_code = colorchooser.askcolor(title ="Choose color")
print(color_code)
x = 1
root = Tk()
button = Button(root, text = "Select color",
command = choose_color)
button.pack()
if 'x' == 1:
str = 'color_code' # Your Hex
match = re.search(r'^#(?:[0-9a-fA-F]{3}){1,2}$', str)
if match:
print('Hex is valid')
ws = Tk()
ws.title("PythonGuides")
ws.config(bg='color_code')
def generate_QR():
if len(user_input.get())!=0 :
global qr,img
qr = pyqrcode.create(user_input.get())
img = BitmapImage(data = qr.xbm(scale=8))
else:
messagebox.showwarning('warning', 'All Fields are Required!')
try:
display_code()
except:
pass
def display_code():
img_lbl.config(image = img)
output.config(text="QR code of " + user_input.get())
lbl = Label(
ws,
text="Enter message or URL",
bg='color_code'
)
lbl.pack()
user_input = StringVar()
entry = Entry(
ws,
textvariable = user_input
)
entry.pack(padx=10)
button = Button(
ws,
text = "generate_QR",
width=15,
command = generate_QR
)
button.pack(pady=10)
img_lbl = Label(
ws,
bg='color_code')
img_lbl.pack()
output = Label(
ws,
text="",
bg='color_code'
)
output.pack()
ws.mainloop()
else:
print('Hex is not valid')
The reason the program closes immediately is because there is no root.mainloop(). Even if there was, there are a lot of other errors that would prevent the program from working.
The first problem is if 'x' == 1. Here you are comparing the literal string "x" to the number 1. These are never going to be equal, so the other window will never show up. I can see what you are trying to do with x, but it won't work as you expect. It is better just to get rid of x completely and call a function after the user selects a colour. I've called this function show_qr_window.
The second problem is how you get the hex code. You use the output of colorchooser.askcolor, which is a tuple containing an rgb tuple representing the colour and it's hex value. You only want the hex value, so you want color_code[1], as the hex value is the second item. I've also added an if statement to make sure color_code is not None. color_code will be None if the user does not choose a colour and just closes the window. If the user has chosen a colour, it is passed to show_qr_window. Because we've checked the user has chosen a colour, you can get rid of all of the other validation as colorchooser will always return a valid hex value. You also no longer have to import re.
The third issue is that you've used Tk twice. This will cause your program to not work properly. Instead, change ws = Tk() to ws = Toplevel().The next issue is str = 'color_code'. This is not how you define a variable. You want to do color_code = 'a string'. In this case, the string is passed to show_qr_window as the variable color, so you can use color_code = color. You also have to change all of the bg = 'color_code' to bg = color_code, as color_code is a variable, not a string. The rest of your code seems to work (I haven't tested the qr code generation as I don't have that module installed). Here is the code with all of the fixes:
from tkinter import *
from tkinter import messagebox
import pyqrcode
from tkinter import colorchooser
def choose_color():
# variable to store hexadecimal code of color
color_code = colorchooser.askcolor(title ="Choose color")
if color_code != None:
show_qr_window(color_code[1])
root = Tk()
button = Button(root, text = "Select color",
command = choose_color)
button.pack()
root.mainloop()
def show_qr_window(color):
ws = Toplevel()
color_code = color
ws.config(bg = color_code)
def generate_QR():
if len(user_input.get())!=0 :
global qr,img
qr = pyqrcode.create(user_input.get())
img = BitmapImage(data = qr.xbm(scale=8))
else:
messagebox.showwarning('warning', 'All Fields are Required!')
try:
display_code()
except:
pass
def display_code():
global img
img_lbl.config(image = img)
output.config(text="QR code of " + user_input.get())
lbl = Label(
ws,
text="Enter message or URL",
bg=color_code
)
lbl.pack()
user_input = StringVar()
entry = Entry(
ws,
textvariable = user_input
)
entry.pack(padx=10)
button = Button(
ws,
text = "generate_QR",
width=15,
command = generate_QR
)
button.pack(pady=10)
img_lbl = Label(
ws,
bg=color_code)
img_lbl.pack()
output = Label(
ws,
text="",
bg=color_code
)
output.pack()
ws.mainloop()
Assuming generate_QR() and display_code() work as they should (I haven't tested them but they look fine), this code will run as expected.
I want to write a program where after a user enters text and clicks a button, the text becomes a label and the button text is changed. My code is:
# Imports
import os, sys
import tkinter
"""
Tkinter program 1
text box + button + label
"""
# Button Entry
def enter(inputtedinfo, randvar, EnterMessage):
randvar = inputtedinfo.get()
EnterMessage = "Submitted!"
# Main Function
def main():
something = tkinter.Tk()
something.title("My First Tkinter Window")
something.geometry("600x400")
randvar = ""
EnterMessage = "Enter"
inputtedinfo = tkinter.StringVar()
userLabel = tkinter.Label(something, text = randvar)
userEntry = tkinter.Entry(something, textvariable = inputtedinfo)
userButton = tkinter.Button(something, text = EnterMessage, command = enter(inputtedinfo, randvar, EnterMessage))
userEntry.grid(row=0,column=0)
userLabel.grid(row=0,column=1)
userButton.grid(row=0,column=2)
something.mainloop()
sys.exit(0)
if(__name__ == "__main__"):
main()
The user input works, but clicking the button does nothing despite the fact that it is supposed to change the variables for the button and label displays. Did I mess up somewhere?
The command argument takes the name of a function. If you write the complete call with arguments, it's not the name of the function but whatever is returned by this exact function call. So, your button will not work. It will have the command None.
In order to do what you want to do, you have to make the StringVar()s accessible to the function you are calling. So, you can both get the contents of the entry and change the values of the button and the label. To do this, best add the string variables and the widgets as attributes to the toplevel you already created (something). So, they stay available to all functions and you can get and change information:
# Button Entry
def enter():
something.randvar.set(something.inputtedinfo.get())
something.userButton["text"] = "Submitted!"
# Main Function
def main():
global something
something = tkinter.Tk()
something.title("My First Tkinter Window")
something.geometry("600x400")
something.randvar = tkinter.StringVar()
something.randvar.set("")
EnterMessage = "Enter"
something.inputtedinfo = tkinter.StringVar()
userLabel = tkinter.Label(something, textvariable = something.randvar)
something.userEntry = tkinter.Entry(something, textvariable = something.inputtedinfo)
something.userButton = tkinter.Button(something, text = EnterMessage, command = enter)
something.userEntry.grid(row=0,column=0)
userLabel.grid(row=0,column=1)
something.userButton.grid(row=0,column=2)
something.mainloop()
if(__name__ == "__main__"):
main()
There are few issues in your code:
assign string to textvariable, should use StringVar instead
command=enter(...) will execute enter(...) immediately and then assign None to command option, should use lambda instead
updating strings inside enter() does not automatically update the label and the button, should use .set() on the StirngVar instead
Below is modified code:
def enter(inputtedinfo, randvar, EnterMessage):
# used .set() to update StringVar
randvar.set(inputtedinfo.get())
EnterMessage.set("Submitted!")
def main():
something = tkinter.Tk()
something.title("My First Tkinter Window")
something.geometry("600x400")
randvar = tkinter.StringVar() # changed to StringVar()
EnterMessage = tkinter.StringVar(value="Enter") # changed to StringVar()
inputtedinfo = tkinter.StringVar()
userLabel = tkinter.Label(something, textvariable=randvar) # used textvariable instead of text option
userEntry = tkinter.Entry(something, textvariable=inputtedinfo)
userButton = tkinter.Button(something, textvariable=EnterMessage, command=lambda: enter(inputtedinfo, randvar, EnterMessage))
userEntry.grid(row=0,column=0)
userLabel.grid(row=0,column=1)
userButton.grid(row=0,column=2)
something.mainloop()
I have three widgets (shown below) - a label, an entry, and a button.
I'm hoping to accept user input of an integer when the button is clicked.
I get the following error: ERROR RESOLVED!
I was making this too complicated. Success is Mine!!!
I believe that my problem is related to this:
Stack Overflow Post on get()
I chopped it down to the minimum amount of code possible to achieve this before posting.
Any Ideas or obvious failures you see would be greatly appreciated.
Screenshot of screen #1 (should eventually progress to screen #2 after all the players are entered. Screen #2 is where the game will begin.):
Working Code:
from tkinter import *
import tkinter
from PIL import Image, ImageTk
# The next 2 imports override the basic Tk widgets
# widget options that use fg and bg will no longer work.
# Must use, instead, ttk.Style class
from tkinter import ttk
from tkinter.ttk import *
# See docs.python.org/8/library/tkinter.ttk.html
# Class Based Windows
# youtube.com/watch?v=RkaekNkIKNY
# Tkinter - GUI Eaxample Multiple Display Frames
# youtube.com/watch?v=KdoOm3xo8X0
def main():
root = tkinter.Tk()
window1 = Window(root, "Play_a_game_of_Pig", "969x690", "someMessage",
"pig.ico", 0)
return None
class Window:
number_of_players = 0
# int player_names[] = new player_names[number_of_players]
def __init__(self, root, title, geometry, message, iconbitmap,
playerNumber):
self.root = root # this is the instance variable for the entire window
self.root.title(title)
self.root.geometry(geometry)
self.root.iconbitmap(iconbitmap)
self.number_of_players = playerNumber
# Make the window show up in center of screen and not be ccovered up by anything else.
# self.root.eval('tk::PlaceWindow %s center' % self.root.wininfo_toplevel())
# this is an important line.
# self.root.mainloop()
def addPlayers():
"""Allows user to input the number of and names of all game players"""
# stackoverflow.com/questions/12169258/should-i-use-entrys-get-or-its-textvariables-for-tkinter-in-python
print("\n initial # of players = " + str(self.number_of_players))
# Collects user input from the entry and turns it into an int
# user_input_number_of_players.set(int(str(entry_player_number.get("1.0", 'end-1c'))))
try:
user_input_number_of_players = int(entry_player_number.get())
print("Inside try block, user_input = ")
print(user_input_number_of_players)
self.number_of_players = user_input_number_of_players
except ValueError:
tkinter.messagebox.showerror('Non-Integer Input', 'User MUST enter a player # greater than 1.', icon = 'error')
# tkinter.messagebox.deiconify()
# tkinter.messagebox.quit()
# tkinter.messagebox.destroy()
#user_input_number_of_players.set(int(str(entry_player_number.get("1.0", 'end-1c'))))
# Set class instance value to this input from the user
# self.number_of_players = user_input_number_of_players
print("# of players after click = " + str(self.number_of_players))
return self.number_of_players
print("# of players after click = " + str(self.number_of_players))
# Add a label
myLabel1 = tkinter.Label(self.root, text="Please Enter # of Players",
width=25)
myLabel1.config(font="Courier 14 bold")
myLabel1.grid(row=2, column=1)
# bind user input to a variable from the entry box.
# Specifies a name whose value is linked to the widget value.
user_input_number_of_players = tkinter.StringVar()
# add an entry box
entry_player_number = tkinter.Entry(self.root, width=5, borderwidth=5,
textvariable=user_input_number_of_players)
# number.set(int("0"))
entry_player_number.grid(row=2, column=3, rowspan=2)
# specify a default value inside the entry box
# entry_player_number.insert(0,int("2"))
# Add a button for adding players to the game
addPlayerButton = tkinter.ttk.Button(self.root,
text="Enter",
command=addPlayers)
addPlayerButton.grid(row=2, column=4)
self.root.mainloop()
pass
pass
main()
In Summary, I first made two screens. I'm hoping to make these into classes. The first screen (pictured) should get input from the user. Then, the first screen should progress to the second screen.
To do this, I have used the following resources:
Resources for merging this code together into Multiple class based windows and the backend:
Python: Tkinter Class Based Windows
Tkinter - GUI Example Multiple Display Frames
I am currently writing a script in python that takes in user data at the beginning of the script which may need to be updated later.
The initial user data is input through a tkinter window which is then passed along to the lower functions. Later in the script, if the information is detected to be bad, I want to alert the user that the info was bad and prompt them to re-input the data without having to start the program from the beginning.
I was attempting to achieve this by adding in a sub window function that would be called whenever the data needed to be re-input, take the new user input, and then pass it up back up to the function that called it. The code below roughly shows what I'm trying to do:
import tkinter as tk
from tkinter import *
def gui():
window = tk.Tk()
window.geometry('300x200')
L1 = tk.Label(window, text = 'This is a test')
L1.grid(column = 1, row = 0)
L2 = tk.Label(window, text = 'Token')
L2.grid(column = 0, row = 1)
E1 = tk.Entry(window, width = 25)
E1.grid(column = 1, row = 1)
B1 = tk.ttk.Button(window, text = 'Run', command = lambda: shell(window, E1.get()))
B1.grid(column = 1, row = 2)
window.mainloop()
def shell(window, val):
print('Old Val:', val)
val = subwindow_test(window)
print('New Val:', val)
def subwindow_test(window):
def subwinfunc(window, val):
if val == None or val == '':
print('Enter something')
else:
window.sub_win.destroy()
return
window.sub_win = tk.Toplevel(window)
window.sub_win.geometry('300x200')
L1 = tk.Label(window.sub_win, text = 'this is a subwindow')
L1.grid(column = 1, row = 0)
L2 = tk.Label(window.sub_win, text = 'New Token')
L2.grid(column = 0, row = 1, sticky = 'E')
var = StringVar()
E1 = tk.Entry(window.sub_win, width = 25, textvariable = var)
E1.grid(column = 1, row = 1)
B1 = tk.ttk.Button(window.sub_win, text = 'Return', command = lambda: subwinfunc(window, var.get()))
B1.grid(column = 1, row = 2)
window.sub_win.mainloop()
return var.get()
gui()
The idea is to pass the window down to the subwindow_test function, spawn a sub window using tk.Toplevel, ask the user for new data, then destroy the sub window and pass the newly entered data back up to the calling function.
In theory, this would prevent me from having to restart the code from the beginning as this subwindow_test function could be run from anywhere in the code.
The issue is that after subwinfunc returns after destroying window.sub_win, the code hangs until the original window object (the one created in the gui function) is closed. Also, removing the return line from subwinfunc does not change this.
Is there a way to get around this issue?
I have tried using a separate window (An entirely different window, not a sub window of the one created in gui), but the same problem comes up.
It is also not possible, as far as I can tell, to pass the sub window object back up to the calling function and close it there, as subwindow_test cannot return until it breaks from window.sub_win.mainloop() (If the return comes before the mainloop(), the window will never appear) .
Additionally, the only way that I could find to get the value to return at all is to use a StringVar. I would rather try and avoid using global variables, and if I had to guess, I would say that the return val.get() is most likely the root of the problem. However because I can't find another way to pass variables up from this function, I'm stumped.
You should not be calling mainloop more than once. Tkinter provides the ability to wait for a window to be closed before continuing with the wait_window method.
Here is a very simple example that shows how to create a popup dialog that won't return until the user clicks the ok or cancel button.
def get_input():
value = None
def do_ok(event=None):
nonlocal value
value = entry.get()
top.destroy()
def do_cancel():
nonlocal value
value = None
top.destroy()
top = tk.Toplevel()
entry = tk.Entry(top)
ok = tk.Button(top, text="ok", command=do_ok)
cancel = tk.Button(top, text="cancel", command=do_cancel)
entry.bind("<Return>", do_ok)
entry.pack(side="top", fill="x")
ok.pack(side="right")
cancel.pack(side="left")
top.wait_window(top)
return value