how do i use my validation in a tkinter environment? - python

from tkinter import *
window = Tk()
window.title("Registration")
window.configure(background="blue")enter code here
Label (window, text= "Firstname: ", bg="blue", fg="white", font="verdana 12 bold") .grid(row=0, sticky=E)
firstname = Entry(window, width=100, bg="white")
firstname.grid(row=0, column=1, sticky=W)
firstname = firstname.get()
firstname = firstname.strip()
firstname = firstname.lower()
Label (window, bg = "blue") .grid(row=2)
Label (window, text= "Surname: ", bg="blue", fg="white", font="verdana 12 bold") .grid(row=3, sticky=E)
surname = Entry(window, width=100, bg="white")
surname.grid(row=3, column=1, sticky=W)
surname = surname.get()
surname = surname.lower()
surname = surname.strip()
Label (window, bg = "blue") .grid(row=4)
Label (window, text = "Pick a number between 0 and 10: ", bg="blue", fg="white", font = "verdana 12 bold") .grid(row=5, sticky=E)
number = Entry(window, width=100, bg="white")
number.grid(row=5, column=1)
while True:
try:
number = number.get()
if (number > 10) or (number < 0): 1/0
except:
print("Sorry, your response must be a number between 0 and 10")
continue
break
window.mainloop()
This is my code so far. I am trying to create a registration system for a quiz I'm making, however, now that I am dealing with GUI, I don't know how to use my validation code in the GUI way/environment. for example, just having "print("sorry, your response must be a number between 0 and 10")" won't and isn't working with my program.
My question: How do I output a message into a textbox like an error message and how do I implement my validation code?
Also, I made my validation code onths ago when I was new to python and used a stack overflow piece of code to help apply it to my program. Anyway, could someone help explain how this code actually works. I don't seem to understand it now and my teacher has struggled to explain it in an understandable way. Specifically the : 1/0 bit. I'm not used to using try and except, I only know how to use for and while loops usually.
Many thanks

