(Alejandro Rodas de Paz) Tkinter GUI Application D - PDFCOFFEE.COM (2024)

Tkinter GUI Application Development Cookbook A practical solution to your GUI development problems with Python and Tkinter Alejandro Rodas de Paz

BIRMINGHAM - MUMBAI

Tkinter GUI Application Development Cookbook Copyright © 2018 Packt Publishing All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews. Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book. Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information. Commissioning Editor: Amarabha Banerjee Acquisition Editor: Reshma Raman Content Development Editor: Jason Pereira Technical Editor: Prajakta Mhatre Copy Editor: Dhanya Baburaj Project Coordinator: Sheejal Shah Proofreader: Safis Editing Indexer: Rekha Nair Production Coordinator: Deepika Naik First published: March 2018 Production reference: 1270318 Published by Packt Publishing Ltd. Livery Place 35 Livery Street Birmingham B3 2PB, UK. ISBN 978-1-78862-230-1 www.packtpub.com

I dedicate my work to my aunt, Elena, and my cousins, Julia and Laura. This book would not have been possible without their love and support.

mapt.io

Mapt is an online digital library that gives you full access to over 5,000 books and videos, as well as industry leading tools to help you plan your personal development and advance your career. For more information, please visit our website.

Why subscribe? Spend less time learning and more time coding with practical eBooks and Videos from over 4,000 industry professionals Improve your learning with Skill Plans built especially for you Get a free eBook or video every month Mapt is fully searchable Copy and paste, print, and bookmark content

PacktPub.com Did you know that Packt offers eBook versions of every book published, with PDF and ePub files available? You can upgrade to the eBook version at www.PacktPub.com and as a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at [emailprotected] for more details. At www.PacktPub.com, you can also read a collection of free technical articles, sign up for a range of free newsletters, and receive exclusive discounts and offers on Packt books and eBooks.

Contributors

About the author Alejandro Rodas de Paz is a computer engineer from Seville, Spain. He has developed several projects with Python, from web crawlers to artificial intelligence algorithms. He has also used Tkinter for building an application for the lighting system of the city of Almere (Netherlands). Prior to this publication, Alejandro co-wrote Packt's title Python Game Development by Example, and collaborated as a technical reviewer on the book Tkinter GUI Application Development Hotshot. I would like to thank the exceptional team at Packt Publishing for their assistance during this journey, and words cannot express my gratitude to Jason and Prajakta for their understanding and immense patience.

About the reviewers Javier Becerra Elcinto received his PhD in image processing from the Université de Bordeaux in 2006. In 2010 he started working as a self-employed developer working with Python and C++, and in 2014 he cofounded Panoimagen S.L. There, he has continued to develop software for industrial and precision agriculture applications. Javier lectures regularly on scientific programming in private companies and several Spanish research institutions. Marcos Perez Gonzalez works in the treatment of multimedia document databases with Python. Previously, he had been using it as glue between projects and technologies for more than 10 years. At the beginning of his career, he used Python for embedding purposes and multiplatform GUI programming. He is a member of the Python Spain association and the Python Madrid meetup. Moreover, he has been a member of the board of directors of the Spanish Association of Computer Engineering. He holds a master's degree in computer engineering from the University of Valladolid. Bryson Tyrrell is a systems development engineer at Jamf in Minneapolis, Minnesota. In addition to the responsibilities of his role, Bryson has spoken at various IT conferences (including the Jamf Nation User Conference, Penn State MacAdmins, Mac Admins and Developers UK, and Atlassian Summit) and maintains a

number of open source tools for the Mac admin community on his GitHub account.

Packt is searching for authors like you If you're interested in becoming an author for Packt, please visit auth ors.packtpub.com and apply today. We have worked with thousands of developers and tech professionals, just like you, to help them share their insight with the global tech community. You can make a general application, apply for a specific hot topic that we are recruiting an author for, or submit your own idea.

Preface As one of the more versatile programming languages, Python is well known for its batteries-included philosophy, which includes a rich set of modules in its standard library; Tkinter is the library used to build desktop applications. Built over the Tk GUI toolkit, Tkinter is a common choice for rapid GUI development, and complex applications can benefit from the full capabilities of this library. This book covers all of your Tkinter and Python GUI development problems and solutions. Tkinter GUI Application Development Cookbook starts with an overview of Tkinter classes and at the same time provides recipes for basic topics, such as layout patterns and event handling. Next, this book covers how to develop common GUI patterns, such as entering and saving data, navigating through menus and dialogs, and performing long-running actions in the background. You can then make your apps leverage network resources effectively and perform graphical operations on a canvas and related tasks such as detecting collisions between items. Finally, this book covers using themed widgets, an extension of Tk widgets that have a more native look and feel. By the end of the book, you will have an in-depth knowledge of Tkinter classes and know how to use them to build efficient and rich GUI applications.

