Tkinter – Shishir Kant Singh https://shishirkant.com Jada Sir जाड़ा सर :) Mon, 17 Apr 2023 14:36:09 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.1 https://shishirkant.com/wp-content/uploads/2020/05/cropped-shishir-32x32.jpg Tkinter – Shishir Kant Singh https://shishirkant.com 32 32 187312365 Tkinter Matplotlib https://shishirkant.com/tkinter-matplotlib/?utm_source=rss&utm_medium=rss&utm_campaign=tkinter-matplotlib Mon, 17 Apr 2023 14:36:06 +0000 https://shishirkant.com/?p=3651 Display a bar chart from matplotlib in Tkinter applications

Matplotlib is a third-party library for creating professional visualizations in Python. Since Matplotlib is a third-party library, you need to install it before use.

To install the matplotlib package, you can use the following pip command:

pip install matplotlibCode language: Python (python)

The following program uses the matplotlib to create a bar chart that shows the top five programming languages by popularity.

import tkinter as tk
import matplotlib

matplotlib.use('TkAgg')

from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (
    FigureCanvasTkAgg,
    NavigationToolbar2Tk
)


class App(tk.Tk):
    def __init__(self):
        super().__init__()

        self.title('Tkinter Matplotlib Demo')

        # prepare data
        data = {
            'Python': 11.27,
            'C': 11.16,
            'Java': 10.46,
            'C++': 7.5,
            'C#': 5.26
        }
        languages = data.keys()
        popularity = data.values()

        # create a figure
        figure = Figure(figsize=(6, 4), dpi=100)

        # create FigureCanvasTkAgg object
        figure_canvas = FigureCanvasTkAgg(figure, self)

        # create the toolbar
        NavigationToolbar2Tk(figure_canvas, self)

        # create axes
        axes = figure.add_subplot()

        # create the barchart
        axes.bar(languages, popularity)
        axes.set_title('Top 5 Programming Languages')
        axes.set_ylabel('Popularity')

        figure_canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)


if __name__ == '__main__':
    app = App()
    app.mainloop()Code language: Python (python)

Output:

Tkinter Matplotlib

How it works.

First, import the matplotlib module

import matplotlibCode language: Python (python)

and call the use() function to tell which backend the matplotlib will use:

matplotlib.use('TkAgg')Code language: Python (python)

In this case, we use TkAgg backend, which is made to integrate into Tkinter.

Second, import the following FigureFigureCanvasTkAgg, and NavigationToolbar2Tk classes:

from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (
    FigureCanvasTkAgg,
    NavigationToolbar2Tk
)Code language: Python (python)

The Figure class represents the drawing area on which matplotlib charts will be drawn.

The FigureCanvasTkAgg class is an interface between the Figure and Tkinter Canvas.

The NavigationToolbar2Tk is a built-in toolbar for the figure on the graph.

Third, prepare the data for showing on the bar chart:

data = {
    'Python': 11.27,
    'C': 11.16,
    'Java': 10.46,
    'C++': 7.5,
    'C#': 5.26
}
languages = data.keys()
popularity = data.values()Code language: Python (python)

The data is a dictionary with the keys are the programming languages and values are their popularity in percentage.

Fourth, create a Figure to hold the chart:

figure = Figure(figsize=(6, 4), dpi=100)Code language: Python (python)

The Figure object takes two arguments: size in inches and dots per inch (dpi). In this example, it creates a 600×400 pixel figure.

Fifth, create a FigureCanvasTkAgg object that connects the Figure object with a Tkinter’s Canvas object:

figure_canvas = FigureCanvasTkAgg(figure, self)Code language: Python (python)

Note that the FigureCanvasTkAgg object is not a Canvas object but contains a Canvas object.

Sixth, create a matplotlib‘s built-in toolbar:

NavigationToolbar2Tk(figure_canvas, self)Code language: Python (python)

Seventh, add a subplot to the figure and return the axes of the subplot:

axes = figure.add_subplot()Code language: Python (python)

Eighth, create a bar chart by calling the bar() method of the axes and passing the languages and popularity into it. Also, set the title and the label of the y-axis:

axes.bar(languages, popularity)
axes.set_title('Top 5 Programming Languages')
axes.set_ylabel('Popularity')Code language: Python (python)

Finally, place the chart on the Tkinter’s root window:

figure_canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)Code language: Python (python)

Summary

  • Use matplotlib library to create professional-quality visualization in the Tkinter application.
]]>
3651
Tkinter Validation https://shishirkant.com/tkinter-validation/?utm_source=rss&utm_medium=rss&utm_campaign=tkinter-validation Mon, 17 Apr 2023 14:34:53 +0000 https://shishirkant.com/?p=3647 Introduction to the Tkinter validation

Tkinter validation relies on the three options that you can use for any input widget such as Entry widget:

  • validate: specifies which type of event will trigger the validation.
  • validatecommand: checks if a data is valid
  • invalidcommand: executes when the data is invalid. In other words, it’ll execute if the validatecommand returns False.

validate

The validate command can be one of the following string values:

‘focus’Validate whenever the widget gets or loses focus
‘focusin’Validate whenever the widget gets focus.
‘focusout’Validate whenever the widget loses focus
‘key’Validate whenever any keystroke changes the widget’s contents.
‘all’Validate in all the above situations focusing, focusout, and key
‘none’Turn the validation off. This is the default. Please note that the string 'none' is not the None value in Python.

validatecommand

The validatecommand is a tuple that contains:

  • A reference to a Tcl/tk function.
  • Zero or more substitution codes specify the information that triggers the event you want to pass into the function.

To get a reference to a Tck/tk function, you pass a callable to the widget.register() method. It returns a string that you can use with the validatecommand.

The following table shows the substitution codes that you can use with the tuple:

%d'Action code: 0 for an attempted deletion, 1 for an attempted insertion, or -1 if the callback was called for focus in, focus out, or a change to the textvariable.
'%i'When the user attempts to insert or delete text, this argument will be the index of the beginning of the insertion or deletion. If the callback was due to focus in, focus out, or a change to the textvariable, the argument will be -1.
'%P'The value that the text will have if the change is allowed.
'%s'The text in the entry before the change.
'%S'If the call was due to an insertion or deletion, this argument will be the text being inserted or deleted.
'%v'The current value of the widget’s validate option.
'%V'The reason for this callback: one of 'focusin''focusout''key', or 'forced' if the textvariable was changed.
'%W'The name of the widget.

The following example constructs a validatecommand that use the self.validate() method and %P substitution code:

vcmd = (self.register(self.validate), '%P')Code language: Python (python)

invalidcommand

Like the validatecommand, the invalidcommand also requires the use of the widget.register() method and substitution code.

The following example returns a tuple that you can pass into the invalidcommand option:

ivcmd = (self.register(self.on_invalid),)Code language: Python (python)

Tkinter validation example

We’ll create a form that contains an email input. If you enter an invalid email address, it’ll show an error message and change the text color of the email input to red. And we’ll trigger the validation event when the focus is moving out of the entry.

Here’s the complete program:

import tkinter as tk
from tkinter import ttk
import re


