Python Forum
Wont create Image from function
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Wont create Image from function
#1
Hi - First Post as I get into Python. Really enjoying it so far.

I am trying (and failing) to create a button that when clicked destroys the existing image and places another on the page.
When i click my button it does remove the first image and no error message appears, however it does not load the new image.
Does anyone have any ideas where i am going wrong please?

import tkinter

window = tkinter.Tk()
window.title("Hello to the World")
window.geometry("480x800")


worldimg = tkinter.PhotoImage(file="world.gif")
imglabel = tkinter.Label (
    window,
    image=worldimg,
)
imglabel.place(x=10,y=10)


def helloworld():
    imglabel.destroy()
    newimg = tkinter.PhotoImage(file="coffee.gif")
    coffimg = tkinter.Label (
        window,
        image=newimg,
    )
    coffimg.place(x=10,y=10)

change_button = tkinter.Button(
    window,
    text="Change Image",
    fg="black",
    bg="yellow",
    width=31,
    font=("Arial",16),
    command = helloworld)
change_button.place(x=10,y=500)
The solution to this - means i should be able to add and remove buttons from an APP at a click of a button. The app will have a consistent housestyle (logo, title, menu buttons as the image attached shows). However as i'm a newbie i was wondering:

1. Is this the best was to load each game by destroying and creating?
2. The main.py file will become long if i include the code for the 12 or so games. I was thinking about when you click the button it loads the function from another file (keep seperate files for each game). Would this be better than one long file? How would a proper coder do it?

Thanks in anticipation.

[Image: app%20diag.jpg]
Reply
#2
I would use a label and just change the image instead of destroying and recreating the label.
Here is an example. I'm using text but, could just as easily be images.

import tkinter as tk
from random import choice


def change():
    imglist = [f'Image {i}' for i in range(5)]
    colors = ['orange', 'red', 'yellow', 'green']
    label.configure(text=choice(imglist), bg=choice(colors))


root = tk.Tk()
root['padx'] = 5
root['pady'] = 5
root.geometry('400x400+300+300')
label = tk.Label(root, text='Start Image', bg='gold', font=(None, 20, 'bold'))
label.pack(side='top', fill='both', expand=True)

button = tk.Button(root, text='Change', command=change)
button.pack(side='left', pady=8)
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
#3
Hi,

Thanks for the response. It has a few parts that i have not done yet but i get the gist. Did you see my other questions underneath. Its not just one image its the buttons and content in the APP that need to be replaced so i first need to get rid of them (which is why i destroyed it) then to create the new game in the middle. I drew a diagram to show how i figured this would work.

I still don't understand why my code didn't work. The code loads the image correctly whether its the world or the coffee. It just wont do it after an image has already been displayed and destroyed. Do you have any thoughts on this.

Thanks
Reply
#4
Buttons can follow the same principle as the above example.
I would recommend creating a button class to use.
Button text, commands, add or remove buttons to your needs.
I welcome all feedback.
The only dumb question, is one that doesn't get asked.
My Github
How to post code using bbtags


Reply
#5
The reason the photo does not appear in your label is because "newimg" is a function variable, and it is the only reference to the image object returned by "tkinter.PhotoImage(file="coffee.gif")'. When the helloworld() function returns, the "newimg" variable ceases to exist, and there is nothing referencing your image. Python, seeing that there is no way to get at the image, assumes that the image is unused and it destroys the image object and recycles the memory.

To see the image you need to keep a reference to the image. I usually add it to the label or button that uses the image.
newimg = tkinter.PhotoImage(file="coffee.gif")
label = tk.Label(window, image=newimg)
label.image = newimg # Keeps a reference to image so it isn't deleted.
Reply
#6
Hi DeanHystad,

Thank you for the reply. I was a little confused by what you mean to create a variable to contain the destroyed image. I know what it would do just that image and variable were to be removed and a totally different one put in. I think if i get you right you were talking about the container to keep the variable in memory.

Anyway you did put me onto the solution in that the newimg was a function variable. This mean that i had to make the variable global to be picked up by the main programme. the code to make it work looks like this.

