3
\$\begingroup\$

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?
asked Apr 28, 2015 at 18:32
\$\endgroup\$
2
  • \$\begingroup\$ When you say the "user", do you mean the person typing or the person developing the validation function(s)? \$\endgroup\$ Commented Apr 28, 2015 at 18:56
  • \$\begingroup\$ @jonrsharpe by user I mean developer in my question. \$\endgroup\$ Commented Apr 28, 2015 at 18:57

1 Answer 1

1
\$\begingroup\$

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.
answered Apr 28, 2015 at 19:32
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.