Python Tkinter Moving images on buttonpress - python

I am trying to write a Tkinter code where it image, which looks like rain will start to move, if button called "Rain" is pressed.
I can not yet tell how the image move part works, but the problem is that when I click on the "Rain" button, it writes -> "Rain" like it should but no image appears on Canvas.
Another interesting thing is that when i take
Here is my code:
root = Tk()
#Create the canvas
canvas = Canvas(width=1000, height=1000)
canvas.pack()
#This is the part that does not work
#Nothing appears when this function is called
def Rain():
image3 = "Drops.png"
drops = PhotoImage(file = image3)
drops_background = canvas1.create_image(100, 100, image=drops)
while True:
canvas1.move(drops_background, 10, 10)
print("Rain")
#Adding a button and making it to use function "Rain"
frame = Frame(root)
frame.pack()
button1 = Button(frame, text = "Rain", command = Rain, fg = "red" ).pack(side = LEFT)
root.mainloop()
Another interesting thing is that if I place this part out of the function it starts working.
image3 = "Drops.png"
drops = PhotoImage(file = image3)
drops_background = canvas1.create_image(100, 100, image=drops)
If anyone could tell me what is wrong here or at least point me in the right direction that would help me out a lot.

There is issue in PhotoImage (or rather in PIL and Pillow module) - PhotoImage must be assigned to global variable.
If PhotoImage is assigned to local variable then Garbage Collector remove it from memory.
My full working example with after
import Tkinter as tk
import random
# --- globals ---
drops_background = None
drops = None
# --- functions ---
def rain():
global drops_background
global drops
filename = "Drops.png"
drops = tk.PhotoImage(file=filename) # there is some error in PhotoImage - it have to be assigned to global variable
drops_background = canvas.create_image(100, 100, image=drops)
# move after 250ms
root.after(250, move) # 250ms = 0.25s
def move():
global drops_background
# TODO: calculate new position
x = random.randint(-10, 10)
y = random.randint(-10, 10)
# move object
canvas.move(drops_background, x, y)
# repeat move after 250ms
root.after(250, move) # 250ms = 0.25s
# --- main ----
root = tk.Tk()
#Create the canvas
canvas = tk.Canvas(root, width=1000, height=1000)
canvas.pack()
#This is the part that does not work
#Nothing appears when this function is called
#Adding a button and making it to use function "Rain"
frame = tk.Frame(root)
frame.pack()
button1 = tk.Button(frame, text="Rain", command=rain, fg="red" )
button1.pack(side=tk.LEFT)
root.mainloop()
after add function and time to its list and mainloop run function from this list after given time.
after expects function name without ()

Related

How to center an image in tkinter with PIL

I want to center an image in tkinter canvas. The only thing I could think of is using anchor = 'c' but that does not seem to work. I have also tried using it on the stage.
def newsetup(filelocation):
global width, height
for widgets in root.winfo_children():
widgets.destroy()
stage = Canvas(root, width = 1000, height = 700, highlightbackground = 'red', highlightthickness = 2)
stage.pack()
imgtk = ImageTk.PhotoImage(Image.open(filelocation))
stage.create_image(stage.winfo_width() + 2, stage.winfo_height() + 2, image = imgtk, anchor = CENTER)
stage.image = imgtk
if you want to center on canvas then you need
stage.winfo_width()/2, stage.winfo_height()/2
(even without anchor= which as default has value center)
But if you put image before it runs mainloop() then stage.winfo_width() stage.winfo_height() gives 0,0 instead of expected width, height (because mainloop creates window and it sets all sizes for widgets) and it may need root.update() to run mainloop once and it will calculate all sizes.
import tkinter as tk
from PIL import ImageTk, Image
root = tk.Tk()
stage = tk.Canvas(root, width=1000, height=700)
stage.pack()
root.update() # force `mainloop()` to calculate current `width`, `height` for canvas
# and later `stage.winfo_width()` `stage.winfo_height()` will get correct values instead `0`
#imgtk = ImageTk.PhotoImage(Image.open('lenna.png'))
imgtk = ImageTk.PhotoImage(file='lenna.png')
stage.create_image((stage.winfo_width()/2, stage.winfo_height()/2), image=imgtk, anchor='center')
stage.image = imgtk
root.mainloop()
Result:
Image Lenna from Wikipedia.
EDIT:
If you want to keep it centered when you resize window then you can bind event <Configure> to canvas and it will execute assigned function - and it can move image.
But it needs image ID which you can get when you create image
img_id = stage.create_image(...)
Because <Configure> is executed when it creates window so code doesn't need root.update() because on_resize() will set correct position at start.
import tkinter as tk
from PIL import ImageTk, Image
# --- functions ---
def on_resize(event):
# it respects anchor
x = event.width/2
y = event.height/2
stage.coords(img_id, x, y)
# it DOESN'T respects anchor so you have to add offset
#x = (event.width - imgtk.width())/2
#y = (event.height - imgtk.height())/2
#stage.moveto(img_id, x, y) # doesn't respect anchor
#stage.itemconfigure(img_id, ...)
# --- main ---
root = tk.Tk()
stage = tk.Canvas(root, width=1000, height=700)
stage.pack(fill='both', expand=True)
#root.update() # force `mainloop()` to calculate current `width`, `height` for canvas
# and later `stage.winfo_width()` `stage.winfo_height()` will get correct values instead `0`
#imgtk = ImageTk.PhotoImage(Image.open('lenna.png'))
imgtk = ImageTk.PhotoImage(file='lenna.png')
img_id = stage.create_image((stage.winfo_width()/2, stage.winfo_height()/2), image=imgtk, anchor='center')
stage.image = imgtk
stage.bind('<Configure>', on_resize) # run function when Canvas change size
root.mainloop()