Showing messages in tkinter (1st question)
To show the user basic massages and get basic options (show an error, ask OK/Cancel or yes/no...) you can use tkinter.messagebox. It provides show*() and ask*() functions. In your case, showerror() or showwarning() is probably best.
To get basic input, tkinter.simpledialog can be used. It provides the functions askinteger, askfloat and askstring that will ask the user for the respective data type.
To get file (path) input, use tkinter.filadialog.
For more complex situations, it is best you use the tkinter.Toplevel widget.
2nd question
Your code
I am going to play interpreter and go through your code. If you just want the solutions (not recommended), jump below.
firstname = Entry(...) => create an Entry widget and assign it to firstname
firstname.grid(...) => put the widget in/on the window
firstname = firstname.get() => get the text currently in the widget and assign it to firstname.
OK, you want to get the text. Just, the window isn't even visible yet. These instructions will work in the IDLE shell, because of special reasons and you wait to call .get() until you typed your name. In "real" execution, the interpreter won't wait and your user can't type (because there isn't a window) before you call .mainloop(). One solution, if you read above, is to use simpledialog. But also this should run after the GUI started, i.e. after .mainloop() is called. I'll get to that part later.
-- same for surname --
Your validation
Interpreter:
number = Entry(...) => create a new Entry widget and assign it to number
number.grid(...) => show it
# while True here
# try here
number = number.get() => call number.get() and assign the value (a str) to number -- keep that in mind
# if condidtion below
number > 10 => check if a str is larger/greater than an int; can't do that, raise a TypeError
# error -> go to the except
print("I asked how to do this on SO") => show this (in the console); in future hopefully via messagebox
continue => go to the beginning of the loop
# in try
number = number.get() => call the .get() method of the str number; can't find it, raise an AttributeError
# error -> go to the except
print(...) => as before
continue => as before
You get caught in an infinite loop of exception that won't stop even if the user enters a correct number (which can't happen anyway, we don't have a window yet). This is a very good reason for avoiding a bare except - you will also catch lots of stuff you don't want.
Why the method you are trying to use wooooould work (you said you found it here -- do you still have a link or remember the title?):
Code (this example in the console for simplicity):
while True:
try:
value = int(input())
if not 0<value<10:
1/0
except ZeroDivisionError: # let ValueError through (you willl want to catch it separately)
print('by executing 1/0, I raised a ZeroDivisionError. This code therefore will execute.')
else:
print('Everything did fine. I will exit the loop to continue.')
break
Interpreter:
# loop
# in try
value = int(input()) => read input and try to convert to an int; we assume this doesn't fail.
# if condition
not 0<value<10 => is value not between 0 and 10? (we assume it isn't)
# if block
1/0 => what?!? I can't do that, I'll raise a ZeroDivisionError
# except block
print(...) => show the text
# loop again
# in try
value = int(input()) => as above
# if condition
not 0<value<10 => we assume value is between 0 and 10 (evaluetes to False)
# else part of the try...except...else
print(...) => show text
break => exit the loop
You intentionally perform 1/0, which will raise a ZeroDivisionError and act on that in the except. Since you said you don't usually do it, I recommend you try to understand what it does.
How to do it better
Make the window appear before expecting user input: Put all code that should execute at application start in a function and either delay it with tkinter.Tk.after (window.after) or add a nice "Start!"
Button.
Don't (ab)use exceptions when a simple if will do (if you really want to (show off), define your own class MyFancyException(Exception): pass)
Look up on concepts you don't understand before using them and insert comments to remind you if something is so complicated you're afraid you won't remember later.
.
import tkinter as tk
from tkinter.simpledialog import askstring, askinteger
from tkinter.messagebox import showwarning
def do_stuff(first_name, surname, number):
...
def start():
# if you want to use Entry, add a "Submit" Button
first_name = askstring('Title', 'first name:')
surname = askstring('Title', 'last name:')
while True: # ask* return None when cancel is pressed
number = askinteger('Title', 'insert a number between 0 and 10:')
if number is not None and 0<number<10: # what we want
break
do_stuff(first_name, surname, number)
# GUI preparation code
window = tk.Tk()
button_start = tk.Button(window, text='Start!', command=start)
button_start.pack() # for use with other widgets (that use grid), you must .grid() here
window.mainloop() # the GUI appears at this moment

Related

How to validate a Tkinter entry widget to only accept string?

I am trying to work my way through Tkinter and this is a part of my code:
FirstName = Label(canvas, text="First Name")
FirstName.configure(width=30, bg="white", fg="black", border=10)
FirstName = canvas.create_window(330, 130, anchor = CENTER, window=FirstName)
FName_Entry = Entry(canvas)
canvas.create_window(850, 145, window=FName_Entry, height=35, width=300)
As you can see this is an entry widget for users to enter their first name.
how can I validate this to only accept string (letters) and if they try to enter integers, symbols or basically anything that is not a letter, it should display a message on the side of the widget urging users to enter a valid name.
I tried to check online but most of them are using classes and I am not used to classes as of yet and am new to Tkinter. other examples explain how to limit entry to integers so I am a bit confused here.
Thanks for helping!
Here is a small snippet to actually make you understand better
from tkinter import *
from tkinter import messagebox
root = Tk()
def check():
sel = e.get()
if not sel.isalpha():
messagebox.showerror('Only letters','Only letters are allowed!')
e = Entry(root)
e.pack(pady=10)
b = Button(root,text='Click Me',command=check)
b.pack(padx=10,pady=10)
root.mainloop()
Here we are checking if sel.isalpha() returns False or not, if it does, then show a messagebox saying only letters are allowed. Simple as that.
Do let me know if any errors. Happy coding
Here is more on isalpha() method
Cheers
You can use list in which you can store the letter which is to be accepted.
Then check the each letter of the input with the element in the list.
If any character not found from the input in the list(acceptable character) then it is invalid input.
# acceptable character list
accepted_characters = ['a', 'b', 'c',.....'z', 'A', 'B', 'C',...'Z']
# input from the tkinter entry widget
inp = "hello"
for i in inp:
if i not in accepted_characters:
print('Invalid data.')
Another way is using RegEx module which is built-in module. But I am not too familiar with RegEx.

Creating an Entry Widet with Limited Input

In an attempt to create a Python Entry widget with limited input(3 or 4 characters), I found this
Knowing nothing yet about validation, my question is this: can the 'subclass' for max length in that tutorial be used as its own class, referencing the entry widget as its parent instead of 'ValidatingEntry', or is all the legwork above it (validating) necessary? Is there any shorter way to accomplish this?
Then I saw this question and its answer:
Considering doing something like that. Then I discovered the builtin 'setattr' function. Is it possible to apply this to a new instance of the class 'Entry' and use it to limit characters?
I should clarify- I'm trying to apply this limit to 3 entry widgets- two with a 3 character limit and one with a 4 character limit (a phone number)
Thanks.
Regarding your stated concern of length validation in an Entry widget. An example of input length validation.
# further reference
# http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/entry-validation.html
import tkinter as tk
root = tk.Tk()
# write the validation command
def _phone_validation(text_size_if_change_allowed):
if len(text_size_if_change_allowed)<4:
return True
return False
# register a validation command
valid_phone_number = root.register(_phone_validation)
# reference the validation command
entry = tk.Entry(validate='all',
validatecommand=(valid_phone_number,'%P' ))
entry.pack()
root.mainloop()
suggestion in response to comment on focus switching between entry widgets, change validate 'all'-> 'key', and use focus_set()
import tkinter as tk
root = tk.Tk()
# write the validation command
def _phone_validation(text_size_if_change_allowed):
if len(text_size_if_change_allowed) == 3:
entry2.focus_set()
if len(text_size_if_change_allowed)<4:
return True
return False
# register a validation command
valid_phone_number = root.register(_phone_validation)
# reference the validation command
entry = tk.Entry(validate='key',
validatecommand=(valid_phone_number,'%P' ))
entry2 = tk.Entry()
entry.pack()
entry2.pack()
root.mainloop()

Using functions within a GUI - how to pass and return variables to widgets

SOLVED - answer found # How to take input from Tkinter
ONE GOTCHA - I tried copying the example's style in the solved link, though I kept some of my own formating. I found that if the object that is getting the returned value has been created with any other dot added to it, for example putting .pack() or in my case .grid() on the same line as the object is created, it will throw out an error when you try to pass the variable back. So, go ahead and use a second line to .pack() or .grid().
ORIGINAL POST
I've got a program that works fine on the command line and I am attempting to give it a GUI. Unfortunately, I'm still pretty confused about how to use functions / methods / commands inside the interface.
My example is to use a button to get everything rolling after getting input from the user. In the command line program, it was a user input passed as a variable to one function and return three other variables. Those three variables get passed to another function to provide a result, and then the result is formatted to display.
Well, I'm attempting to use tkinter. I've got an entry field which assigns the user input to a variable. I've got a button linked to a function that is meant to start the ball rolling... and I do not know how to send that variable to the needed function(s), or how to take the returned variable and apply it. In my specific example, I want to send the variable "notation" to the function "parse()", the output of "parse()" would be sent to "roll()", and the output of "roll()" sent to the variable "output" and then displayed. All this would be started using the button with "command=calculate", with "calculate()" being a function that gets the entire ball rolling.
I am so very sorry... I am entirely self taught, and I am sure that I am not even using the right terminology for a lot of this. I also understand that this example is not very pythonic - I will eventually put all of this into classes and change the functions to methods, but for now I just want to see it work.
Here is the code so far. Fair warning, this is not the only problem I am having... I just want to stick to one question until I can get it worked out.
#!/usr/bin/env python
from tkinter import *
from tkinter import ttk
import re
import random
# Parsing function from the original command line tool
# Turns dice notation format into useful variables
def parse(d):
dice, dtype_mod = d.split('d')
dnum = 1
dtype = 6
mod = 0
if dtype_mod:
if '-' in dtype_mod:
dtype, mod = dtype_mod.split('-')
mod = -1 * int(mod)
elif '+' in dtype_mod:
dtype, mod = dtype_mod.split('+')
mod = int(mod)
else:
dtype = dtype_mod
if not dtype: dtype = 6
if not mod: mod = 0
return (int(dice), int(dtype), int(mod))
# Rolling function from the original command line tool
# 'print()' will be changed into a variable, with the output
# appended in the working version.
def roll(a, b):
rolls = []
t = 0
for i in range(a):
rolls.append(random.randint(1, b))
t += int(rolls[i])
print(('Roll number %d is %s, totaling %d') % (i + 1, rolls[i], t))
return (int(t))
# Placeholder - the rest of the command line code will be used here later
# This code will be what starts everything rolling.
# For debugging, attempting to pass a variable, doing it wrong.
def calculate():
output = "this is something different"
return output
# Initialize
dice = Tk()
dice.title('Roll the Dice')
dice.geometry("800x600+20+20")
# Drawing the main frame
mainframe = ttk.Frame(dice, padding="3 3 12 12")
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)
# Variables needed for program and widgets
random.seed()
notation = ""
output = """This is
an example of a lot of crap
that will be displayed here
if I ever get
this to work
and this is a really long line -Super Cali Fragil Istic Expi Ali Docious
"""
# Dice notation entry field, and it's label
notation_entry = ttk.Entry(mainframe, width=10, textvariable=notation)
notation_entry.grid(column=2, row=1)
ttk.Label(mainframe, text="Dice").grid(column=1, row=1)
# Section used for output
"""Huge laundry list of problems here:
1. textvariable is not displaying anything here. If I change it to text
it seems to work, but from what I can tell, that will not update.
2. I would love for it to have a static height, but height is not allowed here.
Need to figure out a workaround.
3. Also, have not figured out how to get the value returned by calculate()
to show up in here when the button is pressed..."""
output_message = Message(mainframe, textvariable=output, width= 600)
output_message.grid(column=1, row=2, rowspan=3, columnspan=3)
# The 'make it go' button.
"""Can I pass the function a variable?"""
ttk.Button(mainframe, text="Roll!", command=calculate).grid(column=3, row=5)
# This is a bunch of stuff from the command line version of this program.
# Only here for reference
"""while True:
notation = raw_input('Please input dice notation or q to quit: ')
if notation == "q":
raise SystemExit
else:
print(notation)
numbers = parse(notation)
(dice, dtype, mod) = numbers
total = roll(dice, dtype)
total += mod
print('Your total is %d' % total)"""
dice.mainloop()
When you are using textvariable when creating an object a string variable needs to be used instead of a normal string variable,i changed your variable notation to a string variable
notation = StringVar()
notation.set("")
Then to get the value of notation in your calculation sub i used
print(notation.get())
Your code that i have edited is pasted in full beneth
#!/usr/bin/env python
from tkinter import *
from tkinter import ttk
import re
import random
# Parsing function from the original command line tool
# Turns dice notation format into useful variables
def parse(d):
dice, dtype_mod = d.split('d')
dnum = 1
dtype = 6
mod = 0
if dtype_mod:
if '-' in dtype_mod:
dtype, mod = dtype_mod.split('-')
mod = -1 * int(mod)
elif '+' in dtype_mod:
dtype, mod = dtype_mod.split('+')
mod = int(mod)
else:
dtype = dtype_mod
if not dtype: dtype = 6
if not mod: mod = 0
return (int(dice), int(dtype), int(mod))
# Rolling function from the original command line tool
# 'print()' will be changed into a variable, with the output
# appended in the working version.
def roll(a, b):
rolls = []
t = 0
for i in range(a):
rolls.append(random.randint(1, b))
t += int(rolls[i])
print(('Roll number %d is %s, totaling %d') % (i + 1, rolls[i], t))
return (int(t))
# Placeholder - the rest of the command line code will be used here later
# This code will be what starts everything rolling.
# For debugging, attempting to pass a variable, doing it wrong.
def calculate():
print(notation.get())
output = "this is something different"
return output
# Initialize
dice = Tk()
dice.title('Roll the Dice')
dice.geometry("800x600+20+20")
# Drawing the main frame
mainframe = ttk.Frame(dice, padding="3 3 12 12")
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)
# Variables needed for program and widgets
random.seed()
notation = StringVar()
notation.set("")
output = """This is
an example of a lot of crap
that will be displayed here
if I ever get
this to work
and this is a really long line -Super Cali Fragil Istic Expi Ali Docious
"""
# Dice notation entry field, and it's label
notation_entry = ttk.Entry(mainframe, width=10, textvariable=notation)
notation_entry.grid(column=2, row=1)
ttk.Label(mainframe, text="Dice").grid(column=1, row=1)
# Section used for output
"""Huge laundry list of problems here:
1. textvariable is not displaying anything here. If I change it to text
it seems to work, but from what I can tell, that will not update.
2. I would love for it to have a static height, but height is not allowed here.
Need to figure out a workaround.
3. Also, have not figured out how to get the value returned by calculate()
to show up in here when the button is pressed..."""
output_message = Message(mainframe, textvariable=output, width= 600)
output_message.grid(column=1, row=2, rowspan=3, columnspan=3)
# The 'make it go' button.
"""Can I pass the function a variable?"""
ttk.Button(mainframe, text="Roll!", command=calculate).grid(column=3, row=5)
# This is a bunch of stuff from the command line version of this program.
# Only here for reference
"""while True:
notation = raw_input('Please input dice notation or q to quit: ')
if notation == "q":
raise SystemExit
else:
print(notation)
numbers = parse(notation)
(dice, dtype, mod) = numbers
total = roll(dice, dtype)
total += mod
print('Your total is %d' % total)"""
dice.mainloop()

