Python Forum
[Tkinter] Showing windows after one another
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tkinter] Showing windows after one another
#1
import tkinter as tk 
from PIL import Image, ImageTk

class Class1():
    
    def __init__(self) -> None:
        self.root = tk.Tk()
        self.root.geometry('800x800')
        
    def RunWindow(self):
        self.root.mainloop()
        
    def CloseWindow(self):
        self.root.quit()
        
        
    def ShowWindow(self):
        image_og = Image.open('IMG_0126.jpg').resize((500, 350))
        self.image_tk = ImageTk.PhotoImage(image_og)
        PicLabel = tk.Label(self.root, image = self.image_tk)
        PicLabel.place(relx = 0.1, rely = 0.1)
        
        nextButton = tk.Button(self.root,text = 'next', command = self.CloseWindow)
        nextButton.place(relx = 0.7, rely = 0.8)
    
test = Class1()
test.ShowWindow()
test.RunWindow()
print('yes')
test1 = Class1()
test1.ShowWindow()
test1.RunWindow()
When this is run it comes up with an error message saying

Traceback (most recent call last):
File "DocumentLocation", line 31, in <module>
test1.ShowWindow()
File "DocumentLocation", line 20, in ShowWindow
PicLabel = tk.Label(self.root, image = self.image_tk)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/tkinter/__init__.py", line 3214, in __init__
Widget.__init__(self, master, 'label', cnf, kw)
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/tkinter/__init__.py", line 2628, in __init__
self.tk.call(
_tkinter.TclError: image "pyimage2" doesn't exist


I don't understand what the issue is? any help would be appreciated
Reply
#2
quit() exits mainloop. It does not destroy the window. You can see that in the example below.
import tkinter as tk

class Class1(tk.Tk):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.entry = tk.Entry(self)
        self.entry.pack()
        tk.Button(self, text='next', command=self.quit).pack()
        self.mainloop()

    def text(self):
        return self.entry.get()

test = Class1()
print(test.text())
If I type "Hello World" in the entry and press the next button, it prints
Output:
Hello World
If quit() destroyed the window, there would be no entry widget that I could query to get the entered text.

I run he program again, enter "Hello World" in the entry and press the close window frame decoration (X), This deletes the window. Since there are no other windows this also exits the mainloop. When I try to get the entered text I get an exception.
Error:
Traceback (most recent call last): File "test.py", line 17, in <module> print(test.text()) File "c:\test.py", line 12, in text return self.entry.get() File "C:\Program Files\Python310\lib\tkinter\__init__.py", line 3072, in get return self.tk.call(self._w, 'get') _tkinter.TclError: invalid command name ".!entry"
There is no entry widget because it got destroyed along with the window.

Your problem is related, but the opposite. When you press the "next" button, you exit the mainloop, but the window is still there. Then you commit the unforgiveable sin of calling Tk() a second time. This not only creates a top level window, it also re-initializes the tkinter framework. I cannot say exactly why this raises the error we see, but it is something you should never do, and we need to expect bad things to happen.

For your example you want to use destroy, not quit. You can easily test with your existing code by destroying the window using the X window decoration.

I have a question. Why are you destroying a window, and then creating a new one? This is an unusual pattern and there's probably a better way to do what you want to do.
Reply
#3
I would just update the window with the data to be displayed.

Quick example
import tkinter as tk 

class Window:
    def __init__(self, parent):

        parent.geometry('400x400+350+350')
        parent['pady'] = 8

        # Create a list and starting point
        self.colors = ['orange', 'red', 'green', 'gold', 'brown', 'purple']
        self.index = 0

        # Label and buttons
        self.label = tk.Label(parent, text=f'Background Color is {self.colors[self.index]}', font=(None, 20, 'normal'), bg=self.colors[self.index])
        self.label.pack(expand=True, fill='both')

        self.back_button = tk.Button(parent, text='Back', command=self._prev)
        self.back_button.pack(side='left', padx=8, pady=8)

        self.next_button = tk.Button(parent, text='Next', command=self._next)
        self.next_button.pack(side='left', padx=8, pady=8)

    # Method for going backwards in the list
    def _prev(self):
        if self.index <= 0:
            self.index = len(self.colors)-1
        else:
            self.index -= 1
        self.update()

    # Method for going forward in the list
    def _next(self):
        if self.index >= len(self.colors)-1:
            self.index = 0
        else:
            self.index += 1
        self.update()

    # Update the labels. This could be put in the above methods
    def update(self):
        self.label['text'] = f'Background Color is {self.colors[self.index]}'
        self.label['bg'] = self.colors[self.index]



if __name__ == '__main__':
    root = tk.Tk()
    Window(root)
    root.mainloop()
I welcome all feedback.
The only dumb question, is one that doesn't get asked.
My Github
How to post code using bbtags


Reply
#4
Thank you very much for your help. I realised yesterday when looking on stack overflow for a solution that tk() should never be called more than once. I am self taught and therefore if the first video i watched didn't warn me then I was doomed. I have now reworked my code with toplevel windows and no longer need to destroy things randomly.
Reply
#5
You can call Tk() more than once. I do that here to display all the png files in my games folder.
import tkinter as tk
from pathlib import Path

class ShowImage(tk.Tk):
    def __init__(self, image_file):
        super().__init__()
        image=tk.PhotoImage(file=image_file)
        tk.Label(self, image=image).pack()
        tk.Button(self, text='Next Image', command=self.destroy).pack()
        self.mainloop()

for image_file in Path("games").glob("*.png"):
    ShowImage(image_file)
This works because pressing the Next button destroys the only window, ending the tkinter session. To draw another tkinter window I need to call Tk() again. The program may draw dozens of windows, but each window is a completely new tkinter session.

But that is a silly way to display multiple images. Each time you press the Next button you get a new window. The next window might appear in a different location on the screen, forcing you to move the mouse to press the button again. A better solution is to use the one button to show all the images.

This program also displays image files in a directory, but it does it using the same window for each image.
import tkinter as tk
from pathlib import Path


class ShowImage(tk.Tk):
    """Loop through images in a folder."""
    def __init__(self, folder="."):
        super().__init__()
        self.label = tk.Label(self)
        self.label.pack()
        tk.Button(self, text='Next Image', command=self.next_image).pack()
        self.files = Path(folder).iterdir()
        self.next_image()

    def next_image(self):
        """Display next image from folder."""
        try:
            # Loop through files looking for image file to display.
            while image_file := next(self.files):
                try:
                    # Try to open file as an image.
                    self.image = tk.PhotoImage(file=image_file)
                    self.label["image"] = self.image
                    return
                except tk.TclError:
                    pass  # File was not an image file.  Try next file.
        except StopIteration:
            self.destroy()  # No more files.
A fun thing about this program is its use of "duck typing". Some of the files in the folder might not be image files. Some of the files might be image files but aren't in a format that is understood by tk.PhotoImage. Instead of trying to filter out what files to display and what to skip ahead of time, the program tries to open the file as an image. If this fails, the program catches the error and tries the next file.
Reply


Forum Jump:

User Panel Messages

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