How do i pause my code before displaying the label

In my code, I am trying to make a loading screen for a frogger game but for some reason I am encountering a problem where I display a picture and then do the .sleep function before displaying a label over the top of it however it displays both of them at the same time it just runs the code 1 second after it should, can anyone help?
Here is my code below:
from tkinter import *
import tkinter as tk
import time
window = Tk()
window.geometry("1300x899")
LoadingScreen = PhotoImage(file = "FroggerLoad.gif")
Loading = Label(master = window, image = LoadingScreen)
Loading.pack()
Loading.place(x = 65, y = 0)
time.sleep(1)
FroggerDisplay = Label(master = window, font ("ComicSans",100,"bold"),text = "Frogger")
FroggerDisplay.pack()
FroggerDisplay.place(x = 500, y = 300)
window.mainloop()
When you use time.sleep(1) before starting the window.mainloop(), the window is created only after 1 second, and the FroggerDisplay label will be created at the same time with it. So, you can't use time.sleep(seconds) now.However, you can use window.after(ms, func) method, and place into the function all the code between time.sleep(1) and window.mainloop(). Note, that unlike the time.sleep(seconds) you must give the time to window.after (the first argument) as milliseconds.Here is the edited code:
from tkinter import *
def create_fd_label():
frogger_display = Label(root, font=("ComicSans", 100, "bold"), text="Frogger") # create a label to display
frogger_display.place(x=500, y=300) # place the label for frogger display
root = Tk() # create the root window
root.geometry("1300x899") # set the root window's size
loading_screen = PhotoImage(file="FroggerLoad.gif") # create the "Loading" image
loading = Label(root, image=loading_screen) # create the label with the "Loading" image
loading.place(x=65, y=0) # place the label for loading screen
root.after(1000, create_fd_label) # root.after(ms, func)
root.mainloop() # start the root window's mainloop
PS: 1) Why do you use .pack(...) and then .place(...) methods at the same time - the first one (.pack(...) here) will be ignored by Tkinter.
2) It's better to use a Canvas widget for creating a game - unlike labels it supports transparency and simpler to use. For example:
from tkinter import *
root = Tk() # create the root window
root.geometry("1300x899") # set the root window's size
canv = Canvas(root) # create the Canvas widget
canv.pack(fill=BOTH, expand=YES) # and pack it on the screen
loading_screen = PhotoImage(file="FroggerLoad.gif") # open the "Loading" image
canv.create_image((65, 0), image=loading_screen) # create it on the Canvas
root.after(1000, lambda: canv.create_text((500, 300),
font=("ComicSans", 100, "bold"),
text="Frogger")) # root.after(ms, func)
root.mainloop() # start the root window's mainloop
Note: you might need to change coords with Canvas.

Add check-boxes to scrollable image in python