Who this book is for This book targets Python developers who are familiar with the basics of the language—syntax, data structures, and OOP—wants to learn effective solutions to the common challenges of GUI development, and desires to discover interesting capabilities that Tkinter can offer to build complex applications. You do not need previous experience with Tkinter or other GUI development libraries since the first part of the book will teach the basics of the library through the introductory use cases.

What this book covers , Getting Started with Tkinter, introduces the structure of a Tkinter program and shows you how to perform the most common tasks, such as creating widgets and handling user events. Chapter 1

, Window Layout, demonstrates how to place widgets using geometry managers and improve the layout of large applications. Chapter 2

, Customizing Widgets, dives deeper into Tkinter's widget configuration and appearance customization. Chapter 3

, Dialogs and Menus, teaches you how to improve the navigation of Tkinter apps with menus and dialogs. Chapter 4

, Object-Oriented Programming and MVC, teaches you how to effectively apply design patterns in your Tkinter applications. Chapter 5

, Asynchronous Programming, covers several recipes to execute long-running actions without freezing the application—a recurring problem in GUI development. Chapter 6

, Canvas and Graphics, explores the Canvas widget and the types of items you can add to the canvas and how to manipulate them. Chapter 7

, Themed Widgets, teaches you how to extend Tkinter applications with the Tk-themed widget set. Chapter 8

To get the most out of this book To get up and running, users will need to have the following technologies installed: Python 3.x Any operating system

Download the example code files You can download the example code files for this book from your account at www.packtpub.com. If you purchased this book elsewhere, you can visit www.packtpub.com/support and register to have the files emailed directly to you. You can download the code files by following these steps: 1. Log in or register at www.packtpub.com. 2. Select the SUPPORT tab. 3. Click on Code Downloads & Errata. 4. Enter the name of the book in the Search box and follow the onscreen instructions. Once the file is downloaded, please make sure that you unzip or extract the folder using the latest version of: WinRAR/7-Zip for Windows Zipeg/iZip/UnRarX for Mac 7-Zip/PeaZip for Linux The code bundle for the book is also hosted on GitHub at https://githu b.com/PacktPublishing/Tkinter-GUI-Application-Development-Cookbook. In case there's an update to the code, it will be updated on the existing GitHub repository.

We also have other code bundles from our rich catalog of books and videos available at https://github.com/PacktPublishing/. Check them out!

Download the color images We also provide a PDF file that has color images of the screenshots/diagrams used in this book. You can download it here: https://www.packtpub.com/sites/default/files/downloads/TkinterGUIApplicationDevelop

.

mentCookbook_ColorImages.pdf

Conventions used There are a number of text conventions used throughout this book. : Indicates code words in text, database table names, folder names, filenames, file extensions, pathnames, dummy URLs, user input, and Twitter handles. Here is an example: "The delete() method takes two arguments that indicate the range of the characters that should be deleted." CodeInText

A block of code is set as follows: from tkinter import * root = Tk() btn = Button(root, text="Click me!") btn.config(command=lambda: print("Hello, Tkinter!")) btn.pack(padx=120, pady=30) root.title("My Tkinter app") root.mainloop()

When we wish to draw your attention to a particular part of a code block, the relevant lines or items are set in bold: def show_caption(self, event): caption = tk.Label(self, ...) caption.place(in_=event.widget, x=event.x, y=event.y) # ...

Bold: Indicates a new term, an important word, or words that you see onscreen. For example, words in menus or dialog boxes appear in the text like this. Here is an example: "The first will be labeled Choose file."

Warnings or important notes appear like this. Tips and tricks appear like this.

Get in touch Feedback from our readers is always welcome. General feedback: Email [emailprotected] and mention the book title in the subject of your message. If you have questions about any aspect of this book, please email us at [emailprotected]. Errata: Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you have found a mistake in this book, we would be grateful if you would report this to us. Please visit www.packtpub.com/submit-errata, selecting your book, clicking on the Errata Submission Form link, and entering the details. Piracy: If you come across any illegal copies of our works in any form on the Internet, we would be grateful if you would provide us with the location address or website name. Please contact us at [emailprotected] with a link to the material. If you are interested in becoming an author: If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, please visit authors.packtpub.com.

Reviews Please leave a review. Once you have read and used this book, why not leave a review on the site that you purchased it from? Potential readers can then see and use your unbiased opinion to make purchase decisions, we at Packt can understand what you think about our products, and our authors can see your feedback on their book. Thank you! For more information about Packt, please visit packtpub.com.

Getting Started with Tkinter In this chapter, we will cover the following recipes: Structuring a Tkinter application Working with buttons Creating text entries Tracing text changes Validating a text entry Selecting numerical values Creating selections with radio buttons Implementing switches with checkboxes Displaying a list of items Handling mouse and keyboard events Setting the main window's icon, title, and size