Python Tkinter label widget doesn't update

Here's a piece of simplified code which doesn't work as i want it to:
def get_tl(self,x):
self.var_tl = IntVar()
if x == "Random (max = 6)":
self.var_tl.set(randint(1,6))
else:
ask_tl = Toplevel()
def destroy_t_set_tl():
self.var_tl.set(entry_tl_t.get())
ask_tl.destroy()
label_tl_t = Label(ask_tl, text="length:").pack(side=LEFT)
entry_tl_t = Entry(ask_tl, width=25)
entry_tl_t.pack(side=LEFT)
button_enter_tl_t = Button(ask_tl, text="Enter", command=destroy_t_set_tl).pack(side=LEFT)
self.label_tl = Label(self, text="length:").grid(row=1,column=0)
# This only shows the right number when "Random (max = 6)". When "Manual" it shows 0
self.show_tl = Label(self, text=self.var_tl.get()).grid(row=1,column=1)
def get_values(self):
# This always shows the right number.
self.total_label = Label(self, text=self.var_tl.get()).grid(row=4,column=0)
The function get_tl is called by an OptionMenu widget which gives x the values: "Manual" or "Random (max = 6)".
When this function is called I want it to choose a random number or open a Toplevel window which ask the user a number through an Entry. After the random number is chosen or the user has given a number. The number needs to be displayed as a label so the user can see if the number is correct.
The label only show the right number when "Random (max = 6)". When "Manual" it shows 0
After a button is pressed the function get_values is called. This however does give the right number regardless if it is manual or random.
I'm probably making a simple mistake here. But I fail to see it.
In this part:
def get_tl(self,x):
self.var_tl = IntVar()
You're recreating the variable over and over again, so it holds the default value of 0, as explained in the documentation:
VALUE is an optional value (defaults to 0)
Then you set the variable only if x == "Random (max = 6)", so in all other cases it will remain at its default.
Possibly you want to remove this line:
self.var_tl = IntVar()
You should have it only in the constructor of your class. Then all your methods will share the same instance pointed by self.var_tl.
This seems to be the answer to my own question:
def get_tl(self,x):
def tl():
self.label_tl = Label(self, text="length:").grid(row=1,column=0)
self.show_tl = Label(self, text=self.var_tl.get()).grid(row=1,column=1)
if x == "Random (max = 6)":
self.var_tl.set(randint(1,6))
tl()
else:
ask_tl = Toplevel()
def destroy_t_set_tl():
self.var_tl.set(entry_tl_t.get())
ask_tl.destroy()
tl()
label_tl_t = Label(ask_tl, text="length:").pack(side=LEFT)
entry_tl_t = Entry(ask_tl, width=25)
entry_tl_t.pack(side=LEFT)
button_enter_tl_t = Button(ask_tl, text="Enter", command=destroy_t_set_tl).pack(side=LEFT)
def get_values(self):
self.total_label = Label(self, text=self.var_tl.get()).grid(row=4,column=0)
Now both the option "Manual" and "Random" will call the function tl() which will show the number so the user can check it.
I also moved the self.var_tl = IntVar() to the constructor of the class. It might not be the optimal solution but for me it works.