class App(tk.Tk):
    def __init__(self):
        super().__init__()

        self.title('Tkinter Validation Demo')

        self.create_widgets()

    def create_widgets(self):
        self.columnconfigure(0, weight=1)
        self.columnconfigure(1, weight=3)
        self.columnconfigure(2, weight=1)

        # label
        ttk.Label(text='Email:').grid(row=0, column=0, padx=5, pady=5)

        # email entry
        vcmd = (self.register(self.validate), '%P')
        ivcmd = (self.register(self.on_invalid),)

        self.email_entry = ttk.Entry(self, width=50)
        self.email_entry.config(validate='focusout', validatecommand=vcmd, invalidcommand=ivcmd)
        self.email_entry.grid(row=0, column=1, columnspan=2, padx=5)

        self.label_error = ttk.Label(self, foreground='red')
        self.label_error.grid(row=1, column=1, sticky=tk.W, padx=5)

        # button
        self.send_button = ttk.Button(text='Send').grid(row=0, column=4, padx=5)

    def show_message(self, error='', color='black'):
        self.label_error['text'] = error
        self.email_entry['foreground'] = color

    def validate(self, value):
        """
        Validat the email entry
        :param value:
        :return:
        """
        pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
        if re.fullmatch(pattern, value) is None:
            return False

        self.show_message()
        return True

    def on_invalid(self):
        """
        Show the error message if the data is not valid
        :return:
        """
        self.show_message('Please enter a valid email', 'red')


if __name__ == '__main__':
    app = App()
    app.mainloop()Code language: Python (python)

How it works.

First, create a validate command using the self.validate() method and %P substitution code:

vcmd = (self.register(self.validate), '%P')Code language: Python (python)

Second, create the invalidatecommand that uses the self.on_invalid method:

ivcmd = (self.register(self.on_invalid),)Code language: Python (python)

Third, configure the entry widget that uses validationvalidatecommand , and invalidatecommand:

self.email_entry.config(validate='focusout', validatecommand=vcmd, invalidcommand=ivcmd)Code language: Python (python)

Fourth, define the show_message() method that changes the text of the label_error widget and the text color of the email_entry widget:

def show_message(self, error='', color='black'):
    self.label_error['text'] = error
    self.email_entry['foreground'] = colorCode language: Python (python)

Fifth, define the validate() method that validates the value of the email_entry.

def validate(self, value):
    """
    Validat the email entry
    :param value:
    :return:
    """
    pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
    if re.fullmatch(pattern, value) is None:
        return False

    self.show_message()
    return TrueCode language: Python (python)

The validate() method returns True if the input text is valid or False otherwise. In case the input text is a valid email address, call the show_message() to hide the error message and set the text color to black.

Tkinter will execute the on_invalid() method if the input text is not a valid email address.

Finally, define the on_invalid() method that shows an error message and set the text color of the email_entry widget to red.

def on_invalid(self):
    """
    Show the error message if the data is not valid
    :return:
    """
    self.show_message('Please enter a valid email', 'red')Code language: Python (python)

Summary

  • Tkinter uses the validatevalidatecommand, and invalidcommand options on any input widget to validate data.
  • Pass a callable to the widget.register() method to create a command for the validatecommand and invalidcommand options.
  • validationcommand returns True if the data is valid or False otherwise.
  • invalidcommand will execute if the data is not valid, or when the validatecommand return False.
]]>
3647
Tkinter MVC https://shishirkant.com/tkinter-mvc/?utm_source=rss&utm_medium=rss&utm_campaign=tkinter-mvc Mon, 17 Apr 2023 14:33:21 +0000 https://shishirkant.com/?p=3643 Introduction to Tkinter MVC

As your application grows, its complexity also increases. To make the application more manageable, you can use the model-view-controller design pattern.

The MVC design pattern allows you to divide the application into three main components: model, view, and controller. This structure helps you focus on the logic of each part and make it more maintainable, especially when the application grows.

The following diagram illustrates the MVC design pattern:

Model

A model in MVC represents the data. A model deals with getting data from or writing data into storage such as a database or file. The model may also contain the logic to validate the data to ensure data integrity.

The model must not depend on the view and controller. In other words, you can reuse the model in other non-Tkinter applications such as web and mobile apps.

View

A view is the user interface that represents the data in the model. The view doesn’t directly communicate with the model. Ideally, a view should have very little logic to display data.

The view communicates with the controller directly. In Tinker applications, the view is the root window that consists of widgets.

Controller

A controller acts as the intermediary between the views and models. The controller routes data between the views and models.

For example, if users click the save button on the view, the controller routes the “save” action to the model to save the data into a database and notify the view to display a message.

Tkinter MVC example

We’ll take a simple example to illustrate how to apply the MVC design pattern in a Tkinter application:

The application that you’ll build contains an entry for entering an email. When you click the save button, the controller calls the model to validate the email.

If the email is valid, the model saves the email into a text file and the view shows a success message:

If the email is not valid, the view shows an error message:

We’ll hide the message after 3 seconds.

Model class

The following defines the Model class that has an email property:

class Model:
    def __init__(self, email):
        self.email = email

    @property
    def email(self):
        return self.__email

    @email.setter
    def email(self, value):
        """
        Validate the email
        :param value:
        :return:
        """
        pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
        if re.fullmatch(pattern, value):
            self.__email = value
        else:
            raise ValueError(f'Invalid email address: {value}')

    def save(self):
        """
        Save the email into a file
        :return:
        """
        with open('emails.txt', 'a') as f:
            f.write(self.email + '\n')Code language: Python (python)

How it works:

  • The email setter validates the email before assigning it to the __email attribute. If the email is not valid, it’ll raise a ValueError.
  • The save() method writes the email into a simple text file. In real-world applications, you may want to save it into a database.

View

The following defines the view that shows a form to input an email:

class View(ttk.Frame):
    def __init__(self, parent):
        super().__init__(parent)

        # create widgets
        # label
        self.label = ttk.Label(self, text='Email:')
        self.label.grid(row=1, column=0)

        # email entry
        self.email_var = tk.StringVar()
        self.email_entry = ttk.Entry(self, textvariable=self.email_var, width=30)
        self.email_entry.grid(row=1, column=1, sticky=tk.NSEW)

        # save button
        self.save_button = ttk.Button(self, text='Save', command=self.save_button_clicked)
        self.save_button.grid(row=1, column=3, padx=10)

        # message
        self.message_label = ttk.Label(self, text='', foreground='red')
        self.message_label.grid(row=2, column=1, sticky=tk.W)

        # set the controller
        self.controller = None

    def set_controller(self, controller):
        """
        Set the controller
        :param controller:
        :return:
        """
        self.controller = controller

    def save_button_clicked(self):
        """
        Handle button click event
        :return:
        """
        if self.controller:
            self.controller.save(self.email_var.get())

    def show_error(self, message):
        """
        Show an error message
        :param message:
        :return:
        """
        self.message_label['text'] = message
        self.message_label['foreground'] = 'red'
        self.message_label.after(3000, self.hide_message)
        self.email_entry['foreground'] = 'red'

    def show_success(self, message):
        """
        Show a success message
        :param message:
        :return:
        """
        self.message_label['text'] = message
        self.message_label['foreground'] = 'green'
        self.message_label.after(3000, self.hide_message)

        # reset the form
        self.email_entry['foreground'] = 'black'
        self.email_var.set('')

    def hide_message(self):
        """
        Hide the message
        :return:
        """
        self.message_label['text'] = ''Code language: Python (python)

