Python Forum
tkinter.TclError: can't invoke "canvas" command
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
tkinter.TclError: can't invoke "canvas" command
#1
I am getting this tcl error and unable to solve it. Here is the full exception. This happens when I exit the program
Error:
Exception in Tkinter callback Traceback (most recent call last): File "C:\Users\INDIAN\AppData\Local\Programs\Python\Python38\lib\tkinter\__init__.py", line 1883, in __call__ return self.func(*args) File "C:\Users\INDIAN\Desktop\python exercises\pygametooth\untiltledtry 15-2-23.py", line 65, in select_folder canvas.delete("all") File "C:\Users\INDIAN\AppData\Local\Programs\Python\Python38\lib\tkinter\__init__.py", line 2818, in delete self.tk.call((self._w, 'delete') + args) _tkinter.TclError: invalid command name ".!canvas" Traceback (most recent call last): File "C:\Users\INDIAN\Desktop\python exercises\pygametooth\untiltledtry 15-2-23.py", line 135, in <module> canvas = Canvas(root, width=800, height=500, bg="white") File "C:\Users\INDIAN\AppData\Local\Programs\Python\Python38\lib\tkinter\__init__.py", line 2683, in __init__ Widget.__init__(self, master, 'canvas', cnf, kw) File "C:\Users\INDIAN\AppData\Local\Programs\Python\Python38\lib\tkinter\__init__.py", line 2567, in __init__ self.tk.call( _tkinter.TclError: can't invoke "canvas" command: application has been destroyed >>>
import tkinter as tk
from tkinter import Canvas, NW
from tkinter import filedialog
import os
import pygame
import cv2
import numpy as np
from PIL import Image, ImageTk


image_tk = None # Placeholder variable for PhotoImage

# Initialize Pygame
pygame.init()





def select_folder():
    # Open a file dialog to select the folder
    root.filename = filedialog.askdirectory()

    # Load the images from the selected folder
    images = []
    for f in os.listdir(root.filename):
        if f.endswith((".png", ".jpg", ".bmp", ".dcm")):
            image = cv2.imread(os.path.join(root.filename, f))
            if image is not None:
                images.append(image)

    # Set the initial image
    current_image = 0

    # Main loop
    running = True
    while running:
        # Handle events
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    current_image = (current_image - 1 + len(images)) % len(images)
                elif event.key == pygame.K_RIGHT:
                    current_image = (current_image + 1) % len(images)

        
        
        # Clear the canvas
        canvas.delete("all")

        # Get the dimensions of the current image
        height, width = images[current_image].shape[:2]
        # Set the rotation angle
        angle = 90

        # Calculate the center of the image
        center = (width // 2, height // 2)

        # Create the rotation matrix
        rotation_matrix = cv2.getRotationMatrix2D(center, angle, 1.0)
        # Calculate the new dimensions of the rotated image
        new_width, new_height = height, width

        # Rotate the image
        rotated = cv2.warpAffine(images[current_image], rotation_matrix, (new_width, new_height))

        # Convert the BGR image to RGB
        rgb_image = cv2.cvtColor(rotated, cv2.COLOR_BGR2RGB)

         

        # Convert the OpenCV image to a PIL ImageTk
        pil_image = Image.fromarray(np.rot90(rgb_image, 3))
##        pil_image = Image.fromarray(rgb_image)
        image_tk = ImageTk.PhotoImage(image=pil_image)

        # Display the image in the tkinter window
        canvas.create_image(0, 0, image=image_tk, anchor=NW)
        canvas.image = image_tk

        # Update the screen
        root.update()

def exit_program():
    root.destroy()

def on_key_press(event):
    if event.keysym == "Left":
        pygame.event.post(pygame.event.Event(pygame.KEYDOWN, {"key": pygame.K_LEFT}))
    elif event.keysym == "Right":
        pygame.event.post(pygame.event.Event(pygame.KEYDOWN, {"key": pygame.K_RIGHT}))

# Create the GUI
##root = tk.Tk()
root.title("Multi-angle Image Viewer")
root.geometry("800x600")

# Add a button to select the folder
button = tk.Button(root, text="Select Folder", command=select_folder)
button1 = tk.Button(root, text="Exit", command=exit_program)
button.pack()
button1.pack()

# Create the canvas to display the images
canvas = Canvas(root, width=800, height=500, bg="white")
canvas.pack()

# Bind the arrow keys to the canvas
canvas.bind("<Key>", on_key_press)
canvas.focus_set()

# Run the GUI


# Run the GUI
root.mainloop()

### Create the canvas to display the images
canvas = Canvas(root, width=800, height=500, bg="white")
canvas.pack()

# Run the GUI
root.mainloop()
Reply
#2
Remove root.mainloop() before the call Canvas(...). You don't need root.mainloop() more than once.
Reply
#3
Why did you post code that is not the code that generated the error message?

The reason you got an error is because you destroyed the root window. I get the same error with the code below when I close the root window using the decorations in the window frame.
import tkinter as tk

root = tk.Tk()
tk.Label(root, text="Hello World").pack(padx=50, pady=50)
root.mainloop()

print("Must have closed the main window")
tk.Label(root, text="Goodbye World").pack(padx=50, pady=50)
root.mainloop()
Error:
Must have closed the main window Traceback (most recent call last): File "c:\Users\djhys\Documents\python\musings\junk.py", line 8, in <module> tk.Label(root, text="Goodbye World").pack(padx=50, pady=50) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Program Files\Python311\Lib\tkinter\__init__.py", line 3214, in __init__ Widget.__init__(self, master, 'label', cnf, kw) File "C:\Program Files\Python311\Lib\tkinter\__init__.py", line 2628, in __init__ self.tk.call( _tkinter.TclError: can't invoke "label" command: application has been destroyed
Once you destroy the window it is gone. You cannot use it again. Not only is the window gone, but all the widgets in the window are invalid.

What are you trying to accomplish with the destroy button and multiple mainloops?
Reply
#4
the double mainloop() was an oversight sorry . i have tried creating the canvas before the select function but am still getting the same exception can some one help resolve the issue. the exception occurs when I press the exit button.
import tkinter as tk
from tkinter import Canvas, NW
from tkinter import filedialog
import os
import pygame
import cv2
import numpy as np
from PIL import Image, ImageTk


image_tk = None # Placeholder variable for PhotoImage

# Initialize Pygame
pygame.init()


# Set the window size
##window_size = (800, 600)

# Create the window
##screen = pygame.display.set_mode(window_size)

# Create the GUI
root = tk.Tk()
root.title("Multi-angle Image Viewer")
root.geometry("800x600")

##Create the canvas to display the images
canvas = Canvas(root, width=800, height=500, bg="white")
canvas.pack()


def select_folder():
    # Open a file dialog to select the folder
    root.filename = filedialog.askdirectory()

    # Load the images from the selected folder
    images = []
    for f in os.listdir(root.filename):
        if f.endswith((".png", ".jpg", ".bmp", ".dcm")):
            image = cv2.imread(os.path.join(root.filename, f))
            if image is not None:
                images.append(image)

    # Set the initial image
    current_image = 0

    # Main loop
    running = True
    while running:
        # Handle events
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    current_image = (current_image - 1 + len(images)) % len(images)
                elif event.key == pygame.K_RIGHT:
                    current_image = (current_image + 1) % len(images)

        # Clear the canvas
        canvas.delete("all")

        # Get the dimensions of the current image
        height, width = images[current_image].shape[:2]
        # Set the rotation angle
        angle = 90

        # Calculate the center of the image
        center = (width // 2, height // 2)

        # Create the rotation matrix
        rotation_matrix = cv2.getRotationMatrix2D(center, angle, 1.0)
        # Calculate the new dimensions of the rotated image
        new_width, new_height = height, width

        # Rotate the image
        rotated = cv2.warpAffine(images[current_image], rotation_matrix, (new_width, new_height))

        # Convert the BGR image to RGB
        rgb_image = cv2.cvtColor(rotated, cv2.COLOR_BGR2RGB)

        # Convert the OpenCV image to a PIL ImageTk
        pil_image = Image.fromarray(np.rot90(rgb_image, 3))
        image_tk = ImageTk.PhotoImage(image=pil_image)

        # Display the image in the tkinter window
        canvas.create_image(0, 0, image=image_tk, anchor=NW)
        canvas.image = image_tk

        # Update the screen
        root.update()

def exit_program():
    root.destroy()

def on_key_press(event):
    if event.keysym == "Left":
        pygame.event.post(pygame.event.Event(pygame.KEYDOWN, {"key": pygame.K_LEFT}))
    elif event.keysym == "Right":
        pygame.event.post(pygame.event.Event(pygame.KEYDOWN, {"key": pygame.K_RIGHT}))

# Add a button to select the folder
button = tk.Button(root, text="Select Folder", command=select_folder)
button1 = tk.Button(root, text="Exit", command=exit_program)
button.pack()
button1.pack()

# Bind the arrow keys to the canvas
canvas.bind("<Key>", on_key_press)
canvas.focus_set()

# Run the GUI


# Run the GUI
root.mainloop()
Error:
Exception in Tkinter callback Traceback (most recent call last): File "C:\Users\INDIAN\AppData\Local\Programs\Python\Python38\lib\tkinter\__init__.py", line 1883, in __call__ return self.func(*args) File "C:/Users/INDIAN/Desktop/python exercises/pygametooth/wct.py", line 62, in select_folder canvas.delete("all") File "C:\Users\INDIAN\AppData\Local\Programs\Python\Python38\lib\tkinter\__init__.py", line 2818, in delete self.tk.call((self._w, 'delete') + args) _tkinter.TclError: invalid command name ".!canvas" >>>
Reply
#5
Your program still has multiple mainloops(). One on line 117 and another on line 50.

Your program layout is wrong. If you want to combine tkinter and pygame, you cannot have a pygame loop and a tkinter loop. I don't see why you are using pygame at all, but assuming that there is some need to use pygame, I would implement the pygame event loop as a periodic function call.

In this code I stripped out all the image stuff to focus on running the pygame event loop as a periodic event. I do the same thing with the image rotate loop. It is pretty obvious that there is no need for pygame at all. The on_key_press() function can adjust the current image directly.
import tkinter as tk
from tkinter import filedialog
import pygame


def select_folder():
    """Open a folder and load image files"""
    root.filename = filedialog.askdirectory()
    # Load images


def pygame_event_handler():
    if running:
        for event in pygame.event.get():
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    print("LEFT")
                elif event.key == pygame.K_RIGHT:
                    print("Right")
        root.after(20, pygame_event_handler)


def rotate_images():
    """Does image rotation stuff"""
    if running:
        # Do image rotation stuff
        root.after(10, rotate_images)


def exit_program():
    global running
    running = False
    root.destroy()


def on_key_press(event):
    if event.keysym == "Left":
        pygame.event.post(pygame.event.Event(pygame.KEYDOWN, {"key": pygame.K_LEFT}))
    elif event.keysym == "Right":
        pygame.event.post(pygame.event.Event(pygame.KEYDOWN, {"key": pygame.K_RIGHT}))


running = True
pygame.init()
root = tk.Tk()
canvas = tk.Canvas(root, width=800, height=500, bg="white")
canvas.pack()
button = tk.Button(root, text="Select Folder", command=select_folder)
button1 = tk.Button(root, text="Exit", command=exit_program)
button.pack()
button1.pack()

# Bind the arrow keys to the canvas
canvas.bind("<Key>", on_key_press)
canvas.focus_set()

pygame_event_handler()
rotate_images()

root.mainloop()
Reply
#6
Thanks deanhystad
I am trying to make a GUI in tkinter where all the image files from a folder run in a continuous loop controlled by the arrow keys. I have a few sets of images of objects from multiple angles and thus want to create a viewer which shows them as if the object is rotating. hence the use of pygame. Can you suggest a simpler alternative.
Reply
#7
I don't think pygame helps at all. Use "after" to periodically call a method that spins the image a small amount. If you run the method 20 times a second the spinning will look continuous.
Reply
#8
Can you please post a sample code for it.
(Feb-21-2023, 12:41 PM)deanhystad Wrote: I don't think pygame helps at all. Use "after" to periodically call a method that spins the image a small amount. If you run the method 20 times a second the spinning will look continuous.
Reply
#9
Quote:Can you please post a sample code for it.
I already have, in my second response to your post. Actually, I did it twice; once for rotating the image, and another for processing pygame events. Third time's the charm?

I would dump pygame and the canvas and do something like this:
import math
import statistics
import tkinter as tk
from tkinter import filedialog
from PIL import Image, ImageTk
from pathlib import Path
import numpy as np

IMAGE_SIZE = (100, 100)  # Resize all images to this size
LABEL_SIZE = 140  # Max size of rotated image


class SpinningImageButton(tk.Frame):
    """A Button that has an image you can rotate"""
    def __init__(self, parent, file, *args, command=None, **kwargs):
        super().__init__(parent, *args, **kwargs)
        self.running = False
        self.index = 0
        self.command = command

        # Guess a background color for the image by using the
        # most common pixel color.  Use this to fill extra pixels
        # when the image is rotated
        image = Image.open(file)
        pixels = np.array(image)
        pixels = map(tuple, pixels.reshape(-1, pixels.shape[-1]))
        rgb = statistics.mode(pixels)[:3]

        # Create multiple images which flashed in succession
        # make it appear like the image is spinning
        image.thumbnail(IMAGE_SIZE)
        self.images = [
            ImageTk.PhotoImage(image.rotate(angle, fillcolor=rgb, expand=True))
            for angle in range(0, 360, 10)
        ]

        # Make a label to display the image.
        self.image = tk.Label(
            self,
            image=self.images[0],
            width=LABEL_SIZE,
            height=LABEL_SIZE,
            bg=f'#{rgb[0]:02x}{rgb[1]:02x}{rgb[2]:02x}')
        self.image.pack(anchor=tk.CENTER)
        self.image.bind('<Button-1>', func=self._click)

    def _click(self, *args):
        """Called when mouse clicks on me"""
        if self.command:
            self.command(self)

    def _next(self, period=20):
        """Display successive images to make it appear the image is spinning"""
        if self.running:
            self.index = (self.index + 1) % len(self.images)
            self.image.config(image=self.images[self.index])
            self.after(period, self._next, period)

    def start(self, period=20):
        """Start spinning the image"""
        self.running = True
        self._next(period)

    def stop(self):
        """Stop spinning the image"""
        self.running = False


class MyWindow(tk.Tk):
    """Window to show off my fancy spinning buttons"""
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # Make a menubar instead of a bunch of buttons
        menubar = tk.Menu(self)
        menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="File", menu=menu)
        menu.add_command(label="Open", command=self.open_folder)
        menu.add_separator()
        menu.add_command(label="Quit", command=self.destroy)
        self.config(menu=menubar)
        self.images = []
        self.frame = tk.Frame(self, width=300, height=300)
        self.frame.pack(expand=True, fill=tk.BOTH)

    def open_folder(self):
        """Open folder and populate window with image buttons"""
        folder = filedialog.askdirectory()
        if folder:
            # Replace old image buttons with new
            for image in self.images:
                image.grid_forget()
            self.images = [
                    SpinningImageButton(self.frame, file, command=self.toggle)
                    for file in Path(folder).iterdir()
                    if file.suffix in ('.png', '.jpg', '.bmp', '.dcm')
            ]

            # Organize the images on a grid.
            cols = int(math.ceil(len(self.images)**0.5))
            for i, image in enumerate(self.images):
                image.grid(row=i // cols, column=i % cols)

    def toggle(self, image):
        """Toggle image spinning on/off when button clicked"""
        if image.running:
            image.stop()
        else:
            image.start(20)


MyWindow().mainloop()
Instead of a canvas, I made my own image buttons that know how to spin their image. By making buttons instead pasting images on a canvas, I can use a layout manager (grid in this case) to organize the images and resize the screen.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [Tkinter] tkinter.TclError: expected integer but got " " TWB 2 3,682 Feb-19-2023, 05:11 PM
Last Post: TWB
  [Tkinter] Clickable Rectangles Tkinter Canvas MrTim 4 8,849 May-11-2021, 10:01 PM
Last Post: MrTim
  [Tkinter] Draw a grid of Tkinter Canvas Rectangles MrTim 5 7,901 May-09-2021, 01:48 PM
Last Post: joe_momma
  [Tkinter] _tkinter.TclError: can't invoke "destroy" command: application has been destroyed knoxvilles_joker 6 15,543 Apr-25-2021, 08:41 PM
Last Post: knoxvilles_joker
  .get() invoke after a button nested press iddon5 5 3,271 Mar-29-2021, 03:55 AM
Last Post: deanhystad
Thumbs Up tkinter canvas; different page sizes on different platforms? philipbergwerf 4 4,112 Mar-27-2021, 05:04 AM
Last Post: deanhystad
  Continue command in python tkinter? MLGpotato 7 8,464 Mar-27-2021, 04:59 AM
Last Post: deanhystad
  how to resize image in canvas tkinter samuelmv30 2 17,748 Feb-06-2021, 03:35 PM
Last Post: joe_momma
  "tkinter.TclError: NULL main window" Rama02 1 5,843 Feb-04-2021, 06:45 PM
Last Post: deanhystad
  how to rotate lines in tkinter canvas helpmewithpython 1 3,415 Oct-06-2020, 06:56 PM
Last Post: deanhystad

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020