Thanks for the help.

import tkinter

window = tkinter.Tk()
window.title("Hello to the World")
window.geometry("480x800")

worldimg = tkinter.PhotoImage(file="world.gif")
imglabel = tkinter.Label (
    window,
    image=worldimg,
)
imglabel.place(x=10,y=10)


def helloworld():
    imglabel.destroy()
    global newimg
    newimg = tkinter.PhotoImage(file="coffee.gif")
    coffimg = tkinter.Label (
        window,
        image=newimg,
    )
    coffimg.place(x=10,y=10)

change_button = tkinter.Button(
    window,
    text="Change Image",
    fg="black",
    bg="yellow",
    width=31,
    font=("Arial",16),
    command = helloworld)
change_button.place(x=10,y=500)
Reply
#7
As menator mentioned, you should not create new labels when you want to display a different image. You should create one label and change the image displayed in that label.

Avoid using "global". As your programs get bigger it becomes difficult to keep track of where global variables are modified.

You should also use pack() or grid() instead of place(). Place does not handle resizing or font changes and results in a lot more work to maintain your application. You should also use styling instead of setting fonts and colors in the code.

This is how I would write your code.
import tkinter as tk
from itertools import cycle


def next_image():
    image = tk.PhotoImage(file=next(images))
    label.image = image  # keeps a reference to the image to prevent deletion
    label["image"] = image  # sets the image in the label


window = tk.Tk()
window.title("Hello to the World")
label = tk.Label()
label.pack(padx=40, pady=10)
button = tk.Button(window, text="Change Image", command=next_image)
button.pack(padx=40, pady=10)

# Make a list of image files that we can cycle through
images = cycle(("world.gif", "coffee.gif"))
next_image()
window.mainloop()
I thought it might be more fun to have a list of functions you can cycle through instead of one click and you are done. I added a mainloop() call to the end of the program. This is required to prevent the program from exiting and the window being closed. You must be using IDLE to run your programs. IDLE runs programs in a different way, and some programs that work when run in IDLE don't work when run any other way. Your program wouldn't show anything at all if you ran it from the command line.
Reply
#8
Thank you for taking the time to reply. I am new to tkinter and only at a very basic level in python. I am at work now but have seen that your reply gives me some great tips - i will look at how this will work when i get home. Are you able to answer a few questions to support my learning please?

1. So like menator said i should create a container variable for the image so its not completely destroyed instead of using global?
2. Creating a list with the functions name in might be a good way to do this. I have done lists and container data types so i could probably do this with some thought.
3. I have not used classes yet but it was suggested to use a class and attach to the buttons to easily add or remove them. This sounds good - is there anyway i could see a basic example of how this would work?
4. I am using IDLE you are correct. How did you know? It also doesnt run from the command line like you say. Why is this? What way is better and how to implement / change it to work that way?

Thanks again



(Jul-04-2024, 07:58 PM)deanhystad Wrote: As menator mentioned, you should not create new labels when you want to display a different image. You should create one label and change the image displayed in that label.

Avoid using "global". As your programs get bigger it becomes difficult to keep track of where global variables are modified.

You should also use pack() or grid() instead of place(). Place does not handle resizing or font changes and results in a lot more work to maintain your application. You should also use styling instead of setting fonts and colors in the code.

This is how I would write your code.
import tkinter as tk
from itertools import cycle


def next_image():
    image = tk.PhotoImage(file=next(images))
    label.image = image  # keeps a reference to the image to prevent deletion
    label["image"] = image  # sets the image in the label


window = tk.Tk()
window.title("Hello to the World")
label = tk.Label()
label.pack(padx=40, pady=10)
button = tk.Button(window, text="Change Image", command=next_image)
button.pack(padx=40, pady=10)