Based on the excellent answer of this question: Show Large Image using Scrollbar in Python
… I have been able to create a scrollable image frame, using this code, in Windows, Python 3:
import tkinter
from PIL import ImageTk, Image
class ScrollableImage(tkinter.Canvas):
def __init__(self, master=None, **kw):
self.image = kw.pop('image', None)
super(ScrollableImage, self).__init__(master=master, **kw)
self['highlightthickness'] = 0
self.propagate(0) # wont let the scrollbars rule the size of Canvas
self.create_image(0,0, anchor='nw', image=self.image)
# Vertical and Horizontal scrollbars
self.v_scroll = tkinter.Scrollbar(self, orient='vertical', width=6)
self.h_scroll = tkinter.Scrollbar(self, orient='horizontal', width=6)
self.v_scroll.pack(side='right', fill='y')
self.h_scroll.pack(side='bottom', fill='x')
# Set the scrollbars to the canvas
self.config(xscrollcommand=self.h_scroll.set,
yscrollcommand=self.v_scroll.set)
# Set canvas view to the scrollbars
self.v_scroll.config(command=self.yview)
self.h_scroll.config(command=self.xview)
# Assign the region to be scrolled
self.config(scrollregion=self.bbox('all'))
self.focus_set()
self.bind_class(self, "<MouseWheel>", self.mouse_scroll)
def mouse_scroll(self, evt):
if evt.state == 0 :
# self.yview_scroll(-1*(evt.delta), 'units') # For MacOS
self.yview_scroll( int(-1*(evt.delta/120)) , 'units') # For windows
if evt.state == 1:
# self.xview_scroll(-1*(evt.delta), 'units') # For MacOS
self.xview_scroll( int(-1*(evt.delta/120)) , 'units') # For windows
Now, I would like to add a row of checkboxes with numbers based on a given list numbers. The default option should be that all numbers are checked. One can then uncheck some numbers, and then when a bottom is pressed, all checked numbers should be stored in a new list, i.e. checked_numbers.
This is how it could look like:
This is what I have tried so far:
root = tkinter.Tk()
# Creating the check-boxes using a loop
numbers = [3,16,18,22,45]
for ii in numbers:
var = tkinter.IntVar()
c = tkinter.Checkbutton(root, text=str(ii), variable=var)
c.pack()
# PhotoImage from tkinter only supports:- PGM, PPM, GIF, PNG format.
# To use more formats use PIL ImageTk.PhotoImage
img = tkinter.PhotoImage(file='PATH TO IMAGE')
show_image = ScrollableImage(root, image=img, width=400, height=600)
show_image.pack()
This is how it looks:
There are obviously a lot of flaws and things that I don't know how to implement:
How to assign a new variable in every loop ?
How to have all checked-boxes in a row instead of a column ?
How to store which variable has been checked ?
How to store the checked variables as a new list, when a button is pressed ?
Edit
I tried to use the nice answer from #ncica, which works perfectly on its own, and added the scrollable image...
import tkinter as tk
from tkinter import *
root = tk.Tk()
def read_states():
arry = list(map(lambda var: var.get(), states)) # store which variable has been checked
checked = []
result=(zip(numbers,arry))
for item in result:
if item[1] == 1:
checked.append(item[0]) # store the checked variables in a new list "checked"
print ("Checked are:{}".format(checked))
states = []
numbers = [3,16,18,22,45]
for ii in numbers:
var = IntVar()
chk = Checkbutton(root, text=str(ii), variable=var) # assign a new variable in every loop
chk.grid(row=1,column =numbers.index(ii)) # all checked-boxes put in a row
states.append(var)
button = tk.Button(root, text='OK', command=read_states)
button.grid(row=1,column =len(numbers)+1)
# PhotoImage from tkinter only supports:- PGM, PPM, GIF, PNG format.
# To use more formats use PIL ImageTk.PhotoImage
img = tk.PhotoImage(file='IMAGE_FILE_PATH')
show_image = ScrollableImage(root, image=img, width=400, height=600)
show_image.pack()
root.mainloop()
...but I get the following error:
TclError Traceback (most recent call last)
<ipython-input-11-1556cebf9634> in <module>()
31
32 show_image = ScrollableImage(root, image=img, width=400, height=600)
---> 33 show_image.pack()
34
35
~\Anaconda3\lib\tkinter\__init__.py in pack_configure(self, cnf, **kw)
2138 self.tk.call(
2139 ('pack', 'configure', self._w)
-> 2140 + self._options(cnf, kw))
2141 pack = configure = config = pack_configure
2142 def pack_forget(self):
TclError: cannot use geometry manager pack inside . which already has slaves managed by grid
This can be resolved by replacing the line show_image.pack() with show_image.grid(row=2,column =1, columnspan=len(numbers)+1)
import Tkinter as tk
from Tkinter import *
root = tk.Tk()
def read_states():
arry = list(map(lambda var: var.get(), states)) # store which variable has been checked
checked = []
result=(zip(numbers,arry))
for item in result:
if item[1] == 1:
checked.append(item[0]) # store the checked variables in a new list "checked"
print ("Checked are:{}".format(checked))
states = []
numbers = [3,16,18,22,45]
for ii in numbers:
var = IntVar()
var.set(1) # by default set Checkbuttons as checked
chk = Checkbutton(root, text=str(ii), variable=var) # assign a new variable in every loop
chk.grid(row=1,column =numbers.index(ii)) # all checked-boxes put in a row
states.append(var)
button = tk.Button(root, text='OK', command=read_states) # on button clicked read_states
button.grid(row=1,column =len(numbers)+1) # put button after all Checkbuttons
root.mainloop()
output:
on button OK, checked objects will be printed

Why is my image appearing on the wrong window in Tkinter?