Introduction Thanks to its clear syntax and the wide ecosystem of libraries and tools, Python has become a popular and general-purpose programming language. From web development to Natural Language Processing (NLP), you can easily find an open source library that fits the need of your application domain, and in the last instance, you can always use any of the modules included in the Python standard library. The standard library follows the "batteries-included" philosophy, which means that it contains a large collection of utilities: regular expressions, mathematical functions, networking, and so on. The standard Graphical User Interface (GUI) package of this library is Tkinter, a thin object-oriented layer on top of Tcl/Tk. Starting from Python 3, the Tkinter module was renamed to tkinter (with a lowercase t). It also affects to the tkinter.ttk and tkinter.tix extensions. We will dive into the tkinter.ttk module in the last chapter of this book, since the tkinter.tix module is officially deprecated. In this chapter, we will explore several patterns for some basic classes of the tkinter module and some methods that are common to all widget subclasses.

Structuring a Tkinter application One of the main advantages of making applications with Tkinter is that it is very easy to set up a basic GUI with a script of a few lines. As the programs get more complex, it becomes more difficult to separate logically each part, so an organized structure will help us to keep our code clean.

Getting ready We will take the following program as an example: from tkinter import * root = Tk() btn = Button(root, text="Click me!") btn.config(command=lambda: print("Hello, Tkinter!")) btn.pack(padx=120, pady=30) root.title("My Tkinter app") root.mainloop()

It creates a main window with a button that prints Hello, Tkinter! in the console each time it is clicked. The button is placed with a padding of 120px in the horizontal axis and 30px in the vertical axis. The last statement starts the main loop, which processes user events and updates the GUI until the main window is destroyed:

You can execute the program and verify that it is working as expected. However, all our variables are defined in the global namespace, and the more widgets you add, the more difficult it becomes to reason about the parts where they are used.

Wildcard imports (from ... import *) are strongly discouraged in production code because they pollute your global namespace—we only used them here to illustrate an anti-pattern that can be commonly seen in online examples.

These maintainability issues can be addressed with basic OOP techniques, which are considered good practice in all types of Python programs.

How to do it... To improve the modularity of our simple program, we will define a class that wraps our global variables: import tkinter as tk class App(tk.Tk): def __init__(self): super().__init__() self.btn = tk.Button(self, text="Click me!", command=self.say_hello) self.btn.pack(padx=120, pady=30) def say_hello(self): print("Hello, Tkinter!") if __name__ == "__main__": app = App() app.title("My Tkinter app") app.mainloop()

Now, each variable is enclosed in a specific scope, including the command function, which is moved as a separate method.

How it works... First, we replaced the wildcard import with the import ... as syntax to have better control over our global namespace. Then, we defined our App class as a Tk subclass, which now is referenced via the tk namespace. To properly initialize the base class, we will call the __init__ method of the Tk class with the built-in super() function. This corresponds to the following lines: class App(tk.Tk): def __init__(self): super().__init__() # ...

Now, we have a reference to the App instance with the self variable, so we will add all the Button widget as an attribute of our class. Although it may look overkill for such a simple program, this refactoring will help us to reason about each part, the button instantiation is separated from the callback that gets executed when it is clicked, and the application bootstrapping is moved to the if __name__ == "__main__" block, which is a common practice in executable Python scripts. We will follow this convention through all the code samples, so you can take this template as the starting point of any larger application.

There's more... We subclassed the Tk class in our example, but it is also common to subclass other widget classes. We did this to reproduce the same statements that we had before we refactored the code. However, it may be more convenient to subclass Frame or Toplevel in larger programs, such as those with multiple windows. This is because a Tkinter application should have only one Tk instance, and the system creates one automatically if you instantiate a widget before you create the Tk instance. Keep in mind that this decision does not affect the structure of our App class since all widget classes have a mainloop method that internally starts the Tk main loop.

Working with buttons Button widgets represent a clickable item of your GUI applications. They typically use a text or an image indicating the action that will be performed when clicked. Tkinter allows you to easily configure this functionality with some standard options of the Button widget class.

How to do it... The following contains a button with an image that gets disabled when clicked and a list of buttons with the different types of available reliefs: import tkinter as tk RELIEFS = [tk.SUNKEN, tk.RAISED, tk.GROOVE, tk.RIDGE, tk.FLAT] class ButtonsApp(tk.Tk): def __init__(self): super().__init__() self.img = tk.PhotoImage(file="python.gif") self.btn = tk.Button(self, text="Button with image", image=self.img, compound=tk.LEFT, command=self.disable_btn) self.btns = [self.create_btn(r) for r in RELIEFS] self.btn.pack() for btn in self.btns: btn.pack(padx=10, pady=10, side=tk.LEFT) def create_btn(self, relief): return tk.Button(self, text=relief, relief=relief) def disable_btn(self): self.btn.config(state=tk.DISABLED) if __name__ == "__main__": app = ButtonsApp() app.mainloop()

The purpose of this program is to show several configuration options that can be used when creating a Button widget. After executing the preceding code, you will get the following output:

