Posts: 4
Threads: 1
Joined: Jul 2024
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.
Posts: 1,058
Threads: 111
Joined: Sep 2019
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()
Posts: 4
Threads: 1
Joined: Jul 2024
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
Posts: 1,058
Threads: 111
Joined: Sep 2019
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.
Posts: 6,552
Threads: 19
Joined: Feb 2020
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.
Posts: 4
Threads: 1
Joined: Jul 2024
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)
Posts: 6,552
Threads: 19
Joined: Feb 2020
Jul-04-2024, 07:58 PM
(This post was last modified: Jul-04-2024, 07:58 PM by deanhystad.)
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.
Posts: 4
Threads: 1
Joined: Jul 2024
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.
Posts: 6,552
Threads: 19
Joined: Feb 2020
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()
Posts: 42
Threads: 6
Joined: May 2024
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.
|