Python Color Chooser

The tkinter library in Python comes with an askcolor function that will pull up the system’s color picker dialog. It’s really easy to use and it returns a tuple with a RGB value and a hexadecimal value. This makes it really easy for anyone who is working with colors to ask the user for a color.

from tkinter import *
from tkinter.colorchooser import askcolor


class Window(Frame):
    def __init__(self, master=None, cnf={}, **kw):
        super().__init__(master, cnf, **kw)
        self.open = Button(self, text='Pick a color', command=self.pick_a_color)
        self.exit = Button(self, text='Exit', command=self.quit)

        for b in (self.open, self.exit):
            b.pack(side=LEFT, expand=YES, fill=BOTH)
        self.pack()

    def pick_a_color(self):
        print(askcolor(parent=self, title='Pick a color'))


if __name__ == '__main__':
    win = Window(Tk())
    win.mainloop()

askcolor

The askcolor function simply shows the system’s ask color dialog window. Therefore, it will adjust to the user’s platform and look natural. You can add the parent and title arguments if you want but otherwise the defaults work just as well.

Calling the function only requires one line of code.

askcolor(parent=self, title='Pick a color')

When the user picks a color, a tuple is returned with the rgb values and the hexadecimal values.

((255.99609375, 170.6640625, 104.40625), '#ffaa68')

You will get this result if they click on cancel.

(None, None)

Notice that it is a tuple of None.

Advertisements

Python File Dialog

Many Python applications need to ask the user if they want to a open a file or folder or save a file. The tkinter library has built in dialog functions for this exact purpose. Here is an example of how to use the askopenfilename, asksaveasfile, and askdirectory functions with some common configurations.

from tkinter import *
from pathlib import Path
from tkinter.filedialog import askopenfilename, asksaveasfile, askdirectory


class Window(Frame):
    def __init__(self, master=None, cnf={}, **kw):
        super().__init__(master, cnf, **kw)
        self.open = Button(self, text='Open', command=self.open_file)
        self.save = Button(self, text='Save', command=self.save_file)
        self.ask_dir = Button(self, text='Folder', command=self.ask_folder)
        self.exit = Button(self, text='Exit', command=self.quit)

        for b in (self.open, self.save, self.ask_dir, self.exit):
            b.pack(side=LEFT, fill=BOTH)

        self.pack()


    def open_file(self):
        file = askopenfilename(filetypes=(("Python files", "*.py"),
                                           ("All files", "*.*")),
                               title='Open File',
                               initialdir=str(Path.home()))
        if file:
            print(file)
        else:
            print('Cancelled')

    def save_file(self):
        file = asksaveasfile(filetypes=(("Python files", "*.py"),
                                           ("All files", "*.*")),
                               title='Save File',
                               initialdir=str(Path.home()))
        if file:
            print(file)
        else:
            print('Cancelled')

    def ask_folder(self):
        folder = askdirectory(title='Pick a folder', initialdir=str(Path.home()))

        if folder:
            print(folder)
        else:
            print('Cancelled')


if __name__ == '__main__':
    win = Window(Tk())
    win.mainloop()

askopenfilename

You use askopenfilename when you want to open a file. It will return the absolute path of the file as a string if the user picks a file, or it will return None if the user cancels. You can restict the file types by passing an Iterable of tuples to the filetypes argument. If you do not specify an initialdir argument, it will default to the root of the user’s disk. I usually prefer to set it to the user’s home directory using Path.home() but you can adjust this to your application’s needs. The dialog will look specific to the user’s platform.