How it works... The most basic way of instantiation of Button is using the text option to set the button label and the command option that references the function to be invoked when the button is clicked. In out example, we also added PhotoImage via the image option, which takes precedence over the text string. The compound option serves to combine image and text in the same button, determining the position where the image is placed. It accepts the following constants as valid values: CENTER, BOTTOM, LEFT, RIGHT, and TOP. The second row of buttons is created with a list comprehension, using the list of RELIEF values. The label of each button corresponds to the name of the constant, so you can note the difference in the appearance of each button.

There's more... We used an attribute to keep a reference to our PhotoImage instance, even though we are not using it outside our __init__ method. The reason is that images are cleared when they are garbage collected, which will happen if we declare it as a local variable and the method exists. To avoid this, always remember to keep a reference to each PhotoImage object as long as the window where it is shown is still alive.

Creating text entries The Entry widget represents a text input displayed in a single line. Along with the Label and Button classes, it is one of the most commonly used Tkinter classes.

How to do it... This example shows how to create a login form with two entry instances for the username and password fields. Each character of password is displayed as an asterisk to avoid showing it in clear text: import tkinter as tk class LoginApp(tk.Tk): def __init__(self): super().__init__() self.username = tk.Entry(self) self.password = tk.Entry(self, show="*") self.login_btn = tk.Button(self, text="Log in", command=self.print_login) self.clear_btn = tk.Button(self, text="Clear", command=self.clear_form) self.username.pack() self.password.pack() self.login_btn.pack(fill=tk.BOTH) self.clear_btn.pack(fill=tk.BOTH) def print_login(self): print("Username: {}".format(self.username.get())) print("Password: {}".format(self.password.get())) def clear_form(self): self.username.delete(0, tk.END) self.password.delete(0, tk.END) self.username.focus_set() if __name__ == "__main__": app = LoginApp() app.mainloop()

The Log in button prints the values in the console, whereas the Clear button removes the content of both entries and returns the focus to the entry for username:

How it works... The Entry widgets are instantiated using the parent window or frame as the first argument and a set of optional keyword arguments to configure additional options. We did not specify any options for the entry corresponding to the username field. To keep the password secret, we specify the show argument with the string "*", which will display each typed character as an asterisk. With the get() method, we will retrieve the current text as a string. This is used in the print_login method to show the entries' content in the standard output. The delete() method takes two arguments that indicate the range of the characters that should be deleted. Keep in mind that the indices start at the position 0, and they do not include the character at the end of the range. If only one argument is passed, it deletes the character at that position. In the clear_form() method, we delete from index 0 to the constant END, which means that the whole content is removed. Finally, we set the focus to the username entry.

There's more... The content of an Entry widget can be modified programmatically with the insert() method, which takes two arguments: : The position to insert the text; note that entry positions are 0-indexed index

: The text to insert

string

A common pattern to reset the content of an entry with a default value can be achieved with a combination of delete() and insert(): entry.delete(0, tk.END) entry.insert(0, "default value")

Another pattern is to append the text in the current position of the text cursor. Here, you can use the INSERT constant instead of having to calculate the numerical index: entry.insert(tk.INSERT, "cursor here")

Like the Button class, the Entry class also accepts the relief and state options to modify its border style and state. Keep in mind that calls to delete() and insert() are ignored when the state is "disabled" or "readonly".

See also The Tracing text changes recipe The Validating a text entry recipe

Tracing text changes variables allow your applications to get notified when an input changes its value. There are four variable classes in Tkinter: BooleanVar, DoubleVar, IntVar, and StringVar. Each one wraps the value of the corresponding Python type, which should match the type of the input widget attached to the variable. Tk

This feature is particularly useful if you want to automatically update certain parts of your application based on the current state of some input widgets.

How to do it... In the following example, we will associate a StringVar instance to our entry with the textvariable option; this variable traces write operations with the show_message() method as callback: import tkinter as tk class App(tk.Tk): def __init__(self): super().__init__() self.var = tk.StringVar() self.var.trace("w", self.show_message) self.entry = tk.Entry(self, textvariable=self.var) self.btn = tk.Button(self, text="Clear", command=lambda: self.var.set("")) self.label = tk.Label(self) self.entry.pack() self.btn.pack() self.label.pack() def show_message(self, *args): value = self.var.get() text = "Hello, {}!".format(value) if value else "" self.label.config(text=text) if __name__ == "__main__": app = App() app.mainloop()

When you type something into the Entry widget, the label updates its text with a message composed with the Tk variable value. For instance, if you type the word Phara, the label will show Hello, Phara!. If the entry is empty, the label will not show any text. To show you how to modify the variable's content programmatically, we added a button that clears the entry when you click on it:

How it works... The first lines of our application constructor instantiate StringVar and attach a callback to the write mode. The valid mode values are as follows: : Called when the variable is written

"w"

: Called when the variable is read

"r"

(for unset): Called when the variable is deleted

"u"

When invoked, the callback function receives three arguments: the internal variable name, an empty string (it is used in other types of Tk variables), and the mode that triggered the operation. By declaring the method with *args, we make these arguments optional, because we are not using any of these values within the callback. The get() method of Tk wrappers returns the current value of the variable, and the set() method updates its value. They also notify the corresponding observers, so both modifying the entry's content through the GUI or clicking on the Clear button will trigger the call to the show_message() method.