How it works.

  • First, create the widgets in the __init__() method.
  • Second, define the set_controller() method to set a controller.
  • Third, call the save() method of the controller in the click event handler of the save button.
  • Finally, define the show_error()show_success(), and hide_message() methods to show/hide the message.

Controller

The following defines a controller:

class Controller:
    def __init__(self, model, view):
        self.model = model
        self.view = view

    def save(self, email):
        """
        Save the email
        :param email:
        :return:
        """
        try:

            # save the model
            self.model.email = email
            self.model.save()

            # show a success message
            self.view.show_success(f'The email {email} saved!')

        except ValueError as error:
            # show an error message
            self.view.show_error(error)Code language: Python (python)

How the controller works.

  • First, assign the model and view in the __init__() method
  • Second, define the save() method that saves the model into the text file. If the model is saved successfully, show a success message. Otherwise, display an error message.

Application

The following defines the App class that uses the Model, View, and Controller classes:

class App(tk.Tk):
    def __init__(self):
        super().__init__()

        self.title('Tkinter MVC Demo')

        # create a model
        model = Model('hello@pythontutorial.net')

        # create a view and place it on the root window
        view = View(self)
        view.grid(row=0, column=0, padx=10, pady=10)

        # create a controller
        controller = Controller(model, view)

        # set the controller to view
        view.set_controller(controller)


if __name__ == '__main__':
    app = App()
    app.mainloop()Code language: Python (python)

How it works.

  • First, create a model.
  • Second, create a view and place it on the root window.
  • Third, create a controller and set it to the view.

Put it all together.

import re
import tkinter as tk
from tkinter import ttk


class Model:
    def __init__(self, email):
        self.email = email

    @property
    def email(self):
        return self.__email

    @email.setter
    def email(self, value):
        """
        Validate the email
        :param value:
        :return:
        """
        pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
        if re.fullmatch(pattern, value):
            self.__email = value
        else:
            raise ValueError(f'Invalid email address: {value}')

    def save(self):
        """
        Save the email into a file
        :return:
        """
        with open('emails.txt', 'a') as f:
            f.write(self.email + '\n')

class View(ttk.Frame):
    def __init__(self, parent):
        super().__init__(parent)

        # create widgets
        # label
        self.label = ttk.Label(self, text='Email:')
        self.label.grid(row=1, column=0)

        # email entry
        self.email_var = tk.StringVar()
        self.email_entry = ttk.Entry(self, textvariable=self.email_var, width=30)
        self.email_entry.grid(row=1, column=1, sticky=tk.NSEW)

        # save button
        self.save_button = ttk.Button(self, text='Save', command=self.save_button_clicked)
        self.save_button.grid(row=1, column=3, padx=10)

        # message
        self.message_label = ttk.Label(self, text='', foreground='red')
        self.message_label.grid(row=2, column=1, sticky=tk.W)

        # set the controller
        self.controller = None

    def set_controller(self, controller):
        """
        Set the controller
        :param controller:
        :return:
        """
        self.controller = controller

    def save_button_clicked(self):
        """
        Handle button click event
        :return:
        """
        if self.controller:
            self.controller.save(self.email_var.get())

    def show_error(self, message):
        """
        Show an error message
        :param message:
        :return:
        """
        self.message_label['text'] = message
        self.message_label['foreground'] = 'red'
        self.message_label.after(3000, self.hide_message)
        self.email_entry['foreground'] = 'red'

    def show_success(self, message):
        """
        Show a success message
        :param message:
        :return:
        """
        self.message_label['text'] = message
        self.message_label['foreground'] = 'green'
        self.message_label.after(3000, self.hide_message)

        # reset the form
        self.email_entry['foreground'] = 'black'
        self.email_var.set('')

    def hide_message(self):
        """
        Hide the message
        :return:
        """
        self.message_label['text'] = ''            


class Controller:
    def __init__(self, model, view):
        self.model = model
        self.view = view

    def save(self, email):
        """
        Save the email
        :param email:
        :return:
        """
        try:

            # save the model
            self.model.email = email
            self.model.save()

            # show a success message
            self.view.show_success(f'The email {email} saved!')

        except ValueError as error:
            # show an error message
            self.view.show_error(error)        

class App(tk.Tk):
    def __init__(self):
        super().__init__()

        self.title('Tkinter MVC Demo')

        # create a model
        model = Model('hello@pythontutorial.net')

        # create a view and place it on the root window
        view = View(self)
        view.grid(row=0, column=0, padx=10, pady=10)

        # create a controller
        controller = Controller(model, view)

        # set the controller to view
        view.set_controller(controller)


if __name__ == '__main__':
    app = App()
    app.mainloop()            Code language: Python (python)

Summary

  • Use MVC to structure the Tkinter applications to make them more organized.
]]>
3643
Tkinter Thread Progressbar https://shishirkant.com/tkinter-thread-progressbar/?utm_source=rss&utm_medium=rss&utm_campaign=tkinter-thread-progressbar Mon, 17 Apr 2023 14:31:46 +0000 https://shishirkant.com/?p=3639 This tutorial assumes that you know how to use the after() method and understand how threadings work in Python. Also, you should know how to switch between frames using the tkraise() method.

In this tutorial, you’ll build a picture viewer that shows a random picture from unsplash.com using its API.

If you make an HTTP request to the following API endpoint:

https://source.unsplash.com/random/640x480Code language: Python (python)

…you’ll get a random picture with the size of 640×480.

The following picture shows the final Image Viewer application:

When you click the Next Picture button, the program calls the API from unsplash.com to download a random picture and displays it on the window.

It’ll also show a progress bar while the picture is downloading, indicating that the download is in progress:

To call the API, you use the requests module.

First, install the requests module if it’s not available on your computer:

pip install requestsCode language: Python (python)

Second, define a new class that inherits from the Thread class:

class PictureDownload(Thread):
    def __init__(self, url):
        super().__init__()

        self.picture_file = None
        self.url = url

    def run(self):
        """ download a picture and save it to a file """
        # download the picture
        response = requests.get(self.url, proxies=proxyDict)
        picture_name = self.url.split('/')[-1]
        picture_file = f'./assets/{picture_name}.jpg'

        # save the picture to a file
        with open(picture_file, 'wb') as f:
            f.write(response.content)

        self.picture_file = picture_file
Code language: Python (python)

In this PictureDownload class, the run() method calls the API using the requests module.

The run() method downloads a picture and saves it to the /assets/ folder. Also, it assigns the path of the downloaded picture to the picture_file instance attribute.

Third, define an App class that inherits the Tk class. The App class represents the root window.

The root window has two frames, one for displaying the Progressbar and the other for showing the Canvas which holds the downloaded picture:

def __init__(self, canvas_width, canvas_height):
    super().__init__()
    self.resizable(0, 0)
    self.title('Image Viewer')

    # Progress frame
    self.progress_frame = ttk.Frame(self)

    # configrue the grid to place the progress bar is at the center
    self.progress_frame.columnconfigure(0, weight=1)
    self.progress_frame.rowconfigure(0, weight=1)

    # progressbar
    self.pb = ttk.Progressbar(
        self.progress_frame, orient=tk.HORIZONTAL, mode='indeterminate')
    self.pb.grid(row=0, column=0, sticky=tk.EW, padx=10, pady=10)

    # place the progress frame
    self.progress_frame.grid(row=0, column=0, sticky=tk.NSEW)

    # Picture frame
    self.picture_frame = ttk.Frame(self)

    # canvas width & height
    self.canvas_width = canvas_width
    self.canvas_height = canvas_height

    # canvas
    self.canvas = tk.Canvas(
        self.picture_frame,
        width=self.canvas_width,
        height=self.canvas_height)
    self.canvas.grid(row=0, column=0)

    self.picture_frame.grid(row=0, column=0)
Code language: Python (python)

When you click the Next Picture button, the handle_download() method executes:

def handle_download(self):
    """ Download a random photo from unsplash """
    self.start_downloading()

    url = 'https://source.unsplash.com/random/640x480'
    download_thread = PictureDownload(url)
    download_thread.start()

    self.monitor(download_thread)
Code language: Python (python)

The handle_download() method shows the progress frame by calling the start_downloading() method and starts the progress bar:

def start_downloading(self):
    self.progress_frame.tkraise()
    self.pb.start(20)Code language: Python (python)

It also creates a new thread that downloads the random picture and calls the monitor() method to monitor the status of the thread.

The following shows the monitor() method:

def monitor(self, download_thread):
    """ Monitor the download thread """
    if download_thread.is_alive():
        self.after(100, lambda: self.monitor(download_thread))
    else:
        self.stop_downloading()
        self.set_picture(download_thread.picture_file)Code language: Python (python)

The monitor() method checks the status of the thread. If the thread is running, it schedules another check after 100ms.

Otherwise, the monitor() method calls the stop_downloading() method to stop the progressbar, display the picture frame, and show the image.

The following shows the stop_downloading() method:

def stop_downloading(self):
    self.picture_frame.tkraise()
    self.pb.stop()   Code language: Python (python)

The following shows the complete Image Viewer program:

import requests
import tkinter as tk
from threading import Thread
from PIL import Image, ImageTk
from tkinter import ttk
from proxies import proxyDict


class PictureDownload(Thread):
    def __init__(self, url):
        super().__init__()

        self.picture_file = None
        self.url = url

    def run(self):
        """ download a picture and save it to a file """
        # download the picture
        response = requests.get(self.url, proxies=proxyDict)
        picture_name = self.url.split('/')[-1]
        picture_file = f'./assets/{picture_name}.jpg'

        # save the picture to a file
        with open(picture_file, 'wb') as f:
            f.write(response.content)

        self.picture_file = picture_file


class App(tk.Tk):
    def __init__(self, canvas_width, canvas_height):
        super().__init__()
        self.resizable(0, 0)
        self.title('Image Viewer')

        # Progress frame
        self.progress_frame = ttk.Frame(self)

        # configrue the grid to place the progress bar is at the center
        self.progress_frame.columnconfigure(0, weight=1)
        self.progress_frame.rowconfigure(0, weight=1)

        # progressbar
        self.pb = ttk.Progressbar(
            self.progress_frame, orient=tk.HORIZONTAL, mode='indeterminate')
        self.pb.grid(row=0, column=0, sticky=tk.EW, padx=10, pady=10)

        # place the progress frame
        self.progress_frame.grid(row=0, column=0, sticky=tk.NSEW)

        # Picture frame
        self.picture_frame = ttk.Frame(self)

        # canvas width & height
        self.canvas_width = canvas_width
        self.canvas_height = canvas_height

        # canvas
        self.canvas = tk.Canvas(
            self.picture_frame,
            width=self.canvas_width,
            height=self.canvas_height)
        self.canvas.grid(row=0, column=0)

        self.picture_frame.grid(row=0, column=0)

        # Button
        btn = ttk.Button(self, text='Next Picture')
        btn['command'] = self.handle_download
        btn.grid(row=1, column=0)

    def start_downloading(self):
        self.progress_frame.tkraise()
        self.pb.start(20)

    def stop_downloading(self):
        self.picture_frame.tkraise()
        self.pb.stop()

    def set_picture(self, file_path):
        """ Set the picture to the canvas """
        pil_img = Image.open(file_path)

        # resize the picture
        resized_img = pil_img.resize(
            (self.canvas_width, self.canvas_height),
            Image.ANTIALIAS)

        self.img = ImageTk.PhotoImage(resized_img)

        # set background image
        self.bg = self.canvas.create_image(
            0,
            0,
            anchor=tk.NW,
            image=self.img)

    def handle_download(self):
        """ Download a random photo from unsplash """
        self.start_downloading()

        url = 'https://source.unsplash.com/random/640x480'
        download_thread = PictureDownload(url)
        download_thread.start()

        self.monitor(download_thread)

    def monitor(self, download_thread):
        """ Monitor the download thread """
        if download_thread.is_alive():
            self.after(100, lambda: self.monitor(download_thread))
        else:
            self.stop_downloading()
            self.set_picture(download_thread.picture_file)


if __name__ == '__main__':
    app = App(640, 480)
    app.mainloop()
Code language: Python (python)

In this tutorial, you’ve learned how to display a progressbar that connects to a running thread to indicate that an operation is still in progress.

]]>
3639
Tkinter Thread https://shishirkant.com/tkinter-thread/?utm_source=rss&utm_medium=rss&utm_campaign=tkinter-thread Mon, 17 Apr 2023 14:27:29 +0000 https://shishirkant.com/?p=3635 When to use Thread in Tkinter applications

In a Tkinter application, the main loop should always start in the main thread. It’s responsible for handling events and updating the GUI.

If you have a background operation that takes time, you should execute it in a separate thread.

Otherwise, the application won’t be responsive. In the worst case, it will freeze while the operation is running.

To create and control multiple threads in Tkinter applications, you can use the Python threading module.

The threading module is included in Python’s standard library so you don’t need to install it.

For more information on how to use the threading module, you can follow the Python threading tutorial.

Tkinter thread example

We’ll build a simple program that downloads a webpage specified by an URL and displays its contents in a Text widget:

To download a webpage, we’ll use the requests module.

First, install the requests module by executing the following command:

pip install requestsCode language: Python (python)

Next, import tkinterthreading, and requests modules:

import tkinter as tk
from tkinter import ttk
from tkinter.messagebox import showerror
from threading import Thread
import requestsCode language: Python (python)

Then, define a new class called AsyncDownload that inherits from the Thread class:

class AsyncDownload(Thread):
    def __init__(self, url):
        super().__init__()
        self.html = None
        self.url = url

    def run(self):
        response = requests.get(self.url)
        self.html = response.textCode language: Python (python)

