Search code examples
pythontkintercustomtkinter

Why isnt my customTkinter app using .after? It should print "hi" every 10 milliseconds


I am trying to run a function every 10 milliseconds, but I am unable to do so. So I figured I would try to find the problem with using print("hi") and found that it only calls that line once, when the object app is created. I tried lambda, I tried threading, I tried a lot of stuff. But tkinter should have a .after() method but it doesn't work for me. Please help!

import customtkinter
import tkinter as tk
import os
from PIL import Image
from metals import Metal
from alloy_calculator import AlloyCalc as Alloy

# System Settings
customtkinter.set_appearance_mode("dark")
customtkinter.set_default_color_theme("blue")

class App(customtkinter.CTk):
    def __init__(self):
        super().__init__()
        
        # Main Window
        self.geometry("650x740")
        self.title("Auto TerrafirmaCraft Alloy Calculator")
        

        self.grid_rowconfigure((0), weight=0)
        self.grid_rowconfigure((1), weight=0)
        # first column is as big as necessary, second column expands to fit window
        self.grid_columnconfigure((0), weight=0)
        self.grid_columnconfigure((1), weight=1)
      
        # Creates function 'button_function' that outputs a line in the command prompt
        def button_function():
            print("Button Pressed")
        

        # Image Path and Declarations
        image_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "assets")
        self.logo_image = customtkinter.CTkImage(Image.open(os.path.join(image_path, "logo.png")), size=(628,20))
        
        # Title Frame and Image Render
        self.app_title = customtkinter.CTkLabel(self, text="", image=self.logo_image)
        self.app_title.grid(row=0, column=0, padx=10, pady=10, columnspan=3)
            
        # Selection Frame for Metals
        self.material_frame = customtkinter.CTkFrame(self, fg_color="grey13", width=1, height=1)
        self.material_frame.grid(row=1, column=0, sticky="nw")
        
        # Selection Frame Label
        self.material_frame_label = customtkinter.CTkLabel(self.material_frame, text="Metal Selection", compound="top", font=customtkinter.CTkFont(size=23, weight="bold"))
        self.material_frame_label.grid(row=0, column=0, padx=(0,0), pady=0, sticky="ns")
        
        # Selection Frame underscore (tkinter canvas widget and white rectrangle)
        self.material_frame_placeholder_text = customtkinter.CTkLabel(self.material_frame, text="                                      15     25     35             %", compound="top", font=customtkinter.CTkFont(family="Segoe UI Semibold", size=14, weight="normal"))
        self.material_frame_placeholder_text.grid(row=1, column=0, padx=(3,0), pady=0, sticky="ns")
        
        # Selection Frame for metal selection
        self.metal_selection = Metal(self.material_frame)
        self.metal_selection.grid(row=2, column=0, padx=0, pady=(0,20), sticky="n")
        
        self.alloy_info = Alloy(self, width= 150, bg_color = "transparent")
        self.alloy_info.grid(row=1, column=1, sticky="nw")  

# Keeps window open
    
if __name__ == "__main__":
    app = App()
    app.after(10, print("hi"))
    app.mainloop()

Solution

  • There are two problems with your code.

    The first is that after will only schedule something to run once, after a given time period. That is what it is designed to do.

    The second is that after requires a callable - a reference to a function. In your case you're immediately calling print("hi"), and passing the result to after.

    If you want something to be done on an interval, you need to write a function that will call after with itself as an argument, so that each time it's called it will schedule a future invocation.

    def print_hi(app):
        print("hi")
        app.after(10, print_hi, app)
    
    # schedule the first call, and pass `app` as an argument to the function
    app.after(10, print_hi, app)