There's more... Tk variables are optional for Entry widgets, but they are necessary for other widget classes to work correctly, such as the Checkbutton and Radiobutton classes.

See also The Creating selections with radio buttons recipe The Implementing switches with checkboxes recipe

Validating a text entry Typically, text inputs represent fields that follow certain validation rules, such as having a maximum length or matching a specific format. Some applications allow typing any kind of content into these fields and trigger the validation when the whole form is submitted. Under some circumstances, we want to prevent users from typing invalid content into a text field. We will take a look at how to implement this behavior using the validation options of the Entry widget.

How to do it... The following application shows how to validate an entry using regular expressions: import re import tkinter as tk class App(tk.Tk): def __init__(self): super().__init__() self.pattern = re.compile("^\w{0,10}$") self.label = tk.Label(self, text="Enter your username") vcmd = (self.register(self.validate_username), "%i", "%P") self.entry = tk.Entry(self, validate="key", validatecommand=vcmd, invalidcommand=self.print_error) self.label.pack() self.entry.pack(anchor=tk.W, padx=10, pady=10) def validate_username(self, index, username): print("Modification at index " + index) return self.pattern.match(username) is not None def print_error(self): print("Invalid username character") if __name__ == "__main__": app = App() app.mainloop()

If you run this script and type a non-alphanumeric character in the Entry widget, it will keep the same content and print the error message. This will also happen when you try to type more than 10 valid characters since the regular expression also limits the content's length.

How it works... With the validate option set to "key", we will activate the entry validation that gets triggered on any content modification. The value is "none" by default, which means that there is no validation. Other possible values are "focusin" and "focusout", which validate when the widget gets or loses the focus, respectively, or simply "focus" to validate in both cases. Alternatively, we can use the "all" value to validate in all situations. The validatecommand function is called each time the validation is triggered, and it should return true if the new content is valid, and false otherwise. Since we need more information to determine whether the content is valid or not, we create a Tcl wrapper around our Python function using the register method of the Widget class. Then, you can add the percent substitution for each parameter that will be passed to the Python function. Finally, we will group these values as a Python tuple. This corresponds to the following line from our example: vcmd = (self.register(self.validate_username), "%i", "%P")

In general, you can use any of the following substitutions: : Type of action; 1 for insertion, 0 for deletion, and -1 otherwise %d

: Index of the string being inserted or deleted

%i

: Value of the entry if the modification is allowed

%P

: Value of the entry before the modification

%s

: String content that is being inserted or deleted

%S

: The type of validation currently set

%v

: Type of validation that triggered the action

%V

: The name of the Entry widget

%W

The invalidcommand option takes a function that is invoked when validatecommand returns false. The same percent substitutions can be applied to this option, but in our example, we directly passed the print_error() method of our class.

There's more... The Tcl/Tk documentation suggests not mixing the validatecommand and the textvariable options since setting an invalid value to the Tk variable will turn off validation. The same occurs if the validatecommand function do not return a Boolean value. In case you are not familiar with the re module, you can check out the detailed introduction to regular expressions in the official Python documentation at https://docs.python.org/3.6/howto/regex.html.

See also The Creating text entries recipe

Selecting numerical values Previous recipes cover how to work with text inputs; we may want to enforce some inputs to contain only numerical values. This is the use case for the Spinbox and Scale classes—both widgets allow users to select a numerical value from a range or a list of valid options, but there are several differences in the way they are displayed and configured.

How to do it... This program has Spinbox and Scale for selecting an integer value from 0 to 5: import tkinter as tk class App(tk.Tk): def __init__(self): super().__init__() self.spinbox = tk.Spinbox(self, from_=0, to=5) self.scale = tk.Scale(self, from_=0, to=5, orient=tk.HORIZONTAL) self.btn = tk.Button(self, text="Print values", command=self.print_values) self.spinbox.pack() self.scale.pack() self.btn.pack() def print_values(self): print("Spinbox: {}".format(self.spinbox.get())) print("Scale: {}".format(self.scale.get())) if __name__ == "__main__": app = App() app.mainloop()

In the preceding code, for debugging purposes, we added a button that prints the value of each widget when you click on it:

How it works... Both classes accept the from_ and to options to indicate the range of valid values—the trailing underscore is necessary because the from option was originally defined in Tcl/Tk, but it is a reserved keyword in Python. A handy functionality of the Scale class is the resolution option, which sets the precision of the rounding. For instance, a resolution of 0.2 will allow the user to select the values 0.0, 0.2, 0.4, and so on. The value of this option is 1 by default, so the widget rounds all values to the nearest integer. As usual, the value of each widget can be retrieved with the get() method. An important difference is that Spinbox returns the number as a string, whereas Scale returns an integer value or a float value if the rounding accepts decimal values.