Combined Event Handling of widgets in TKinter

I am making a GUI Program in Tkinter and am running into problems.What I want to do is draw 2 checkboxes and a button. According to the user input next steps should take place. A part of my code has been shown below :-
CheckVar1 = IntVar()
CheckVar2 = IntVar()
self.C1 = Checkbutton(root, text = "C Classifier", variable = CheckVar1, onvalue = 1, offvalue = 0, height=5,width = 20).grid(row=4)
self.C2 = Checkbutton(root, text = "GClassifier", variable = CheckVar2, onvalue = 1, offvalue = 0, height=5, width = 20).grid(row=5)
self.proceed1 = Button(root,text = "\n Proceed",command = self.proceed(CheckVar1.get(),CheckVar2.get())).grid(row=6)
# where proceed prints the combined values of 2 checkboxes
The error that I am getting is typical ie a default value of both the selected checkboxes gets printed up and then there is no further input. The error that I get is NullType Object is not callable.
I searched on the net and I think the answer is related to lambda events or curry.
Please help ..
You're passing the value of self.proceed(CheckVar1.get(),CheckVar2.get()) to the Button constructor, but presumably what you want is for command to be set to a function which will call self.proceed(CheckVar1.get(),CheckVar2.get()) and return a new, possibly different value every time the button is pressed. You can fix that with a lambda, or by wrapping the call in a short callback function. For example, replace the last line with:
def callback():
return self.proceed(CheckVar1.get(), CheckVar2.get())
self.proceed1 = Button(root, text="\n Proceed", command=callback).grid(row=6)
This is pretty typical Tkinter. Remember: when you see a variable called command in Tkinter, it's looking for a function, not a value.
EDIT: to be clear: you're getting 'NullType Object is not callable' because you've set command to equal the return value of a single call to self.proceed (that's the NullType Object). self.proceed is a function, but its return value is not. What you need is to set command to be a function which calls self.proceed.
Like Peter Milley said, the command option needs a reference to a function (ie: give it a function name (ie: no parenthesis). Don't try to "inline" something, create a special function. Your code will be easier to understand and to maintain.

Categories

Resources