def open_file(self):
    file = askopenfilename(filetypes=(("Python files", "*.py"),
                                       ("All files", "*.*")),
                           title='Open File',
                           initialdir=str(Path.home())
    if file:
        print(file)
    else:
        print('Cancelled')

asksaveasfile

You can use this dialog when you want to perform a save operation. It takes arguments that are similar to askopenfilename, but it will return a file handler object opened in write mode (if you use the default arguments) rather than a string. You can then proceed with your save code. This function also returns None if the user cancels.

def save_file(self):
    file = asksaveasfile(filetypes=(("Python files", "*.py"),
                                       ("All files", "*.*")),
                         title='Save File',
                         initialdir=str(Path.home()))
    if file:
        print(file)
    else:
        print('Cancelled')

askdirectory

This function is used to let the user pick a folder. It will return a string if the user picked a folder or None if they chose to cancel.

def ask_folder(self):
    folder = askdirectory(title='Pick a folder', initialdir=str(Path.home()))

    if folder:
        print(folder)
    else:
        print('Cancelled')

Python Simple Dialogs

Applications typically have to request input from the user from time to time. Python and tkinter have built in dialogs that help you ask the user basic questions. These dialogs also provide validation to help make sure that the user enters valid input. This is an example program that shows off askquestion, askfloat, askinteger, and askstring.

from tkinter import *
from tkinter.messagebox import askquestion, showinfo
from tkinter.simpledialog import askfloat, askinteger, askstring


class AskDialogDemo(Frame):
    def __init__(self, master=None, cnf={}, **kw):
        super().__init__(master, cnf, **kw)
        self.pack()
        Button(self, text='Ask a Question', command=self.askquestion_demo).pack(side=LEFT, fill=BOTH, expand=YES)
        Button(self, text='Ask for a Float', command=self.askfloat_demo).pack(side=LEFT, fill=BOTH, expand=YES)
        Button(self, text='Ask for an Integer', command=self.askinteger_demo).pack(side=LEFT, fill=BOTH, expand=YES)
        Button(self, text='Ask for a String', command=self.askstring_demo).pack(side=LEFT, fill=BOTH, expand=YES)

    def askquestion_demo(self):
        answer = askquestion('Question', 'Do you like corn?')
        if answer:
            showinfo('Answer', answer)
        else:
            self.canceled()

    def askfloat_demo(self):
        num = askfloat('Float', 'Enter a decimal number')
        if num:
            showinfo('Float', 'You entered {}'.format(num))
        else:
            self.canceled()

    def askinteger_demo(self):
        num = askinteger('Integer', 'Enter a whole number')
        if num:
            showinfo('Integer', 'You entered {}'.format(num))
        else:
            self.canceled()

    def askstring_demo(self):
        str = askstring('String', 'Enter a string')
        if str:
            showinfo('String', 'You entered {}'.format(str))
        else:
            self.canceled()

    def canceled(self):
        showinfo('Canceled', 'You canceled')


if __name__ == '__main__':
    AskDialogDemo().mainloop()

Explanation

askquestion

You use askquestion to ask the user a basic yes or no question. It takes two arguments: one for the title, and the other is for the question. The returned value will be a string containing yes or no.

answer = askquestion('Question', 'Do you like corn?')

askfloat

The askfloat function returns decimal numbers. It will also perform validation that makes sure the user enters a valid float. The function will either return the float that the user entered or it will return None if they hit cancel.

num = askfloat('Float', 'Enter a decimal number')

askinteger

This function works like askfloat but it returns integers.

num = askinteger('Integer', 'Enter a whole number')

askstring

You can use askstring to get a string value from the user. It will return None if the user enters a blank string and hits Ok or Cancel.

str = askstring('String', 'Enter a string')

Python Advanced Quit Button

Object orientated programming fits extremely well with GUI programming. Using OOP, we can easily make reusable GUI components. This post shows off a quit button that confirms if the user really wants to exit the application. I got the idea from Programming Python: Powerful Object-Oriented Programming. Here is my implementation of the idea followed by the explanation.

from tkinter import *
from tkinter.messagebox import *


class TkQuitButton(Frame):
    def __init__(self, master=None,
                 auto_pack=True,  # Pack the widget automatically?
                 dialog_title='Confirm',  # Title text for the askyesno dialog
                 dialog_message='Are you sure you want to quit?',  # Message for the askyesno dialog
                 button_text='Quit',  # The quit button's text
                 quit_command=Frame.quit,  # Callback command for when the user wants to quit
                 cnf={}, **kw):

        super().__init__(master, cnf, **kw)
        # Store our fields for later user
        self.quit_command = quit_command
        self.dialog_message = dialog_message
        self.dialog_title = dialog_title
        self.quit_button = Button(self, text=button_text, command=self.quit)

        # Notice that self.quit_button is exposed. This can be useful for when
        # the client code needs to configure this frame on its own
        if auto_pack:
            self.pack_widget()
    
    # This let's us override the packing        
    def pack_widget(self):
        self.pack()
        self.quit_button.pack(side=LEFT, expand=YES, fill=BOTH)

    def quit(self):
        # Call the askyesno dialog
        result = askyesno(self.dialog_title, self.dialog_message)
        if result:
            # if they quit, then execute the stored callback command
            self.quit_command(self)


if __name__ == '__main__':
    TkQuitButton().mainloop()

This class extends the Frame class and packs a button into the frame. There are a few configuration properties that can be passed into the constructor. For example, we can auto_pack the widget so that it uses a default packing scheme. We can specifiy a custom title for the askyesno dialog as well as a custom message. The code even lets use customize the text of the button. We can also use a custom quit handler function should we choose to do so.

We can customize how the widget is packed in two different ways. The first way to access the quit_button property and call pack on it directly. This allows client code to change how this widget is packed into their GUIs. Alternatively, we can subclass this class and just override the pack_widget method.

The default quit implementation uses Tk’s askyesno dialog function to display a confirmation dialog to the user. It’s title and message are set to self.dialog_title and self.dialog_message properties. This allows use to customize what the user sees when the dialog is displayed. If the user presses yes, then we call the self.quit_command function which defaults to Frame.quit. Note that since self.quit is a method, we can customize this behavior by overriding it. Since we use a callback handler to exit the applicaiton, we can also customize how the application exits as well.

Tk Standard Dialogs

Applications generally need to show system dialogs to alert the user to events. In this post, we will cover the yes or no dialog, a warning dialog, information dialog, and an error dialog. Tk uses system calls to show dialogs that are native to the underlying platform. Therefore, dialogs on Windows will look like they should on Windows while Mac OS X dialogs will appear correct for that platform.

askyesno

The askyesno is a dialog that is used to present a user with a yes or no choice. It returns a boolean to the caller.

result = askyesno('Yes No Demo', 'Click on either yes or no')

yesno

showwarning

You use showwarning when you want to warn the user about something.

showwarning('Warning Demo', 'You have been warned')

warning

showinfo

This dialog is used to supply the user with information.

showinfo('Info Demo', 'This is some information')

info

showerror

You should use showerror when you need to report an error to the user.

showerror('Error Demo', 'This is an error')

error

Putting it Together

Standard dialog calls are a useful way to notify the user about something important. Since they block the program’s execution, the user is forced to interact with the dialog. This makes the dialogs ideal for forcing the user to read a message or make a choice. Below is a complete program that demonstrates all of the dialogs.

from tkinter import *
from tkinter.messagebox import *


def ask_yes_no_demo():
    result = askyesno('Yes No Demo', 'Click on either yes or no')
    if result:
        showinfo('Result', 'You clicked on Yes')
    else:
        showinfo('Result', 'You clicked on No')


def warning_demo():
    showwarning('Warning Demo', 'You have been warned')


def info_demo():
    showinfo('Info Demo', 'This is some information')


def error_demo():
    showerror('Error Demo', 'This is an error')


root = Tk()
Button(text='Ask Yes No', command=ask_yes_no_demo).pack(fill=X)
Button(text='Warning', command=warning_demo).pack(fill=X)
Button(text='Info', command=info_demo).pack(fill=X)
Button(text='Error', command=error_demo).pack(fill=X)
Button(text='Quit', command=(lambda: sys.exit(0))).pack(fill=X)
mainloop()

Tk — Toplevel

Toplevel widgets are non-root windows. In other words, Toplevel widgets are windows that appear outside of the application’s root window. We can use Toplevel widgets for items such as dialogs, color picker windows, or even dragging tabs into windows. Basically, anytime you need a window that isn’t part of the main application, you can use a Toplevel to create it.

Here is an example of a script that creates Toplevel windows everytime the Spawn button is clicked.

from tkinter import *

count = 0


def spawn_top_level(text):
    global count

    # Create a new window with a label
    win = Toplevel()
    Label(win, text=text, font=('Arial', 32, 'italic')).pack(expand=YES, fill=BOTH)

    count += 1


# This is the main application window
root = Tk()
Button(root,
       text='Spawn',
       command=(lambda: spawn_top_level('Top Level: {}'.format(count)))).pack()
Button(root, text='Quit', command=root.quit).pack()
root.mainloop()

The code creates Toplevel windows inside of the spawn_top_level() function. The window itself is created on line 10, and then we attach a Label to it on line 11. Notice how we pass win as the Label’s parent on line 11. This is how Tkinter knows where to attach the label. When the script is run, we get something like the screenshot below.

toplevel

Tk – GUI Composition with Classes

Since Python’s Tk widgets are classes, it is really easy to compose GUIs by using Pythons OOP capabilities. OOP works really well because we can break complex GUIs down into smaller components and then compose a larger GUI out of these components. Let’s begin with a text area control.

from tkinter import *


class TextArea(Text):
    def __init__(self, parent=None):
        Text.__init__(self, parent, width=40, height=10, border=2)
        self.pack(expand=YES, fill=BOTH, side=TOP)

if __name__ == '__main__':
    TextArea(Toplevel())
    mainloop()

This code subclasses the Text control and initializes it to what would be a reasonably sized text area control that grows and shrinks with the window. We can verify if our control is working properly by using the self-test code found in the script. Here is a screenshot of what it looks like.

textarea

It’s not much of a window, but we aren’t done composing our GUI yet either. Now let’s make some buttons that will let us load, save, and quit the application. The buttons will be arranged horizontally from left to right. This time, we are going to subclass the Frame class.

class ControlPanel(Frame):
    def __init__(self, parent=None, save=None, load=None, quit_command=exit):
        Frame.__init__(self, parent)
        Button(self, text='Save', command=save).pack(side=LEFT)
        Button(self, text='Load', command=load).pack(side=LEFT)
        Button(self, text='Exit', command=quit_command).pack(side=LEFT)
        self.pack(expand=YES, fill=BOTH, side=TOP)

if __name__ == '__main__':
    ControlPanel(Toplevel())
    mainloop()

In this example, we are using the self variable as a parent object to our 3 objects. The ControlPanel’s constructor accepts three references to functions that act as event handlers for the buttons. Inside of the constructor, we create three buttons and set their text and command attributes. Then we pack them to the left side of the layout. Finally, the frame itself is packed. Running the self-test code gives us the following window.

control_panel

The final task is to combine our controls into a single window. Once again, we are going to subclass Frame.

class TextPanel(Frame):
    def __init__(self, parent=None):
        Frame.__init__(self, parent)
        TextArea(self)
        ControlPanel(self)
        self.pack(expand=YES, fill=BOTH)

if __name__ == '__main__':
    TextPanel(Toplevel())
    mainloop()

Notice how the TextPanel class simply uses TextArea and ControlPanel. Once again, we are using composition to build up a complex GUI. The beauty of this pattern is that we can use both TextArea and ControlPanel in other GUIs. Futhermore, the TextPanel class can also get embedded into other GUIs as well.

Since all three classes have test code, we can easily see how our code is working as we develop. This is part of the reason why it’s so easy to build up GUI applications in Python using Tk or another widget toolkit. We can easily contruct GUIs using OOP and then test then instantly and independently of the application.

Here is the finished GUI followed by a complete script.

complete

from tkinter import *


class TextArea(Text):
    def __init__(self, parent=None):
        Text.__init__(self, parent, width=40, height=10, border=2)
        self.pack(expand=YES, fill=BOTH, side=TOP)


class ControlPanel(Frame):
    def __init__(self, parent=None, save=None, load=None, quit_command=exit):
        Frame.__init__(self, parent)
        Button(self, text='Save', command=save).pack(side=LEFT)
        Button(self, text='Load', command=load).pack(side=LEFT)
        Button(self, text='Exit', command=quit_command).pack(side=LEFT)
        self.pack(expand=YES, fill=BOTH, side=TOP)


class TextPanel(Frame):
    def __init__(self, parent=None):
        Frame.__init__(self, parent)
        TextArea(self)
        ControlPanel(self)
        self.pack(expand=YES, fill=BOTH)


if __name__ == '__main__':
    TextArea(Toplevel())
    ControlPanel(Toplevel())
    TextPanel(Toplevel())
    mainloop()