There's more... The Spinbox class has a similar configuration to the Entry widget, such as the textvariable and validate options. You can apply all these patterns to spinboxes with the main difference that it restricts to numerical values.

See also The Tracing text changes recipe

Creating selections with radio buttons With the Radiobutton widget, you can let the user select among several options. This pattern works well for a relatively small number of mutually exclusive choices.

How to do it... You can connect multiple Radiobutton instances using a Tkinter variable so that when you click on a non-selected option, it will deselect whatever other option was previously selected. In the following program, we created three radio buttons for the Red, Green, and Blue options. Each time you click on a radio button, it prints the lowercase name of the corresponding color: import tkinter as tk COLORS = [("Red", "red"), ("Green", "green"), ("Blue", "blue")] class ChoiceApp(tk.Tk): def __init__(self): super().__init__() self.var = tk.StringVar() self.var.set("red") self.buttons = [self.create_radio(c) for c in COLORS] for button in self.buttons: button.pack(anchor=tk.W, padx=10, pady=5) def create_radio(self, option): text, value = option return tk.Radiobutton(self, text=text, value=value, command=self.print_option, variable=self.var) def print_option(self): print(self.var.get()) if __name__ == "__main__": app = ChoiceApp() app.mainloop()

If you run this script, it will display the application with the Red radio button already selected:

How it works... To avoid repeating the code of the Radiobutton initialization, we defined a utility method that is called from a list comprehension. We unpacked the values of each tuple of the COLORS list and then passed these local variables as options to Radiobutton. Remember to try to not repeat yourself whenever possible. Since StringVar is shared among all the Radiobutton instances, they are automatically connected, and we force the user to select only one choice.

There's more... We set a default value of "red" in our program; however, what would happen if we omit this line, and the value of StringVar does not match any of the radio button values? It will match the default value of the tristatevalue option, which is the empty string. This causes the widget to display in a special "tri-state" or indeterminate mode. Although this option can be modified with the config() method, a better practice is to set a sensible default value so the variable is initialized in a valid state.

Implementing switches with checkboxes Choices between two alternatives are typically implemented with checkboxes and lists of options where each choice is independent from the rest. As we will see in the next example, these concepts can be implemented using the Checkbutton widget.

How to do it... The following application shows how to create Checkbutton, which must be connected to an IntVar variable to be able to inspect the button state: import tkinter as tk class SwitchApp(tk.Tk): def __init__(self): super().__init__() self.var = tk.IntVar() self.cb = tk.Checkbutton(self, text="Active?", variable=self.var, command=self.print_value) self.cb.pack() def print_value(self): print(self.var.get()) if __name__ == "__main__": app = SwitchApp() app.mainloop()

In the preceding code, we simply printed the value of the widget each time it is clicked:

How it works... Like the Button widget, the Checkbutton also accepts the command and text options. With the onvalue and offvalue options, we can specify the values used when the button is on and off. We use an integer variable because these values are 1 and 0 by default, respectively; however, you can also set them to any other integer values.

There's more... With Checkbuttons, it is also possible to use other variable types: var = tk.StringVar() var.set("OFF") checkbutton_active = tk.Checkbutton(master, text="Active?", variable=self.var, onvalue="ON", offvalue="OFF", command=update_value)

The only restriction is to match onvalue and offvalue with the type of the Tkinter variable; in this case, since "ON" and "OFF" are strings, the variable should be a StringVar. Otherwise, the Tcl interpreter will raise an error when trying to set the corresponding value of a different type.

See also The Tracing text changes recipe The Creating selections with radio buttons recipe

Displaying a list of items The Listbox widget contains text items that can be selected by the user with the mouse or keyboard. This selection can be individual or multiple, depending on the widget configuration.

How to do it... The following program creates a list selection with the days of the week. There is a button to print the actual selection and a list of buttons to change the selection mode: import tkinter as tk DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] MODES = [tk.SINGLE, tk.BROWSE, tk.MULTIPLE, tk.EXTENDED] class ListApp(tk.Tk): def __init__(self): super().__init__() self.list = tk.Listbox(self) self.list.insert(0, *DAYS) self.print_btn = tk.Button(self, text="Print selection", command=self.print_selection) self.btns = [self.create_btn(m) for m in MODES] self.list.pack() self.print_btn.pack(fill=tk.BOTH) for btn in self.btns: btn.pack(side=tk.LEFT) def create_btn(self, mode): cmd = lambda: self.list.config(selectmode=mode) return tk.Button(self, command=cmd, text=mode.capitalize()) def print_selection(self): selection = self.list.curselection() print([self.list.get(i) for i in selection]) if __name__ == "__main__": app = ListApp() app.mainloop()

You can try out changing the mode of selection and printing the

selected items:

How it works... We create an empty Listbox object and add all the text items with the insert() method. The 0 index indicates that the items should be added at the beginning of the list. In the following code snippet, we unpacked the DAYS list, but individual items can be appended at the end with the END constant: self.list.insert(tk.END, "New item")

