I decided to build a GUI that would seemlessly create user interfaces for validation
functions, functions that check if a string satisfies given rules.
An example 'hello, world!' usage is:
from gui_validation import gui_validation
def is_valid_salutation(salutation):
"""
Salutations to be valid must start with one of:
['hello', 'hi', 'howdy'] + ',' [COMMA]
and must end with '!' [EXCLAMATION MARK]
>>> is_valid_salutation('howdy, moon!')
True
>>> is_valid_salutation('random phrase')
False
"""
return any(salutation.startswith(i+',') for i in ['hello', 'hi', 'howdy']) \
and salutation.endswith('!')
if __name__ == "__main__":
gui_validation(is_valid_salutation)
As you can see the only argument required is the function itself.
gui_validation.py
"""
General purpose user input validation GUI.
"""
import tkinter
from tkinter.constants import X
from tkinter import messagebox
def gui_validation(validator, title=None, text=None, button_text="Validate"):
"""
Provides a general purpose gui to validate user input.
The user will be prompted to enter a string and will be
given feedback by the `validator` function.
This interface avoids verbosity and assumes the title to be
the `validator` name and the text to be the `validator` doc if not +
told explicitly.
"""
if title is None:
title = validator.__name__.replace('_',' ').capitalize()
if text is None:
text = validator.__doc__
def validate():
if validator(user_input.get()):
messagebox.showinfo("Input valid.",
"Congratulations, you entered valid input.")
else:
messagebox.showerror("Input NOT valid.",
"Please try again and enter a valid input.")
root = tkinter.Tk()
root.wm_title(title)
title_label = tkinter.Label(root, text=title, font='25px')
title_label.pack(fill=X, expand=1)
text_label = tkinter.Label(root, text=text, font='20px')
text_label.pack(fill=X, expand=1)
user_input = tkinter.Entry(root)
user_input.pack()
button = tkinter.Button(root, text=button_text, command=validate)
button.pack()
root.mainloop()
I was wondering:
- Would a class make the code clearer or just more verbose?
- Am I asking too little, should I force the user to give more details?
- Is it OK to use big fonts? The small fonts are much less legible to me.
- Any other improvement?
-
\$\begingroup\$ When you say the "user", do you mean the person typing or the person developing the validation function(s)? \$\endgroup\$jonrsharpe– jonrsharpe2015年04月28日 18:56:12 +00:00Commented Apr 28, 2015 at 18:56
-
\$\begingroup\$ @jonrsharpe by user I mean developer in my question. \$\endgroup\$Caridorc– Caridorc2015年04月28日 18:57:56 +00:00Commented Apr 28, 2015 at 18:57
1 Answer 1
If the goal is "general purpose" I would be inclined to start from a much simpler premise, allowing users (i.e. developers) to build GUIs with the features that they need. Using object-oriented techniques you could subclass the tkinter
widgets and extend them as needed. For example:
import tkinter as tk
from tkinter import messagebox
class ValidEntry(tk.Entry):
def __init__(self, *args, validator=lambda text: True, **kwargs):
super().__init__(*args, **kwargs)
self.validator = validator
def get(self):
text = super().get()
if not self.validator(text):
raise ValueError('Invalid input')
return text
This is now a reusable component; it can be dropped in wherever a regular tk.Entry
is required (indeed the default validator
allows it to be a direct replacement if validation isn't actually needed). The next step up is then:
class ValidatorFrame(tk.Frame):
INSTRUCTION_FONT = '20px'
def __init__(self, root, *args, button_text='Validate', instructions=None,
validator=lambda text: True, **kwargs):
super().__init__(root, *args, **kwargs)
if instructions is None:
instructions = validator.__doc__
self.instructions = tk.Label(
root,
font=INSTRUCTION_FONT,
justify=tk.LEFT,
text=instructions
)
self.instructions.pack(expand=1, fill=tk.X)
self.user_input = ValidEntry(root, validator=validator)
self.user_input.pack(expand=1, fill=tk.X)
self.validate = tk.Button(root, command=self.validate, text=button_text)
self.validate.pack()
def validate(self):
try:
text = self.user_input.get()
except ValueError:
messagebox.showerror(
"Input NOT valid.",
"Please try again and enter a valid input."
)
else:
messagebox.showinfo(
"Input valid.",
"Congratulations, you entered valid input."
)
Again, this is a component that can be easily used elsewhere. One final step outward:
class TestGui(tk.Tk):
TITLE_FONT = '25px'
def __init__(self, *args, title=None, validator=None, **kwargs):
super().__init__(*args, **kwargs)
self.title_label = tk.Label(self, font=self.TITLE_FONT, text=title)
self.title_label.pack(expand=1, fill=tk.X)
self.frame = ValidatorFrame(self, validator=validator)
self.frame.pack(expand=1, fill=tk.X)
if title is None:
title = validator.__name__.replace('_', ' ').capitalize()
self.wm_title(title)
By building up from basic components, you give yourself and your users a lot more flexibility. You can now launch this (using the validator you've already written) as simply as:
if __name__ == '__main__':
app = TestGui(validator=is_valid_salutation)
app.mainloop()
It's slightly more code, but a lot more flexible. One thing I haven't put much thought into is where the text would go once valid...
A few other things to note:
import tkinter as tk
saves you repeating a few extra characters all over the place;- You tend to end up with a lot of keyword arguments to widgets, so I keep them in alphabetical order; and
- Factoring out the styling information makes it easier to reuse, too.