How the AsyncDownload class works:

  • In the __init__() method of the AsyncDownload class, we initialize the html and url attributes.
  • In the run() method, we call the get the get() function to download the webpage specified by the URL and assign the HTML source code to the html attribute.

After that, create the App class inherits from the Tk class. The App class represents the root window.

The root window consists of three frames that hold all the widgets. We won’t focus on how to create widgets and place them on the window using the grid geometry manager.

When you click the download button, the program executes the handle_download() method of the App class.

In the handle_download() method, we check if the url is provided. If yes, we create a new instance of the AsyncDownload class and start the thread. Also, we disable the download button and clear the contents of the Text widget.

In addition, we call the monitor() method to monitor the status of the thread.

def handle_download(self):
    url = self.url_var.get()
    if url:
        self.download_button['state'] = tk.DISABLED
        self.html.delete(1.0, "end")

        download_thread = AsyncDownload(url)
        download_thread.start()

        self.monitor(download_thread)
    else:
        showerror(title='Error',
                message='Please enter the URL of the webpage.')
Code language: Python (python)

In the monitor() method, we schedule an action that will run the monitor() method after 100ms if the thread is still alive.

If the download completed, we update the contents for the Entry widget and re-enable the download button:

def monitor(self, thread):
    if thread.is_alive():
        # check the thread every 100ms
        self.after(100, lambda: self.monitor(thread))
    else:
        self.html.insert(1.0, thread.html)
        self.download_button['state'] = tk.NORMAL
Code language: Python (python)

Finally, run the application’s main loop:

if __name__ == "__main__":
    app = App()
    app.mainloop()Code language: Python (python)

The following show the complete program:

import tkinter as tk
from tkinter import ttk
from tkinter.messagebox import showerror
from threading import Thread
import requests


class AsyncDownload(Thread):
    def __init__(self, url):
        super().__init__()

        self.html = None
        self.url = url

    def run(self):
        response = requests.get(self.url)
        self.html = response.text


class App(tk.Tk):
    def __init__(self):
        super().__init__()

        self.title('Webpage Download')
        self.geometry('680x430')
        self.resizable(0, 0)

        self.create_header_frame()
        self.create_body_frame()
        self.create_footer_frame()

    def create_header_frame(self):

        self.header = ttk.Frame(self)
        # configure the grid
        self.header.columnconfigure(0, weight=1)
        self.header.columnconfigure(1, weight=10)
        self.header.columnconfigure(2, weight=1)
        # label
        self.label = ttk.Label(self.header, text='URL')
        self.label.grid(column=0, row=0, sticky=tk.W)

        # entry
        self.url_var = tk.StringVar()
        self.url_entry = ttk.Entry(self.header,
                                   textvariable=self.url_var,
                                   width=80)

        self.url_entry.grid(column=1, row=0, sticky=tk.EW)

        # download button
        self.download_button = ttk.Button(self.header, text='Download')
        self.download_button['command'] = self.handle_download
        self.download_button.grid(column=2, row=0, sticky=tk.E)

        # attach the header frame
        self.header.grid(column=0, row=0, sticky=tk.NSEW, padx=10, pady=10)

    def handle_download(self):
        url = self.url_var.get()
        if url:
            self.download_button['state'] = tk.DISABLED
            self.html.delete(1.0, "end")

            download_thread = AsyncDownload(url)
            download_thread.start()

            self.monitor(download_thread)
        else:
            showerror(title='Error',
                      message='Please enter the URL of the webpage.')

    def monitor(self, thread):
        if thread.is_alive():
            # check the thread every 100ms
            self.after(100, lambda: self.monitor(thread))
        else:
            self.html.insert(1.0, thread.html)
            self.download_button['state'] = tk.NORMAL

    def create_body_frame(self):
        self.body = ttk.Frame(self)
        # text and scrollbar
        self.html = tk.Text(self.body, height=20)
        self.html.grid(column=0, row=1)

        scrollbar = ttk.Scrollbar(self.body,
                                  orient='vertical',
                                  command=self.html.yview)

        scrollbar.grid(column=1, row=1, sticky=tk.NS)
        self.html['yscrollcommand'] = scrollbar.set

        # attach the body frame
        self.body.grid(column=0, row=1, sticky=tk.NSEW, padx=10, pady=10)

    def create_footer_frame(self):
        self.footer = ttk.Frame(self)
        # configure the grid
        self.footer.columnconfigure(0, weight=1)
        # exit button
        self.exit_button = ttk.Button(self.footer,
                                      text='Exit',
                                      command=self.destroy)

        self.exit_button.grid(column=0, row=0, sticky=tk.E)

        # attach the footer frame
        self.footer.grid(column=0, row=2, sticky=tk.NSEW, padx=10, pady=10)


if __name__ == "__main__":
    app = App()
    app.mainloop()Code language: Python (python)

Summary

  • Do execute background tasks in separate threads to make the Tkinter application responsive.
]]>
3635
How to Schedule an Action with Tkinter after() method https://shishirkant.com/how-to-schedule-an-action-with-tkinter-after-method/?utm_source=rss&utm_medium=rss&utm_campaign=how-to-schedule-an-action-with-tkinter-after-method Mon, 17 Apr 2023 14:25:45 +0000 https://shishirkant.com/?p=3631 Introduction to Tkinter after() method

All Tkinter widgets have the after() method with the following syntax:

widget.after(delay, callback=None)Code language: Python (python)

The after() method calls the callback function once after a delay milliseconds (ms) within Tkinter’s main loop.

If you don’t provide the callback, the after() method behaves like the time.sleep() function. However, the after() method uses the millisecond instead of the second as the unit.

Tkinter after() method example

Let’s see the following program:

import tkinter as tk
from tkinter import ttk
import time


class App(tk.Tk):
    def __init__(self):
        super().__init__()

        self.title('Tkinter after() Demo')
        self.geometry('300x100')

        self.style = ttk.Style(self)

        self.button = ttk.Button(self, text='Wait 3 seconds')
        self.button['command'] = self.start
        self.button.pack(expand=True, ipadx=10, ipady=5)

    def start(self):
        self.change_button_color('red')
        time.sleep(3)
        self.change_button_color('black')

    def change_button_color(self, color):
        self.style.configure('TButton', foreground=color)


if __name__ == "__main__":
    app = App()
    app.mainloop()Code language: Python (python)

The program consists of a button. When you click the button:

  • First, the color of the button turns red.
  • Then, the program sleeps for 3 seconds.
  • Finally, the color of the button turns black.

However, when you run the program and click the button, you’ll notice that the color of the button doesn’t change at all. Also, the window freezes for 3 seconds like this:

The reason was that the sleep() function suspended the main thread execution. Therefore, Tkinter couldn’t update the GUI.

To fix the issue, you can use the after() method to schedule the action that updates the color of the button instead of suspending the main thread execution. For example:

import tkinter as tk
from tkinter import ttk
import time


