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 — 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