Tkinter Template

Ball python, Photo credit: Micheal McConville

Tkinter (a shortening of “Tk interface”) is the standard GUI package that comes with python. I’m using it to built my first non-web GUI. I may move to wxPython down the line, but Python 3.5 is not currently supported, Tkinter does what I need it to do, and since the inclusion of ttk (themed Tk), the styling of the widgets look just fine to my eye.

Tkinter documentation and help as far as best practices and design patterns is not particularly thorough or abundant (Tkdocs and effbot have been very helpful references, if not quite sufficient), so I thought I’d put down and share the basic template I wrangled together over a couple of days of learning. Especially because it can be hard to find simple, well-commented skeleton templates for good overall application organization, this is something that would have helped me a lot. I’ve kept all my application logic in other scripts, so this is just the GUI. I’ll probably update this as I learn more; visit the GitHub repository for updates.

import tkinter
# Lots of tutorials have from tkinter import *, but that is pretty much always a bad idea 
from tkinter import ttk
import abc

class Menubar(ttk.Frame):
    """Builds a menu bar for the top of the main window"""
    def __init__(self, parent, *args, **kwargs): 
        ''' Constructor''' 
        ttk.Frame.__init__(self, parent, *args, **kwargs) 
        self.root = parent 
        self.init_menubar() 
 
    def on_exit(self): 
        '''Exits program''' 
        quit()

    def display_help(self): 
        '''Displays help document''' 
        pass 
 
    def display_about(self): 
        '''Displays info about program''' 
        pass

    def init_menubar(self): 
        self.menubar = tkinter.Menu(self.root) 
        self.menu_file = tkinter.Menu(self.menubar) # Creates a "File" menu 
        self.menu_file.add_command(label='Exit', command=self.on_exit) # Adds an option to the menu 
        self.menubar.add_cascade(menu=self.menu_file, label='File') # Adds File menu to the bar. Can also be used to create submenus.

        self.menu_help = tkinter.Menu(self.menubar) #Creates a "Help" menu 
        self.menu_help.add_command(label='Help', command=self.display_help) 
        self.menu_help.add_command(label='About', command=self.display_about) 
        self.menubar.add_cascade(menu=self.menu_help, label='Help') 
 
        self.root.config(menu=self.menubar)

class Window(ttk.Frame):
    """Abstract base class for a popup window"""
    __metaclass__ = abc.ABCMeta 
    def __init__(self, parent): 
        ''' Constructor ''' 
        ttk.Frame.__init__(self, parent) 
        self.parent = parent
        self.parent.resizable(width=False, height=False) # Disallows window resizing
        self.validate_notempty = (self.register(self.notEmpty), '%P') # Creates Tcl wrapper for python function. %P = new contents of field after the edit. 
        self.init_gui()

    @abc.abstractmethod # Must be overwriten by subclasses 
    def init_gui(self): 
        '''Initiates GUI of any popup window'''
        pass 
 
    @abc.abstractmethod 
    def do_something(self): 
        '''Does something that all popup windows need to do''' 
        pass

    def notEmpty(self, P): 
        '''Validates Entry fields to ensure they aren't empty''' 
        if P.strip(): 
            valid = True 
        else: 
            print("Error: Field must not be empty.") # Prints to console 
            valid = False 
        return valid

    def close_win(self): 
        '''Closes window''' 
        self.parent.destroy()

class SomethingWindow(Window): 
    """ New popup window """

    def init_gui(self): 
        self.parent.title("New Window") 
        self.parent.columnconfigure(0, weight=1)
        self.parent.rowconfigure(3, weight=1)

        # Create Widgets
        self.label_title = ttk.Label(self.parent, text="This sure is a new window!")
        self.contentframe = ttk.Frame(self.parent, relief="sunken")

        self.label_test = ttk.Label(self.contentframe, text='Enter some text:')
        self.input_test = ttk.Entry(self.contentframe, width=30, validate='focusout', validatecommand=(self.validate_notempty))

        self.btn_do = ttk.Button(self.parent, text='Action', command=self.do_something) 
        self.btn_cancel = ttk.Button(self.parent, text='Cancel', command=self.close_win)

        # Layout
        self.label_title.grid(row=0, column=0, columnspan=2, sticky='nsew') 
        self.contentframe.grid(row=1, column=0, columnspan=2, sticky='nsew')

        self.label_test.grid(row=0, column=0)
        self.input_test.grid(row=0, column=1, sticky='w')

        self.btn_do.grid(row=2, column=0, sticky='e')
        self.btn_cancel.grid(row=2, column=1, sticky='e')

        # Padding
        for child in self.parent.winfo_children(): 
            child.grid_configure(padx=10, pady=5)
        for child in self.contentframe.winfo_children(): 
            child.grid_configure(padx=5, pady=2)

    def do_something(self):
        '''Does something'''
        text = self.input_test.get().strip()
        if text:
            # Do things with text
            self.close_win()
        else:
            print("Error: But for real though, field must not be empty.") 
 
class GUI(ttk.Frame):
    """Main GUI class""" 
    def __init__(self, parent, *args, **kwargs): 
        ttk.Frame.__init__(self, parent, *args, **kwargs) 
        self.root = parent 
        self.init_gui() 
 
    def openwindow(self): 
        self.new_win = tkinter.Toplevel(self.root) # Set parent 
        SomethingWindow(self.new_win) 
 
    def init_gui(self): 
        self.root.title('Test GUI') 
        self.root.geometry("600x400") 
        self.grid(column=0, row=0, sticky='nsew') 
        self.grid_columnconfigure(0, weight=1) # Allows column to stretch upon resizing 
        self.grid_rowconfigure(0, weight=1) # Same with row 
        self.root.grid_columnconfigure(0, weight=1) 
        self.root.grid_rowconfigure(0, weight=1) 
        self.root.option_add('*tearOff', 'FALSE') # Disables ability to tear menu bar into own window 

        # Menu Bar
        self.menubar = Menubar(self.root)

        # Create Widgets 
        self.btn = ttk.Button(self, text='Open Window', command=self.openwindow)

        # Layout using grid 
        self.btn.grid(row=0, column=0, sticky='ew') 

        # Padding
        for child in self.winfo_children(): 
            child.grid_configure(padx=10, pady=5)
 
if __name__ == '__main__': 
    root = tkinter.Tk() 
    GUI(root) 
    root.mainloop()

2 thoughts on “Tkinter Template

  1. self.root.geometry(“600×400 self.grid(column=0, row=0, sticky=’nsew’)

    ^^^^^
    This part is wrong, can you confirm what this line of code should be?

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s