So I am trying to make a text-based game, but also want to incorporate images into it, I have a main menu, a window that is always open, and then a game window, that should have the image in, but for some reason, it appears in the menu window instead. Has anyone got any idea why?
def menu():
master = Tk()
master.geometry("350x300")
master.wm_iconbitmap("zombie.ico")
master.configure(background = '#484a2c')
master.title("Menu")
def game():
master = Tk()
master.geometry("800x500")
master.title("Game")
master.wm_iconbitmap("zombie.ico")
master.configure(background = '#484a2c')
image = PhotoImage(file="Kitchenimage.gif")
label5 = Label(image=image)
label5.image = image
label5.pack()
label = Label(master, text = "Zombie Mansion", background = '#484a2c',
font = ("AR CHRISTY", 30))
label.pack()
b = Button(master,text = "Play Game", height = 1, width = 10)
b.config(background = '#484a2c', activebackground = '#484a2c', font =
("AR CHRISTY", 14), command = get_username)
b.place(x = 120, y = 70)
mainloop()
"Why is my image appearing on the wrong window in Tkinter?"
Assuming you have another instance of Tk as what you refer to as main menu somewhere in your code that you're not showing us like:
main = Tk()
in addition to master = Tk() in your game method, then it's because when you instantiate widgets without passing a parent widget explicitly like in:
label5 = Label(image=image)
then the widget's parent defaults to the Tk instance whose mainloop is being run, in above case supposedly main's. By default widgets are shown under their parents, hence the reason.
Pass parent explicitly like:
label5 = Label(master=main, image=image)
or
label5 = Label(master, image=image)
In most cases, if not all cases, you shouldn't have multiple instances of Tk. If you require additional windows, please use Toplevel widget.

Change position of label in Tkinter window

I am writing a simple program that pulls up an image (BackgroundFinal.png) and displays it in a window. I want to be able to press a button on the window to move the picture down by 22 pixels. Everything works except the button does not do anything.
import Tkinter
import Image, ImageTk
from Tkinter import Button
a = 0 #sets inital global 'a' and 'b' values
b = 0
def movedown(): #changes global 'b' value (adding 22)
globals()[b] = 22
return
def window(): #creates a window
window = Tkinter.Tk();
window.geometry('704x528+100+100');
image = Image.open('BackgroundFinal.png'); #gets image (also changes image size)
image = image.resize((704, 528));
imageFinal = ImageTk.PhotoImage(image);
label = Tkinter.Label(window, image = imageFinal); #creates label for image on window
label.pack();
label.place(x = a, y = b); #sets location of label/image using variables 'a' and 'b'
buttonup = Button(window, text = 'down', width = 5, command = movedown()); #creates button which is runs movedown()
buttonup.pack(side='bottom', padx = 5, pady = 5);
window.mainloop();
window()
If I am not mistaken, the button should change the global 'b' value, therefore changing the y position of the label. I really appreciate any help, sorry for my god-awful conventions. Thanks in advance!
You have a few problems here.
First, you're using pack and place. In general, you should only use 1 geometry manager within a container widget. I don't recommend using place. That's just too much work that you need to manage.
Second, you're calling the callback movedown when you construct your button. That's not what you want to do -- You want to pass the function, not the result of the function:
buttonup = Button(window, text = 'down', width = 5, command = movedown)
Third, globals returns a dictionary of the current namespace -- It's not likely to have an integer key in it. To get the reference to the object referenced by b, you'd need globals()["b"]. Even if it did, changing the value of b in the global namespace won't change the position of your label because the label has no way of knowing that change. And in general, if you need to use globals, you probably need to rethink your design.
Here's a simple example of how I would do it...
import Tkinter as tk
def window(root):
buf_frame = tk.Frame(root,height=0)
buf_frame.pack(side='top')
label = tk.Label(root,text="Hello World")
label.pack(side='top')
def movedown():
buf_frame.config(height=buf_frame['height']+22)
button = tk.Button(root,text='Push',command=movedown)
button.pack(side='top')
root = tk.Tk()
window(root)
root.mainloop()
Thanks for the reply but, It was not really what I was looking for. I'll post what I found worked best here for anybody else with the same problem.
Essentially, It is much better, in this case, to use a Canvas instead of a label. With canvases, you can move objects with canvas.move, here is a simple example program
# Python 2
from Tkinter import *
# For Python 3 use:
#from tkinter import *
root = Tk()
root.geometry('500x500+100+100')
image1 = PhotoImage(file = 'Image.gif')
canvas = Canvas(root, width = 500, height = 400, bg = 'white')
canvas.pack()
imageFinal = canvas.create_image(300, 300, image = image1)
def move():
canvas.move(imageFinal, 0, 22)
canvas.update()
button = Button(text = 'move', height = 3, width = 10, command = move)
button.pack(side = 'bottom', padx = 5, pady = 5)
root.mainloop()
my code may not be perfect (sorry!) but that is the basic idea. Hope I help anybody else with this problem

Categories