This is my first day using tkinter, and I wanted to know how to properly code what I have done.
What I wanted to make was a kind of diaporama so I wanted to find a way to display pictures in a canvas and have two buttons so I could go to the previous picture or the following one. The way I stored pictures is using a list lof numpy arrays, with l of size n, so I have n pictures and they are the same size. So I wrote the following code:
from tkinter import *
import numpy as np
from PIL import Image, ImageTk
import sys
sys.setrecursionlimit(10000) #I increased the recursion limit because I had some issues
l = [(255*np.random.rand(50,50)).astype(np.uint8) for i in range(105)] #this is a random list in case you want to test the code
def next(i,n):
if i == n-1:
return 0
else:
return i+1
def previous(i,n):
if i==0:
return n-1
else:
return i-1
window = Tk()
window.geometry("200x100+900+500")
def display(i):
#This is to clear my window at each iteration because I would like to keep the same window
for widget in window.winfo_children():
widget.destroy()
array = l[i] #this is the i-th picture
img = ImageTk.PhotoImage(image=Image.fromarray(array),master = window)
canvas = Canvas(window, width=48, height=48)
canvas.place(x=10, y=20)
canvas.create_image(0,0, anchor="nw", image=img)
Label(window, text="Picture n°"+str(i), fg="black").place(x=5, y=0)
Button(window, text ='Next',command=lambda: display(next(i,len(l)))).place(x=140, y=35)
Button(window, text ='Previous',command = lambda: display(previous(i,len(l)))).place(x=70, y=35)
window.mainloop()
display(0)
I know that is bad code and not the way it should be written. It is working fine but I need help to improve the code please.
You should only put the code of updating the image inside display() and create the interface outside the function. Then recursion is not needed.
Also a global variable is required to keep track the current index to the image list. This variable should be updated when Next or Previous button is clicked.
Below is a modified example:
from tkinter import *
import numpy as np
from PIL import Image, ImageTk
l = [(255*np.random.rand(50,50)).astype(np.uint8) for i in range(105)] #this is a random list in case you want to test the code
current = 0
def display(dir):
global current
# update "current" based on "dir"
current = (current + dir) % len(l)
# show the "current" image
image = ImageTk.PhotoImage(Image.fromarray(l[current]))
imgbox.config(image=image, text=f"Picture n°{current}")
imgbox.image = image # keep a reference to avoid "image" from being garbage collected
window = Tk()
window.geometry("200x100+900+500")
# use a label for showing image and text together
imgbox = Label(window, fg="black", compound="bottom", width=70)
imgbox.place(x=5, y=0)
Button(window, text ='Next', command=lambda: display(+1)).place(x=150, y=35)
Button(window, text ='Previous', command=lambda: display(-1)).place(x=80, y=35)
display(0) # show first image
window.mainloop()
Related
I'm looking for a way to move multiple images together with the background. Moving the background image works fine, but I can't figure out how to add two images on top (they disappear immediately) and then move together with the background. I guess there is an easy way to do that?
I appreciate any hint!
from tkinter import *
import time
tk = Tk()
w = 1000
h = 800
pos = 0
canvas = Canvas(tk, width=w, height=h)
canvas.pack()
tk.update()
background_image = PhotoImage(file="bg.gif")
background_label = Label(tk, image=background_image)
background_label.place(x=0, y=0)
tk.update()
def addImages(files):
for f in files:
image = PhotoImage(file=f)
label = Label(tk, image=image)
label.place(x=files[f][0],y=files[f][1])
tk.update()
def move(xPos):
pos = background_label.winfo_x() - xPos
while background_label.winfo_x() > pos:
background_label.place(x=background_label.winfo_x()-25)
tk.update()
time.sleep(0.001)
img = {"file1.gif": [10,10], "file2.gif": [50,50]}
addImages(img)
move(100)
tk.mainloop()
I'm having difficulty in understanding your code. Why create a canvas and then not use it? You have also littered your code with tk.update(), most of which are unnecessary. But, the described problem is because you create the labels inside a function and the association between label and image gets garbage collected when the function exits. You have to explicitly remember this association:
def addImages(files):
for f in files:
image = PhotoImage(file=f)
label = Label(tk, image=image)
label.image = image # Lets the label remember the image outside the function
label.place(x=files[f][0],y=files[f][1])
If you are then going to move these labels you might want to keep some kind of reference to them or you won't be able to address them.
Complete example
I changed tk to root because tk is the name usually used as alias for tkinter (eg. import tkinter as tk) which gets confusing.
I'm creating a image_list to hold references to the labels containing images. Later I use the list to loop through the labels and move them.
After I have built the GUI I wait 1000 milliseconds before starting the move function. Also I move the images just 1 pixel at a time to clearer see the action.
from tkinter import *
import time
root = Tk()
root.geometry('800x600') # Setting window size instead of usin canvas to do that
pos = 0
background_image = PhotoImage(file="bg.gif")
background_label = Label(root, image=background_image)
background_label.place(x=0, y=0)
image_list = [] # List for holding references to labels with images
def addImages(files):
for f in files:
image = PhotoImage(file=f)
label = Label(root, image=image)
label.image = image # Remember the image outside the function
label.place(x=files[f][0],y=files[f][1])
image_list.append(label) # Append created Label to the list
def move(xPos):
pos = background_label.winfo_x() - xPos
while background_label.winfo_x() > pos:
background_label.place(x=background_label.winfo_x()-1)
for image in image_list: # Loop over labels in list
image.place(x=image.winfo_x()-1) # Move the label
root.update()
time.sleep(0.001)
img = {"file1.gif": [10,10], "file2.gif": [50,50]}
addImages(img)
root.after(1000, move, 100) # Waits 1 second and then moves images
root.mainloop()
By the way; after is a function much preferred over sleep as sleep suspends the program until its finished, whereas after works by making a call after a time and the program runs meanwhile. But if you are ok with that the program freezes during the move, then why not.
Hi I'm trying to make a code that would replace an image button with an image label when the button is pressed. But the window isn't updating so the new image doesn't became visible. Can anybody help me? If it is even possible to do that way.
There is the code I'm trying:
from tkinter import *
import time
gifdir = "./"
class Game:
def __init__(self):
self.__window = Tk()
igm = PhotoImage(file=gifdir+"empty.gif")
Button(self.__window, image=igm, command= self.change_picture)\
.grid(row=1, column=2, sticky=E)
def change_picture():
igm = PhotoImage(file=gifdir+"new.gif")
Label(self.__window, image=igm,)\
.grid(row=1, column=2, sticky=E)
self.__window.mainloop()
def main():
Game()
main()
When I add this code to the end:
self.__window.update_idletasks()
time.sleep(1)
the new picture is shown for a one second but I need to see it all the time and still be able to press other buttons.
I modified your code, as your code is very strangely designed, and incorrect IMO. This is the modified version:
from tkinter import *
import time
class Game:
def __init__(self):
self.__window = Tk()
self.gifdir = "./"
self.igm = PhotoImage(file=self.gifdir+"empty.gif")
self.btn = Button(self.__window, image=self.igm, command = self.change_picture)
self.btn.grid(row=1, column=2, sticky=E)
self.__window.mainloop()
def change_picture(self):
self.igm = PhotoImage(file=self.gifdir+"new.gif")
self.btn.configure(image = self.igm)
def main():
Game()
main()
In this new version, pressing the button, will change the image on it. Basically, in your class, you need to keep references to created widgets. Especially keeping a reference for PhotoImage is important, as if the reference is not kept, garbage collector will remove the image, when instance of PhotoImage will go out of scope in change_picture.
I expect the same output for both of the scripts below.
But I don't get the image on the button when I execute Script 1. However, Script 2 works well.
Script 1
from Tkinter import *
class fe:
def __init__(self,master):
self.b=Button(master,justify = LEFT)
photo=PhotoImage(file="mine32.gif")
self.b.config(image=photo,width="10",height="10")
self.b.pack(side=LEFT)
root = Tk()
front_end=fe(root)
root.mainloop()
Script 2
from Tkinter import *
root=Tk()
b=Button(root,justify = LEFT)
photo=PhotoImage(file="mine32.gif")
b.config(image=photo,width="10",height="10")
b.pack(side=LEFT)
root.mainloop()
The only reference to the image object is a local variable. When __init__ exits, the local variable is garbage collected so the image is destroyed. In the second example, because the image is created at the global level it never goes out of scope and is therefore never garbage collected.
To work around this, save a reference to the image. For example, instead of photo use self.photo.
its work
x1=Button(root)
photo=PhotoImage(file="Re.png")
x1.config(image=photo,width="40",height="40",activebackground="black"
,bg="black", bd=0,command=sil)
x1.place(relx=1,x=5, y=-5, anchor=NE)
but this is useless
def r():
x1=Button(root)
photo=PhotoImage(file="Re.png")
x1.config(image=photo,width="40",height="40",activebackground="black",
bg="black", bd=0,command=sil)
x1.place(relx=1,x=5, y=-5, anchor=NE)
r()
logo = PhotoImage(file = 'mine32.gif')
small_logo = logo.subsample(5, 5)
self.b.config(image = small_logo , compound = LEFT )
Unrelated answer, but this is the answer I was looking for when I first came here. Use this to resize the image before adding it to the button.
from PIL import Image, ImageTk
image = Image.open("path/to/image.png")
image = image.resize((25, 25), Image.ANTIALIAS)
self.reset_img = ImageTk.PhotoImage(image)
self.button = tk.Button(frame, image=self.reset_img)
from tkinter import *
root= Tk()
btnPlay = Button(root)
btnPlay.config(image=imgPlay, width="30", height="30")
btnPlay.grid(row=0, column=0)
root.mainloop()
I am trying to Make a simple Photo editor. I already got all the functions for editing photos. Just trying to make it look nice with a GUI. When the user presses file>open, the open dialogue box comes up and they can choose an image. Then I want that image to load inside of a Layer in the GUI.
Here is my code so far:
p.s. When I put the code that is inside the open_img function outise the function, it works, but when i put it inside the function, no image loads
import os
from tkinter import *
import tkinter.messagebox
from PIL import Image, ImageTk
from tkinter.filedialog import askopenfilename
def g_quit():
mExit=tkinter.messagebox.askyesno(title="Quit", message="Are You Sure?")
if mExit>0:
mGui.destroy()
return
#open menu
def open_img():
file = tkinter.filedialog.askopenfilename(initialdir='D:/Users/')
w_box = 500
h_box = 500
pil_image = Image.open(file)
w, h = pil_image.size
pil_image_resized = resize(w, h, w_box, h_box, pil_image)
# wr, hr = pil_image_resized.size
tk_image = ImageTk.PhotoImage(pil_image_resized)
label2.config(image=tk_image, width=w_box, height=h_box)
def resize(w, h, w_box, h_box, pil_image):
'''
resize a pil_image object so it will fit into
a box of size w_box times h_box, but retain aspect ratio
'''
f1 = 1.0*w_box/w # 1.0 forces float division in Python2
f2 = 1.0*h_box/h
factor = min([f1, f2])
#print(f1, f2, factor) # test
# use best down-sizing filter
width = int(w*factor)
height = int(h*factor)
return pil_image.resize((width, height), Image.ANTIALIAS)
mGui = Tk()
mGui.title('Photo Filters')
mGui.geometry('650x500')
mGui.resizable(0, 0) #Disable Resizeability
photoFrame = Frame(mGui, bg="orange", width=500, height=500)
photoFrame.pack(side=LEFT)
filtersFrame = Frame(mGui, bg="yellow", width=150, height=500)
filtersFrame.pack(side=LEFT, fill=Y)
label2 = Label(photoFrame)
#Create Buttons for All the Possible Filters
negative_btn = Button(filtersFrame, text="Negative")
negative_btn.pack()
weighted_grayscale_btn = Button(filtersFrame, text="weighted Grayscale")
weighted_grayscale_btn.pack()
solarize_btn = Button(filtersFrame, text="Solarize")
solarize_btn.pack()
black_and_white_btn = Button(filtersFrame, text="Black and White")
black_and_white_btn.pack()
black_and_white_and_gray_btn = Button(filtersFrame, text="Black and White and Gray")
black_and_white_and_gray_btn.pack()
extreme_contrast_btn = Button(filtersFrame, text="Extreme Contrast")
extreme_contrast_btn.pack()
sepia_tint_btn = Button(filtersFrame, text="Sepia Tint")
sepia_tint_btn.pack()
adjust_component_btn = Button(filtersFrame, text="Adjusts Components")
adjust_component_btn.pack()
posterize_btn = Button(filtersFrame, text="Posterize")
posterize_btn.pack()
simplify_btn = Button(filtersFrame, text="Simplify")
simplify_btn.pack()
detect_edges_btn = Button(filtersFrame, text="Detect Edges")
detect_edges_btn.pack()
detect_edges_better_btn = Button(filtersFrame, text="Detect Edges Better")
detect_edges_better_btn.pack()
blur_btn = Button(filtersFrame, text="Blur")
blur_btn.pack()
flip_image = Button(filtersFrame, text="Flip Horizontal")
flip_image.pack()
#Menu Bar
menubar = Menu(mGui)
filemenu = Menu(menubar)
#Create the Menu Options that go under drop down
filemenu.add_command(label="New")
filemenu.add_command(label="Open", command=open_img)
filemenu.add_command(label="Save As")
filemenu.add_command(label="Close", command=g_quit)
#Create the Main Button (e.g file) which contains the drop down options
menubar.add_cascade(label="File", menu=filemenu)
mGui.config(menu=menubar)
mGui.mainloop()
You've actually got two problems here. One of them, you found for yourself—you never call label2.pack(), so the label itself never gets displayed. But, even after you fix that, the label is blank.
As the Label documentation says:
You can use the label to display PhotoImage and BitmapImage objects. When doing this, make sure you keep a reference to the image object, to prevent it from being garbage collected by Python’s memory allocator.
You're not doing that. You store the PhotoImage in a local variable, tk_image, which goes away as soon as the function exits.
That should explain why it works if you move the code outside a function. If you do that, tk_image becomes a global variable, which doesn't go away until the whole program exits. And, since you're already using global variables all over the place implicitly, one possible fix is to use an explicit global for this:
def open_img():
global tk_image
# the rest of your code
However, a much better trick is to just cram the image into an attribute of something else that's going to stick around, like the Label object itself:
def open_img():
# your existing code
label2.tk_image = ImageTk.PhotoImage(pil_image_resized)
label2.config(image=label2.tk_image, width=w_box, height=h_box)
Now, the image can't be garbage collected until the label that's using it gets garbage collected (or until you replace it with a new image).
In fact, now that I look at the docs, this is exactly what the example of using a PhotoImage with a Label does:
photo = PhotoImage(file="icon.gif")
w = Label(parent, image=photo)
w.photo = photo
w.pack()
I'm adding strings to a listbox using the code below. When I run the code and the window opens, the longer strings get clipped as the window is not large enough (see screenshot). I have tried making the window resizeable and adding scroll bars but I was wondering if there was a way to automatically size it to fit the content.
master = tk.Tk()
listbox = tk.Listbox(master, selectmode=tk.SINGLE)
games = ["Garry's Mod", "Mount and Blade: Warband", "Tekkit"]
for game in sorted(games):
listbox.insert(tk.END, game)
button = tk.Button(master, text="Execute", command=execute)
listbox.pack()
button.pack()
tk.mainloop()
Resetting the listbox width worked for me. I used the Oblivion's answer and noticed that the width is always zero.
listbox = tk.Listbox(master, selectmode=tk.SINGLE)
listbox.config(width=0)
I also recommend to reset the root window geometry after reloading a content of the list. Otherwise if user manually extends a window the window would stop accommodate size of its content.
root.winfo_toplevel().wm_geometry("")
just give width and height 0 as below
listbox.config(width=0,height=0)
tkListAutoWidth.py shows one way to do it.
Edit:
So you might have something along the lines of,
import tkinter as tk
from tkinter import font
class NewListbox(tk.Listbox):
def autowidth(self, maxwidth=100)
autowidth(self, maxwidth)
def autowidth(list, maxwidth=100):
f = font.Font(font=list.cget("font"))
pixels = 0
for item in list.get(0, "end"):
pixels = max(pixels, f.measure(item))
# bump listbox size until all entries fit
pixels = pixels + 10
width = int(list.cget("width"))
for w in range(0, maxwidth+1, 5):
if list.winfo_reqwidth() >= pixels:
break
list.config(width=width+w)
if __name__ == "__main__":
master = tk.Tk()
listbox = NewListbox(master, selectmode=tk.SINGLE)
# ...
# ...
keys = serverDict.keys()
for key in sorted(keys):
listbox.insert("end", key)
listbox.pack()
button = tk.Button(master, text="Execute", command=execute)
button.pack()
listbox.autowidth()
master.mainloop()