The current selection is retrieved using the curselection() method. It returns the indices of the selected items to transform them to the corresponding text items we called the get() method for each index in a comprehension list. Finally, the list is printed in the standard output for debugging purposes. In our example, the selectmode option can be changed programmatically to explore the different behaviors, as follows: : Single choice

SINGLE

: Single choice that can be moved with the up and down

BROWSE

keys : Multiple choice

MULTIPLE

: Multiple choice with ranges that can be selected with the Shift and Ctrl keys EXTENDED

There's more... If the number of text items is large enough, it may be necessary to add a vertical scroll bar. You can easily connect it using the yscrollcommand option. In our example, we can wrap both widgets in a frame to keep the same layout. Remember to specify the fill option when packing the scroll so that it fills the available space in the y axis: def __init__(self): self.frame = tk.Frame(self) self.scroll = tk.Scrollbar(self.frame, orient=tk.VERTICAL) self.list = tk.Listbox(self.frame, yscrollcommand=self.scroll.set) self.scroll.config(command=self.list.yview) # ... self.frame.pack() self.list.pack(side=tk.LEFT) self.scroll.pack(side=tk.LEFT, fill=tk.Y)

Similarly, there is a xscrollcommand option for the horizontal axis.

See also The Creating selections with radio buttons recipe

Handling mouse and keyboard events Being able to react to events is one of the most basic but important topics in GUI application development since it determines how users can interact with the program. Pressing keys of the keyboard and clicking on items with the mouse are some common types of events, which are automatically handled in some Tkinter classes. For instance, this behavior is already implemented on the command option of the Button widget class, which invokes the specified callback function. Some events can get triggered without user interaction, such as changing the input focus programmatically from one widget to another.

How to do it... You can attach an event binding to a widget using the bind method. The following example binds some mouse events to a Frame instance: import tkinter as tk class App(tk.Tk): def __init__(self): super().__init__() frame = tk.Frame(self, bg="green", height=100, width=100) frame.bind("", self.print_event) frame.bind("", self.print_event) frame.bind("", self.print_event) frame.bind("", self.print_event) frame.bind("", self.print_event) frame.bind("", self.print_event) frame.pack(padx=50, pady=50) def print_event(self, event): position = "(x={}, y={})".format(event.x, event.y) print(event.type, "event", position) if __name__ == "__main__": app = App() app.mainloop()

All events are handled by the print_event() method of our class, which prints the type of event and the position of the mouse in the console. You can try it out by clicking on the green frame with the mouse, and moving it around while it starts printing the event messages. The following example contains an Entry widget with a couple of bindings; one for the event that gets triggered when the entry gets the focus, and another for all the key press events:

import tkinter as tk class App(tk.Tk): def __init__(self): super().__init__() entry = tk.Entry(self) entry.bind("", self.print_type) entry.bind("", self.print_key) entry.pack(padx=20, pady=20) def print_type(self, event): print(event.type) def print_key(self, event): args = event.keysym, event.keycode, event.char print("Symbol: {}, Code: {}, Char: {}".format(*args)) if __name__ == "__main__": app = App() app.mainloop()

The first message this program will output is the FocusIn event when you set the focus on the Entry widget. If you try it out, you will see that it will also show the events of keys that do not correspond to non-printable characters, such as arrow keys or the return key.

How it works... The bind method is defined in the widget class and takes three arguments, an event sequence, a callback function, and an optional add string: widget.bind(sequence, callback, add='')

The sequence string uses the syntax. In first place, modifiers are optional and allow you to specify additional combinations to the general type of the event: : When the user presses the Shift key

Shift

: When the user presses the Alt key

Alt

: When the user presses the Ctrl key

Control

: When the user presses the Shift lock

Lock

: When the event happens twice in quick succession

Double

: When the event happens thrice in quick succession

Triple

Event types determine the general type of event: or Button: Event generated when a mouse button is

ButtonPress

pressed : Event generated when a mouse button is

ButtonRelease

released : Event generated when you move the mouse over a

Enter

widget : Event generated when the mouse pointer leaves a widget Leave

: Event generated when the widget gets the input focus

FocusIn

: Event generated when the widget loses the input

FocusOut

focus or Key: Event generated when a key is pressed

KeyPress

: Event generated when a key is released

KeyRelease

: Event generated when the mouse is moved

Motion

The detail is also optional and serves to indicate the mouse button or key: For mouse events, 1 is the left button, 2 is the middle button, and 3 is the right button. For keyboard events, it is the key character. Special keys use the key symbol; some common examples are return, Tab, Esc, up, down, right, left, Backspace, and function keys (from F1 to F12). The callback function takes an event parameter. For mouse events, it has the following attributes: and y: Current mouse position in pixels

x

and y_root: Same as x and y, but relative to the left-upper corner of the screen x_root

: Mouse button number

num

For keyboard events, it contains these attributes: : Pressed character code as a string

char

: Pressed key symbol

keysym

: Pressed key code

keycode