class App(tk.Tk):
    def __init__(self):
        super().__init__()

        self.title('Tkinter after() Demo')
        self.geometry('300x100')

        self.style = ttk.Style(self)

        self.button = ttk.Button(self, text='Wait 3 seconds')
        self.button['command'] = self.start
        self.button.pack(expand=True, ipadx=10, ipady=5)

    def start(self):
        self.change_button_color('red')
        self.after(3000,lambda: self.change_button_color('black'))


    def change_button_color(self, color):
        self.style.configure('TButton', foreground=color)
        print(color)


if __name__ == "__main__":
    app = App()
    app.mainloop()Code language: Python (python)

Output:

Why use Tkinter after() method

A Python program can have one or multiple threads. When you launch a Tkinter application, it executes in the main thread.

The Tkinter’s main loop must start from the main thread. It’s responsible for handling events and updating GUI.

If you start a long-running task in the main thread, the GUI will freeze and don’t respond to user events.

To prevent a long-running task from blocking the main thread, you can schedule an action that won’t be executed earlier than a specified time by using the after() method.

Tkinter will execute the callback in the main thread when the main thread is not busy.

A practical Tkinter after() method example

The following program displays a digital clock. It uses the after() method to update the current time every second:

import tkinter as tk
from tkinter import ttk
import time


class DigitalClock(tk.Tk):
    def __init__(self):
        super().__init__()

        # configure the root window
        self.title('Digital Clock')
        self.resizable(0, 0)
        self.geometry('250x80')
        self['bg'] = 'black'

        # change the background color to black
        self.style = ttk.Style(self)
        self.style.configure(
            'TLabel',
            background='black',
            foreground='red')

        # label
        self.label = ttk.Label(
            self,
            text=self.time_string(),
            font=('Digital-7', 40))

        self.label.pack(expand=True)

        # schedule an update every 1 second
        self.label.after(1000, self.update)

    def time_string(self):
        return time.strftime('%H:%M:%S')

    def update(self):
        """ update the label every 1 second """

        self.label.configure(text=self.time_string())

        # schedule another timer
        self.label.after(1000, self.update)


if __name__ == "__main__":
    clock = DigitalClock()
    clock.mainloop()
Code language: Python (python)

Output:

How it works.

The following method returns the current time in the string format:

def time_string(self):
    return time.strftime('%H:%M:%S')Code language: Python (python)

The __init__() method uses the after() method to schedule an action that updates the current time to the label every second:

self.label.after(1000, self.update)Code language: Python (python)

In the update() method, update the current time to the label, and schedule another update after one second:

def update(self):
    """ update the label every 1 second """

    self.label.configure(text=self.time_string())

    # schedule another timer
    self.label.after(1000, self.update)Code language: Python (python)

Note that this program uses the Digital 7 font from the 1001fonts.com

Summary

  • Use the Tkinter after() method to schedule an action that will run after a timeout has elapsed
  • The callback passed into the after() method still runs in the main thread. Therefore, you should avoid performing the long-running task using the after() method.
]]>
3631
Tkinter Style map() Method https://shishirkant.com/tkinter-style-map-method/?utm_source=rss&utm_medium=rss&utm_campaign=tkinter-style-map-method Mon, 17 Apr 2023 14:23:50 +0000 https://shishirkant.com/?p=3627 Typically, a tkinter widget allows you to change its appearance based on a specific state.

The following table shows a list of widget states and their meanings:

StateMeaning
activeThe mouse is currently within the widget.
alternateTtk reserved this state for application use.
backgroundThe widget is on a window that is not the foreground window. The foreground window is a window that is getting user inputs. This state is only relevant to Windows and macOS.
disabledThe widget won’t respond to any actions.
focusThe widget currently has focus.
invalidThe value of the widget is currently invalid.
pressedThe widget is currently being clicked or pressed e.g. when a Button widget is pressed.
readonlyThe readonly widget prevents you from changing its current value e.g., a read-only Entry widget won’t allow you to change its text contents.
selectedThe widget is selected e.g. when radio buttons are checked.

To change the appearance of a widget dynamically, you can use the map() method of the Style object:

style.map(style_name, query)Code language: Python (python)

The map() method accepts the first argument as the name of the style e.g., TButton and TLabel.

The argument query is a list of keyword arguments where each key is a style option and values are lists of tuples of (state,value).

For example, the following code dynamically changes the foreground color of a button widget:

import tkinter as tk
from tkinter import ttk


class App(tk.Tk):
    def __init__(self):
        super().__init__()

        self.geometry('300x100')

        button = ttk.Button(self, text='Save')
        button.pack(expand=True)

        style = ttk.Style(self)
        style.configure('TButton', font=('Helvetica', 16))
        style.map('TButton',
                foreground=[('pressed', 'blue'),
                            ('active', 'red')])

        print(style.layout('TButton'))


if __name__ == "__main__":
    app = App()
    app.mainloop()Code language: Python (python)

In this example, when you move focus to the button, its text color changes to red. And when you click or press the button, its text color turns to blue.

Output:

Summary

  • Use the style.map() method to dynamically change the appearance of a widget based on its specific state.
]]>
3627
Tkinter Elements https://shishirkant.com/tkinter-elements/?utm_source=rss&utm_medium=rss&utm_campaign=tkinter-elements Mon, 17 Apr 2023 14:21:41 +0000 https://shishirkant.com/?p=3623 Introduction to ttk elements

So far, you have learned that a theme is a collection of styles that defines the appearance of all Tkinter widgets.

A style is a description of the appearance of a widget class. A style is composed of one or more elements.

For example, a Label consists of borderpadding and label elements. And these elements are nested within each other like the following picture:

In general, most of the built-in ttk styles use the concept of a layout to organize the different element layers that build up a widget.

To get the layout of a widget class, you use the layout() method of the Style object like this:

style.layout(widget_class)Code language: Python (python)

If a widget class doesn’t have a layout, the layout() method will raise a tk.TclError exception.

The layout() method returns a list of tuples (element_namedescription), where:

  • element_name is the name of the element.
  • description is a dictionary that describes the element.

The following example uses the layout() method to get the layout of the TLabel widget class:

import tkinter as tk
from tkinter import ttk


class App(tk.Tk):
    def __init__(self):
        super().__init__()

        style = ttk.Style(self)

        layout = style.layout('TLabel')
        print(layout)


if __name__ == "__main__":
    app = App()
    app.mainloop()Code language: Python (python)

The following output shows the style’s layout of the TLabel:

[('Label.border',
    {'sticky': 'nswe',
     'border': '1',
     'children': [('Label.padding',
                   {'sticky': 'nswe',
                    'border': '1',
                    'children': [('Label.label',
                                  {'sticky': 'nswe'})]
                    })]
     }
)]Code language: Python (python)

The TLabel has three elements nested within each other:

  • The Label.border is the outermost element that has the stickyborder, and children keys.
  • The Label.padding is nested inside the Label.border. It also has the stickyborder, and children keys.
  • The Label.label is the innermost element that has only one sticky key.

For example, when an element has a sticky key with the value of nswe, it would be stretched to adhere to the north, south, west, and east of the parent element.

Note that the style’s layout of a widget’s class depends on the current theme. If you change the theme, the layout may be different.

Element options

Each element has a list of options that specify the appearance of the element. To get the list of option names, you use the element_options() method of Style object:

style.element_options(styleName)Code language: Python (python)

The following program shows the element options of the Label.borderLabel.padding, and Label.label elements:

import tkinter as tk
from tkinter import ttk


class App(tk.Tk):
    def __init__(self):
        super().__init__()

        style = ttk.Style(self)

        # layout
        layout = style.layout('TLabel')
        print(layout)

        # element options
        print(style.element_options('Label.border'))
        print(style.element_options('Label.padding'))
        print(style.element_options('Label.label'))


if __name__ == "__main__":
    app = App()
    app.mainloop()Code language: Python (python)

Output:

('relief',)
('padding', 'relief', 'shiftrelief')
('compound', 'space', 'text', 'font', 'foreground', 'underline', 'width', 'anchor', 'justify', 'wraplength', 'embossed', 'image', 'stipple', 'background')Code language: Python (python)

In this output:

  • The Label.border element has one option: 'relief'.
  • The Label.padding element has three options: 'padding''relief', and 'shiftrelief'.
  • The Label.label element has many options including 'font''foreground''with', etc.

Attributes of element options

To get a list of attributes associated with an element option, you use the lookup() method of the Style object:

style.lookup(layout_name, option_name)Code language: Python (python)

The following example shows the attributes of the fontforeground, and background options in the TLabel.label element:

import tkinter as tk
from tkinter import ttk


class App(tk.Tk):
    def __init__(self):
        super().__init__()

        style = ttk.Style(self)

        # attributes of the font, foreground, and background
        # of the Label.label element
        print(style.lookup('Label.label', 'font'))
        print(style.lookup('Label.label', 'foreground'))
        print(style.lookup('Label.label', 'background'))


if __name__ == "__main__":
    app = App()
    app.mainloop()Code language: Python (python)

Output:

TkDefaultFont
SystemWindowText
SystemButtonFaceCode language: Python (python)

As you can see clearly from the output, the font is TkDefaultFont, the foreground is SystemWindowText, and the background is SystemButtonFace.

Put it all together

The following shows how to change the appearance of a Label widget:

import tkinter as tk
from tkinter import ttk


class App(tk.Tk):
    def __init__(self):
        super().__init__()

        self.geometry('500x100')

        message = 'This is an error message!'

        label = ttk.Label(self, text=message, style='Error.TLabel')
        label.pack(expand=True)

        style = ttk.Style(self)

        style.configure('Error.TLabel', foreground='white')
        style.configure('Error.TLabel', background='red')
        style.configure('Error.TLabel', font=('Helvetica', 12))
        style.configure('Error.TLabel', padding=(10, 10))


if __name__ == "__main__":
    app = App()
    app.mainloop()Code language: Python (python)

Summary

  • A ttk widget is made up of elements. The layout determines how elements assembled the widget.
  • Use the Style.layout() method to retrieve the layout of a widget class.
  • Use the Style.element_options() method to get the element options of an element.
  • Use the Style.lookup() method to get the attributes of an element option.
]]>
3623
Tkinter Styles https://shishirkant.com/tkinter-styles/?utm_source=rss&utm_medium=rss&utm_campaign=tkinter-styles Mon, 17 Apr 2023 14:20:06 +0000 https://shishirkant.com/?p=3619 Introduction to the ttk styles

A theme of a collection of styles that determine the appearances of Tkinter widgets.

A style is a description of the appearance of a widget class. Typically, a theme comes with a predefined set of styles.

Therefore, to change the appearance of ttk widgets, you can:

  • Modify the built-in styles
  • Create new styles

Generally, the style name of a ttk widget starts with the letter 'T' followed by the widget name, for example, TLabel and TButton.

In Tkinter, every widget has a default widget class. A widget class defines the default style for a widget.

The following table shows the style names of the common ttk widget classes:

Widget classStyle name
ButtonTButton
CheckbuttonTCheckbutton
ComboboxTCombobox
EntryTEntry
FrameTFrame
LabelTLabel
LabelFrameTLabelFrame
MenubuttonTMenubutton
NotebookTNotebook
PanedWindowTPanedwindow
Progressbar*Horizontal.TProgressbar or Vertical.TProgressbar, depending on the orient option.
RadiobuttonTRadiobutton
Scale*Horizontal.TScale or Vertical.TScale, depending on the orient option.
Scrollbar*Horizontal.TScrollbar or Vertical.TScrollbar, depending on the orient option
SeparatorTSeparator
SizegripTSizegrip
Treeview*Treeview

(*) The style names of the Progressbar , ScaleScrollbar, and Treeview widgets don’t start with the letter T.

At runtime, you can get the widget class of a widget by calling the winfo_class() method of the widget instance.

The following example uses the winfo_class() method to get the widget class of a button widget:

button = ttk.Button(root, text='Click Me')
print(button.winfo_class())Code language: Python (python)

Output:

TButtonCode language: Python (python)

Modifying built-in ttk styles

Every style has a set of options that define the widget’s appearance.

To modify the appearance of a style, you use the configure() method of the Style class:

style = ttk.Style(root)
style.configure(style_name, **options)Code language: Python (python)

The following program shows how to change the font of all the Label and Button widgets by modifying the TLabel and TButton‘s styles:

import tkinter as tk
from tkinter import ttk


class App(tk.Tk):
    def __init__(self):
        super().__init__()

        self.geometry('300x110')
        self.resizable(0, 0)
        self.title('Login')

        # UI options
        paddings = {'padx': 5, 'pady': 5}
        entry_font = {'font': ('Helvetica', 11)}

        # configure the grid
        self.columnconfigure(0, weight=1)
        self.columnconfigure(1, weight=3)

        username = tk.StringVar()
        password = tk.StringVar()

        # username
        username_label = ttk.Label(self, text="Username:")
        username_label.grid(column=0, row=0, sticky=tk.W, **paddings)

        username_entry = ttk.Entry(self, textvariable=username, **entry_font)
        username_entry.grid(column=1, row=0, sticky=tk.E, **paddings)

        # password
        password_label = ttk.Label(self, text="Password:")
        password_label.grid(column=0, row=1, sticky=tk.W, **paddings)

        password_entry = ttk.Entry(
            self, textvariable=password, show="*", **entry_font)
        password_entry.grid(column=1, row=1, sticky=tk.E, **paddings)

        # login button
        login_button = ttk.Button(self, text="Login")
        login_button.grid(column=1, row=3, sticky=tk.E, **paddings)

        # configure style
        self.style = ttk.Style(self)
        self.style.configure('TLabel', font=('Helvetica', 11))
        self.style.configure('TButton', font=('Helvetica', 11))


if __name__ == "__main__":
    app = App()
    app.mainloop()Code language: Python (python)

Output:

How it works.

First, create a new instance of the ttk.Style class:

self.style = ttk.Style(self)
Code language: Python (python)

Second, change the font of the TLabel and TButton‘s styles using the configure() method of the Style object:

self.style.configure('TLabel', font=('Helvetica', 12))
self.style.configure('TButton', font=('Helvetica', 12))Code language: Python (python)

Extending built-in ttk styles