# Make a list of image files that we can cycle through
images = cycle(("world.gif", "coffee.gif"))
next_image()
window.mainloop()
I thought it might be more fun to have a list of functions you can cycle through instead of one click and you are done. I added a mainloop() call to the end of the program. This is required to prevent the program from exiting and the window being closed. You must be using IDLE to run your programs. IDLE runs programs in a different way, and some programs that work when run in IDLE don't work when run any other way. Your program wouldn't show anything at all if you ran it from the command line.
Reply
#9
Deleting/replacing content or changing the view are both valid options. I think deleting the existing game view and packing a new view is the best option. If you do decide to create all your games and want to navigate through the views, you could use a ttk.Notebook.

This is an example that creates 7 "games" that you can navigate through using buttons on the bottom of the window. Each "game" is a different page in the notebook. The buttons select what page is visible.
import tkinter as tk
from tkinter import ttk, font
from functools import partial


class GameWindow(tk.Frame):
    """Stub for fun game window."""

    def __init__(self, parent, color):
        super().__init__(parent)
        self.label = tk.Label(self, text=color, font=font.Font(size=128), bg=color)
        self.label.pack(expand=True, fill=tk.BOTH)

    def play(self):
        """Do something."""
        bg = self.label["bg"]
        fg = self.label["fg"]
        self.label.config(bg=fg, fg=bg)


class MainWindow(tk.Tk):
    """Demonstrate using notebook witnout tabs."""

    def __init__(self):
        super().__init__()
        # Define notebook style where Tab is an empty list.
        self.style = ttk.Style()
        self.style.layout("TNotebook.Tab", [])

        # Instead of tabs, create buttons to select what notebook page is selected.
        self.notebook = ttk.Notebook(self)
        self.notebook.pack(side=tk.TOP, expand=True, fill=tk.BOTH)
        self.games = [
            GameWindow(self.notebook, color)
            for color in ("red", "orange", "yellow", "green", "blue", "indigo", "violet")
        ]
        for game in self.games:
            self.notebook.add(game)

        # Create buttons to navigate through the games.
        nav_buttons = tk.Frame(self)
        nav_buttons.pack(side=tk.TOP, expand=True, fill=tk.X)
        button = tk.Button(nav_buttons, text="  <<  ", command=partial(self.select_game, -1))
        button.pack(side=tk.LEFT)
        button = tk.Button(nav_buttons, text="Play", command=self.play_game)
        button.pack(side=tk.LEFT, expand=True, fill=tk.X)
        button = tk.Button(nav_buttons, text="  >>  ", command=partial(self.select_game, 1))
        button.pack(side=tk.LEFT)
        self.index = -0
        self.select_game(0)

    def play_game(self):
        """Play selected game."""
        self.games[self.index].play()

    def select_game(self, increment):
        """Select a game window."""
        self.index = max(0, min(len(self.games) - 1, self.index + increment))
        self.notebook.select(self.games[self.index])


MainWindow().mainloop()
Reply
#10
def helloworld():

write another def for the first image. PhotoImage class. That way you have two definitions, one for world.gif and one for coffee.gif. This an engineering problem and not just a program solution. So you would write out several definitions, one to add or change, and one to destroy. Both taken from the PhotoImage class from tkinter. Then see if that algorithm works. For a better solution.
Programs are like instructions or rules. Learning it gets us closer to a solution. Desired outcome. Computer talk.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [Kivy] Create a function to store text fields and drop downs selection in KivyMD floxia 0 2,066 Dec-18-2022, 04:34 AM
Last Post: floxia
  simple tkinter question function call not opening image gr3yali3n 5 4,395 Aug-02-2022, 09:13 PM
Last Post: woooee
  Class function does not create command button Heyjoe 2 2,600 Aug-22-2020, 08:06 PM
Last Post: Heyjoe
  [PyQt] Create exe file including referenced image (*.png) files mart79 0 1,888 Jul-21-2020, 09:49 AM
Last Post: mart79
  Create image on a Toplevel using tkinter ViktorWong 3 8,498 Jun-13-2020, 03:21 PM
Last Post: deanhystad
  Refresh image in label after every 1s using simple function jenkins43 1 5,749 Jul-28-2019, 02:49 PM
Last Post: Larz60+
  Simple Button click on image file to create action? jpezz 4 7,655 Mar-27-2019, 10:08 PM
Last Post: jpezz

Forum Jump:

User Panel Messages

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