In both cases, the event has the widget attribute, referencing the instance that generated the event, and type, which specifies the event type. We strongly recommend that you define methods for the callback functions since you will also have the reference to the class instance, and therefore you can easily access each of the widget attributes.

Finally, the add parameter can be '', to replace the callback function if there was a previous binding, or '+' to add the callback and preserve the old ones.

There's more... Apart from the event types described here, there are also other types that may be useful in some scenarios, such as the event that is generated when a widget is destroyed or the event that is sent when the size or position of the widget changes. You can check out the Tcl/Tk documentation for a complete list of event types at https://www.tcl.tk/man/tcl/TkCmd/bind.htm#M7.

See also The Structuring a Tkinter application recipe

Setting the main window's icon, title, and size The Tk instance differs from normal widgets in the way that it is configured, so we will explore some basic methods that allow us to customize how it is displayed.

How to do it... This snippet creates a main window with a custom title and icon. It has 400px of width by 200px of height, with a separation of 10px in each axis to the upper-left corner of the screen: import tkinter as tk class App(tk.Tk): def __init__(self): super().__init__() self.title("My Tkinter app") self.iconbitmap("python.ico") self.geometry("400x200+10+10") if __name__ == "__main__": app = App() app.mainloop()

This program assumes that you have a valid ICO file called python.ico in the same directory where the script is placed and executed.

How it works... The methods title() and iconbitmap() of the Tk class are very selfdescriptive—the first one sets the window title, whereas the second one takes the path to the icon that is associated to the window. The geometry() method configures the size of the window with a string that follows the following pattern: {width}x{height}+{offset_x}+{offset_y} In case you add more secondary windows to your application, these methods are also available in the Toplevel class.

There's more... If you want to make the application fullscreen, replace the call to the geometry() method with self.state("zoomed").

Window Layout In this chapter, we will cover the following recipes: Grouping widgets with frames Using the Pack geometry manager Using the Grid geometry manager Using the Place geometry manager Grouping inputs with the FrameLabel widget Dynamically laying out widgets Creating horizontal and vertical scrollbars

Introduction Widgets determine the actions that users can perform with our GUI application; however, we should pay attention to their placement and the relationships we establish with that arrangement. Effective layouts help users to identify the meaning and priority of each graphical element so that they can quickly understand how to interact with our program. Layout also determines the visual appearance that users expect to find consistently across the whole application, such as always placing confirmation buttons at the bottom-right corner of the screen. Although this information might be obvious to us as developers, end users may feel overwhelmed if we do not guide them through the application by following a natural order. This chapter will dive into the different mechanisms that Tkinter offers to lay out and group widgets and control other attributes, such as their size or spacing.

Grouping widgets with frames A frame represents a rectangular region of a window, typically used in complex layouts to contain other widgets. Since they have their own padding, border, and background, you can remark that the group of widgets is related logically. Another common pattern for frames is to encapsulate part of the application's functionality so that you can create an abstraction that hides the implementation details of child widgets. We will see an example that covers both scenarios by creating a component that inherits from the Frame class and exposes certain information on the containing widgets.

Getting ready We will build an application that contains two lists, where the first one has a list of items and the second one is initially empty. Both lists are scrollable, and you can move items between them with two central buttons that transfer the current selection:

How to do it… We will define a Frame subclass to represent a scrollable list, and then create two instances of this class. The two buttons will also be directly added to the main window: import tkinter as tk class ListFrame(tk.Frame): def __init__(self, master, items=[]): super().__init__(master) self.list = tk.Listbox(self) self.scroll = tk.Scrollbar(self, orient=tk.VERTICAL, command=self.list.yview) self.list.config(yscrollcommand=self.scroll.set) self.list.insert(0, *items) self.list.pack(side=tk.LEFT) self.scroll.pack(side=tk.LEFT, fill=tk.Y)

def pop_selection(self): index = self.list.curselection() if index: value = self.list.get(index) self.list.delete(index) return value def insert_item(self, item): self.list.insert(tk.END, item) class App(tk.Tk): def __init__(self): super().__init__() months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] self.frame_a = ListFrame(self, months) self.frame_b = ListFrame(self) self.btn_right = tk.Button(self, text=">", command=self.move_right) self.btn_left = tk.Button(self, text="

(Alejandro Rodas de Paz) Tkinter GUI Application D - PDFCOFFEE.COM (2024)
Top Articles
Latest Posts
Recommended Articles
Article information

Author: Domingo Moore

Last Updated:

Views: 6393

Rating: 4.2 / 5 (53 voted)

Reviews: 84% of readers found this page helpful

Author information

Name: Domingo Moore

Birthday: 1997-05-20

Address: 6485 Kohler Route, Antonioton, VT 77375-0299

Phone: +3213869077934

Job: Sales Analyst

Hobby: Kayaking, Roller skating, Cabaret, Rugby, Homebrewing, Creative writing, amateur radio

Introduction: My name is Domingo Moore, I am a attractive, gorgeous, funny, jolly, spotless, nice, fantastic person who loves writing and wants to share my knowledge and understanding with you.