To create a new style that is derived from a built-in style, you use the style name like this:

new_style.builtin_styleCode language: Python (python)

For example, to create a new style of the Label widget used for displaying a heading, you can name it as follows:

Heading.TLabelCode language: Python (python)

The Heading.TLabel style inherits all options from the built-in TLabel style.

To override a specific option, you also use the configure() method of the Style class:

style = ttk.Style(self)
style.configure(custom_style, **options)Code language: Python (python)

The following example adds a heading to the login window. The heading has the style derived from the TLabel style:

import tkinter as tk
from tkinter import ttk


class App(tk.Tk):
    def __init__(self):
        super().__init__()

        self.geometry('300x150')
        self.resizable(0, 0)
        self.title('Login')

        # UI options
        paddings = {'padx': 5, 'pady': 5}
        entry_font = {'font': ('Helvetica', 11)}

        # configure the grid
        self.columnconfigure(0, weight=1)
        self.columnconfigure(1, weight=3)

        username = tk.StringVar()
        password = tk.StringVar()

        # heading
        heading = ttk.Label(self, text='Member Login', style='Heading.TLabel')
        heading.grid(column=0, row=0, columnspan=2, pady=5, sticky=tk.N)

        # username
        username_label = ttk.Label(self, text="Username:")
        username_label.grid(column=0, row=1, sticky=tk.W, **paddings)

        username_entry = ttk.Entry(self, textvariable=username, **entry_font)
        username_entry.grid(column=1, row=1, sticky=tk.E, **paddings)

        # password
        password_label = ttk.Label(self, text="Password:")
        password_label.grid(column=0, row=2, sticky=tk.W, **paddings)

        password_entry = ttk.Entry(
            self, textvariable=password, show="*", **entry_font)
        password_entry.grid(column=1, row=2, sticky=tk.E, **paddings)

        # login button
        login_button = ttk.Button(self, text="Login")
        login_button.grid(column=1, row=3, sticky=tk.E, **paddings)

        # configure style
        self.style = ttk.Style(self)
        self.style.configure('TLabel', font=('Helvetica', 11))
        self.style.configure('TButton', font=('Helvetica', 11))

        # heading style
        self.style.configure('Heading.TLabel', font=('Helvetica', 12))


if __name__ == "__main__":
    app = App()
    app.mainloop()Code language: Python (python)

Output:

How it works.

First, add a heading to the window and assign the style Heading.TLabel to the style option of the Label widget:

heading = ttk.Label(self, text='Member Login', style='Heading.TLabel')Code language: Python (python)

Then, change the font of the Heading.TLabel style to Helvetica, 12 pixels:

self.style.configure('Heading.TLabel', font=('Helvetica', 12))Code language: Python (python)

Hierarchies of styles

It’s possible to create your own entire hierarchies of styles. For example, you can have a Message.TLabel style that inherits from the built-in TLabel style.

And then you can create styles that inherit from the Message.TLabel style like Error.Message.TLabelWarning.Message.TLabel, and Information.Message.TLabel.

When you use a particular style e.g., Error.Message.TLabelttk looks for options in the Error.Message.TLabel style first. If it doesn’t find the options, it’ll search in the Message.TLabel style. And it continues searching for the options in the TLabel style.

The following picture illustrates the example of the style hierarchy:

The root style determines the appearance of all widgets. The name of the root style is '.'.

For example, if you want to change the text to a 12-pixel Helvetica font for all widgets, you can configure it as follows:

style = ttk.Style(root)
style.configure('.', font=('Helvetica', 12))Code language: Python (python)

Summary

  • A theme of a collection of styles. A style is a description of the appearance of a widget.
  • Use the widget.winfo_class() method to get the widget class of a widget. The widget class defines the default style for the widget.
  • Use the style.configure() method to modify the style of the widget.
  • To customize the built-in style, you can extend it using the style name new_style.builtin_style.
]]>
3619
Tkinter Themes https://shishirkant.com/tkinter-themes/?utm_source=rss&utm_medium=rss&utm_campaign=tkinter-themes Mon, 17 Apr 2023 14:09:35 +0000 https://shishirkant.com/?p=3615 Introduction to Tkinter ttk themes

In Tkinter, a theme determines the “look & feel” of all the widgets. It’s a collection of styles for all the ttk widgets.

A style specifies the appearance of a widget class e.g., a Button. Each theme comes with a set of styles. It’s possible to change the appearance of widgets by:

  • Modifying the built-in styles
  • or creatting new styles

Tkinter allows you to change the current theme to another. When you change the current theme to a new one, Tkinter will apply the styles of that theme to all the ttk widgets.

To get the available themes, you use the theme_names() method of the ttk.Style instance.

First, create a new instance of the ttk.Style class:

style = ttk.Style(root)Code language: Python (python)

Second, get the available themes by calling the theme_names() method:

style.theme_names()Code language: Python (python)

To get the current theme, you use the theme_use() method:

current_theme = style.theme_use()Code language: Python (python)

Note that every operating system (OS) such as Windows, macOS, and Linux comes with its own predefined themes. If you use the theme_names() and theme_use() methods on different OS, you’ll get different results.

To change the current theme to a new one, you pass the new theme name to the theme_use() method:

style.theme_use(theme_name)Code language: Python (python)

The following program shows all themes in your system and allows you to change one theme to another:

import tkinter as tk
from tkinter import ttk


class App(tk.Tk):
    def __init__(self):
        super().__init__()

        # root window
        self.title('Theme Demo')
        self.geometry('400x300')
        self.style = ttk.Style(self)

        # label
        label = ttk.Label(self, text='Name:')
        label.grid(column=0, row=0, padx=10, pady=10,  sticky='w')
        # entry
        textbox = ttk.Entry(self)
        textbox.grid(column=1, row=0, padx=10, pady=10,  sticky='w')
        # button
        btn = ttk.Button(self, text='Show')
        btn.grid(column=2, row=0, padx=10, pady=10,  sticky='w')

        # radio button
        self.selected_theme = tk.StringVar()
        theme_frame = ttk.LabelFrame(self, text='Themes')
        theme_frame.grid(padx=10, pady=10, ipadx=20, ipady=20, sticky='w')

        for theme_name in self.style.theme_names():
            rb = ttk.Radiobutton(
                theme_frame,
                text=theme_name,
                value=theme_name,
                variable=self.selected_theme,
                command=self.change_theme)
            rb.pack(expand=True, fill='both')

    def change_theme(self):
        self.style.theme_use(self.selected_theme.get())


if __name__ == "__main__":
    app = App()
    app.mainloop()
Code language: Python (python)

In this example, when you select a theme from the radio button list, the change_theme() method will apply the selected theme.

If you run the program on Windows 10, you’ll see the following window:

Tkinter Theme

If you change the theme to classic, you’ll see the style of the widgets (Label, Entry, Button, LabelFrame, and Radio Button) change to the following:

ttk Theme

Summary

  • Create an instance of the ttk.Style class to access the style database.
  • Use the style.theme_names() method to get available themes from the Operating System on which the Tkinter application is running.
  • Use the style.theme_use() method to change the current theme to a new one.
]]>
3615