I want an image to scroll up inside a canvas. But I am unable to do it.
import tkinter
def scroll_image(event):
global roll
roll = roll - 10
canvas1.itemconfig(canvas1.create_image(20, roll, image=i))
roll = 10
windows = tkinter.Tk()
windows.title("My Application")
# Adding canvas to show image there
canvas1 = tkinter.Canvas(windows, width=200, height=100)
i = tkinter.PhotoImage(file="Capture.gif")
canvas1.create_image(20, 20, image=i)
# trying to implement roll-up image
canvas1.itemconfig(canvas1.create_image(20, 20, image=i))
canvas1.bind("<Configure>", scroll_image)
canvas1.grid(column=0, row=2)
windows.mainloop()
I tried using loops but I noticed that loops are running as expected but unfortunately the canvas update is taking place only once. So I removed the loop. But I need to find a way out to implement a roll up image.
Image is moving up and down.
This example use
module PIL/pillow to load jpg/png instead of gif
after(time_in_millisecond, function_name) to repeat function which moves image
img_id to use only one image (instead of creating many images with create_image)
canvas.move(ID, offset_x, offset_y) to move image (or other object)
canvas.coords(ID) to get current positon of image (or other object)
canvas.pack(fill='both', expand=True) to use full window. Canvas will use full window even when you resize window.
Code:
import tkinter as tk
from PIL import Image, ImageTk
def scroll_image():
global offset_y # inform function that you want to assign value to external variable instead of local one.
# move image
canvas.move(img_id, offset_x, offset_y)
# get current position
x, y = canvas.coords(img_id)
print(x, y)
# set position (if you don't use canvas.move)
#canvas.coords(img_id, x+offset_x, y+offset_y)
# x += offset_x
# y += offset_y
# change direction
if y <= -100 or y >= 0:
offset_y = -offset_y
# repeat after 20ms
root.after(20, scroll_image)
offset_x = 0
offset_y = -3
root = tk.Tk()
canvas = tk.Canvas(root)
canvas.pack(fill='both', expand=True) # use full window
#photo = tk.PhotoImage(file="Capture.gif")
image = Image.open("image.jpg")
photo = ImageTk.PhotoImage(image)
img_id = canvas.create_image(0, 0, image=photo)
# start scrolling
scroll_image()
root.mainloop()
You can use also canvas.coords(ID, x, y) to set new position.
More examples on GitHub for: Tkinter and other Python's modules
Related
I am new to Tkinter. I want to control the movement of the image with a cursor and a small window:
enter image description here
I tried with this code, but the result is not really what I'm looking for.
import tkinter as tk
from io import BytesIO
import requests
from PIL import Image , ImageTk
def full_dimensions(imag_fs):
top = tk.Toplevel(root)
img = tk.Label(top, image=imag_fs)
img.pack()
def get_image():
_url = 'https://i.imgur.com/4m7AHVu.gif'
_img = requests.get(_url)
if _img.status_code == 200:
_content = BytesIO(_img.content)
else:
_content = 'error.gif'
print('image loaded')
return _content
root = tk.Tk()
_content = get_image()
_x = Image.open(_content)
imag_fs = ImageTk.PhotoImage(_x)
_x.thumbnail((100, 100), Image.ANTIALIAS)
imag = ImageTk.PhotoImage(_x)
img = tk.Button(root, image=imag, command=lambda: full_dimensions(imag_fs))
img.grid(column=3, row=1)
root.mainloop()
I tested a window, but when I import an image I can't control
import tkinter as tk
main_window = tk.Tk()
def check_hand_enter():
canvas.config(cursor="hand1")
def check_hand_leave():
canvas.config(cursor="")
canvas = tk.Canvas(width=200, height=200)
tag_name = "polygon"
canvas.create_polygon((25, 25), (25, 100), (125, 100), (125, 25), outline='black', fill="", tag=tag_name)
canvas.tag_bind(tag_name, "<Enter>", lambda event: check_hand_enter())
canvas.tag_bind(tag_name, "<Leave>", lambda event: check_hand_leave())
canvas.pack()
main_window.mainloop()
So here is how I would do this (a simple example):
# import all necessary modules and classes
from tkinter import Tk, Canvas, Frame
from PIL import Image, ImageTk
import requests
# checking if the file exists, if it doesn't exist download it, if can't download it, exit the program
try:
open('space.jpg')
except FileNotFoundError:
url = 'https://images5.alphacoders.com/866/866360.jpg'
img = requests.get(url)
if img.status_code == 200:
with open('space.jpg', 'wb') as file:
file.write(img.content)
print('File not found. Downloaded the necessary file.')
else:
print('File not found. Could not download the necessary file.')
exit()
class MovableImage(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
# dictionary for storing information about movement
self.start_coords = {'x': 0, 'y': 0, 'move': False}
# dictionary for storing information about movement
self.start_coords_main = {'x': 0, 'y': 0, 'move': False}
# loads the image
self.image = Image.open('space.jpg')
# sets the images to their corresponding variables so that they can be referenced later
# resizes the smaller image to fit the navigation window
self.main_image = ImageTk.PhotoImage(self.image)
self.nav_image = ImageTk.PhotoImage(self.image.resize((200, 100), Image.ANTIALIAS))
# creates the canvas to store the bigger image on
self.main_canvas = Canvas(self, width=700, height=500, highlightthickness=0)
self.main_canvas.pack()
# puts image on canvas
self.main_image_id = self.main_canvas.create_image((0, 0), image=self.main_image, anchor='nw', tags='main_image')
# creates the smaller canvas that will be used for navigation
self.nav_canvas = Canvas(self.main_canvas, width=200, height=100, highlightthickness=0)
# adds the smaller canvas as a window to the main_canvas
self.main_canvas.create_window((500, 400), window=self.nav_canvas, anchor='nw', tags='nav_canvas')
# adds the resized image to nav_canvas
self.nav_canvas.create_image((0, 0), image=self.nav_image, anchor='nw')
# creates a rectangle to indicate the current view of the image
self.nav_box = self.nav_canvas.create_rectangle((0, 0, 70, 50), outline='white')
# binds functions
self.main_canvas.bind('<Button-1>', self.set_start_coords_main)
self.main_canvas.bind('<B1-Motion>', self.move_coords_main)
# binds functions
self.nav_canvas.bind('<Button-1>', self.set_start_coords)
self.nav_canvas.bind('<B1-Motion>', self.move_coords)
# function that sets the starting coords so that they can be referenced later, also sets whether the box can be moved at all
def set_start_coords(self, event):
x1, y1, x2, y2 = self.nav_canvas.coords(self.nav_box)
if x1 < event.x < x2 and y1 < event.y < y2:
self.start_coords['x'] = event.x - x1
self.start_coords['y'] = event.y - y1
self.start_coords['move'] = True
else:
self.start_coords['move'] = False
# the moving part, this takes reference from the starting coords and uses them for calculation
# basic border checks and then the actual moving
def move_coords(self, event):
if not self.start_coords['move']:
return
dx = self.start_coords['x']
dy = self.start_coords['y']
x = event.x - dx
y = event.y - dy
if x < 0:
x = 0
elif x + 70 > 200:
x = 130
if y < 0:
y = 0
elif y + 50 > 100:
y = 50
self.nav_canvas.coords(self.nav_box, x, y, x + 70, y + 50)
self.main_canvas.coords(self.main_image_id, -x * 10, -y * 10)
# function that sets the starting coords so that they can be referenced later, also sets whether the box can be moved at all
def set_start_coords_main(self, event):
x1, y1, x2, y2 = self.main_canvas.bbox('main_image')
if x1 < event.x < x2 and y1 < event.y < y2:
self.start_coords_main['x'] = event.x - x1
self.start_coords_main['y'] = event.y - y1
self.start_coords_main['move'] = True
else:
self.start_coords_main['move'] = False
# the moving part, this takes reference from the starting coords and uses them for calculation
# basic border checks and then the actual moving
def move_coords_main(self, event):
if not self.start_coords_main['move']:
return
dx = self.start_coords_main['x']
dy = self.start_coords_main['y']
x = event.x - dx
y = event.y - dy
if x < -1300:
x = -1300
elif x > 0:
x = 0
if y < -500:
y = -500
elif y > 0:
y = 0
self.nav_canvas.coords(self.nav_box, -x / 10, -y / 10, -x / 10 + 70, -y / 10 + 50)
self.main_canvas.coords(self.main_image_id, x, y)
# basic Tk() instance and afterwards the root.mainloop()
root = Tk()
MovableImage(root).pack()
# mainloop
root.mainloop()
Few things to mention: this is a pretty hardcoded sample that pretty much works well with only images whose resolution is 2000x1000 pixels, other images may not be shown correctly or resizing won't look as good. With this issue You will have to deal Yourself or ask another question about what issues You encountered while trying to make this adjustable. So it would be great if any image could be put up there and it would work.
About the code, it is pretty simple:
Import modules
check for file (there could be a better way but this works too), then if the file does not exist just download it, if that can't be done, well just exit the program.
Then setting up some references (a dictionary so that global doesn't have to be used and also I would say that a dictionary is better for other reasons too like all the necessary variables are in one place and for example x and y variable names are not taken globally).
Then setting the first function which will register the mouse click. This is done to get the position of mouse relative to the moveable square (basically where is the mouse on the square if the square was an independent window or sth) and it is made possible to move, but before that the function checks if the mouse is inside the square at all. If not moving is disabled so that for example if You were to click outside of the square and start moving it does not do that.
Then the moving function is defined. There the relative x and y coords are retrieved but first it checks if it can move and if it cannot, it stops the execution of that function. Then some more calculations and checking for border (again this is pretty hardcoded and should be changed to a more dynamic function so that it can detect this based on picture). Then comes the moving part which just move both the navigation box and the actual picture to their according spots (this is somewhat hardcoded but basically if You were to keep the scale to 1/10 of the smaller box, then this specific part would work with different images).
Then comes the basic Tk() initiation and later the .mainloop().
In the middle just open the image and set it to 2 variables and resize the one that will go to the navigation box.
Then create the main canvas where the main image will be shown.
Then add that image to canvas and keep the id reference so that the image can be later moved.
Create the smaller canvas and don't pack it or anything but add the instance as a window to the main canvas so that it is on top of it (as in the photo in Your question). Again it is placed in a hardcoded manner.
Then add the navigation image to the navigation canvas and add the small box that will be moved too.
Then bind functions to mouse activity in nav_canvas.
If You have questions ask. Note that the try/except is not necessary if You are using Your own images and it will always come with the program. It is there because You may not have this exact picture with those exact dimensions with that exact name so it is kinda temporary for testing purposes.
EDIT: put code in a class that inherits from Frame so that this can be placed as a widget.
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.
I am attempting to create a simple window that maintains a square shape when resized, using python 3.6.4 and tkinter 8.6. Here is my code that produces a window, but does not maintain its aspect ratio when resized.
import tkinter as tk
w = tk.Tk()
w.aspect(1,1,1,1)
w.mainloop()
maybe you can use a canvas to make that, he detect event (image size changed) and .place relative x and relative y. I tryed make a script to help you, but you need make somes changes
from tkinter import *
# create a canvas with no internal border
canvas = Canvas(bd=0, highlightthickness=0)
canvas.pack(fill=BOTH, expand=1)
lastw, lasth = canvas.winfo_width(), canvas.winfo_height()
# track changes to the canvas size and draw
# a rectangle which fills the visible part of
# the canvas
def configure(event):
global lastw, lasth
canvas.delete("all")
w, h = event.width, event.height
try:
label.config(font = ('Arial ', int(12 * ((w - lastw) / (h - lasth))))) # -- this formula need change :3
except ZeroDivisionError: pass
lastw, lasth = canvas.winfo_width(), canvas.winfo_height()
canvas.bind("<Configure>", configure)
label = Label(canvas, text = "YOLO")
label.place(relx = 0.5, rely = 0.5) # - this make the widget automatic change her pos
mainloop()
you can see how this work here http://effbot.org/zone/tkinter-window-size.htm
I am working on a Choose-Your-Own-Adventure game in Python. Every so often, I want the user to be given a scene, and be allowed to find objects in that scene. Whatever they find can be put in their inventory, and whenever they are ready to stop looking for items, they can hit an "Exit" button in the bottom left. However, I am running into some errors with actually getting the button click coordinates to actually do something.
So far, I have this:
root = Tk()
imageIQ1 = Canvas(root, width=1000, height=1000)
imageIQ1.pack()
original = Image.open("prison.jpg")
original = original.resize((1000,1000)) #resize image
img = ImageTk.PhotoImage(original)
imageIQ1.create_image(0, 0, image=img, anchor="nw")
def getcoors(eventcoors):
global x0,y0
x0 = evencoors.x
y0 = evencoors.y
print(x0,y0)
After the user clicks a certain set of coordinates (or somewhere near them), I want the program to clear the picture from the screen and continue with the program. For insance, clicking anywhere from (300, 400) to (500, 500) would close the picture and continue with the rest of the program. I know this will use some form of loop like
while (x not in range) and (y not in range):
But I am unsure of what I would actually do to clear the image. I read about using something like .kill() and .terminate(), but they don't work in this situation.
Any ideas?
You need to have a reference for the image in order to later be able to delete it as in:
canvImg = imageIQ1.create_image(0, 0, image=img, anchor="nw")
and then when you call:
imageIQ1.delete(canvImg)
it will get deleted.
Based on this you can put that in an event method like:
def motion(event):
x, y = event.x, event.y
someSpecificX = 142
someSpecificY = 53
marginX = 100
marginY = 100
print(x, y)
if x in range(someSpecificX - marginX, someSpecificX + marginX):
if y in range(someSpecificY - marginY, someSpecificY + marginY):
imageIQ1.delete(canvImg)
imageIQ1.bind('<Button-1>', motion)
Your final code should look like:
from tkinter import *
from PIL import ImageTk, Image
root = Tk()
imageIQ1 = Canvas(root, width=1000, height=1000)
imageIQ1.pack()
original = Image.open("prison.jpg")
original = original.resize((1000,1000)) #resize image
img = ImageTk.PhotoImage(original)
canvImg = imageIQ1.create_image(0, 0, image=img, anchor="nw")
def motion(event):
x, y = event.x, event.y
someSpecificX = 142
someSpecificY = 53
marginX = 100
marginY = 100
print(x, y)
if x in range(someSpecificX - marginX, someSpecificX + marginX):
if y in range(someSpecificY - marginY, someSpecificY + marginY):
imageIQ1.delete(canvImg)
imageIQ1.bind('<Button-1>', motion)
root.mainloop()
I am beginning to learn Python after being trapped using VB6 forever. To help myself learn some gui with Tkinter, I'm making a simple slot machine. To animate the wheels, I have a ~300x2000 image that has all of the pictures I intend to use displayed as a strip. About 6 pictures right now. I just show part of the image (wheel) at a time to make it spin. Anyway, I'm having trouble with the part of the code where I come to the end of the image and need to transition from the end of the image back to the beginning (wheel spins down, so the start is the bottom and end is the top).
For some reason, I can't get the images to crop and display correctly, even though I think my script makes sense logically. My code is posted below. You can make an image around the resolution of 300x2000 or so to use with this to see the problem I am having. My code starts printing outside of the area that I want it to show (outside the showsize variable) and I can't figure out why.
Any help with this problem would be much appreciated. It seems that the crops don't cut the images short enough, but all the information that I found about it make me think my script should be working just fine. I've tried to annotate my code to explain what's going on.
from Tkinter import *
from PIL import Image, ImageTk
def main():
root = Tk()
root.title = ("Slot Machine")
canvas = Canvas(root, width=1500, height=800)
canvas.pack()
im = Image.open("colors.png")
wheelw = im.size[0] #width of source image
wheelh = im.size[1] #height of source image
showsize = 400 #amount of source image to show at a time - part of 'wheel' you can see
speed = 3 #spin speed of wheel
bx1 = 250 #Box 1 x - where the box will appear on the canvas
by = 250 #box 1 y
numberofspins = 100 #spin a few times through before stopping
cycle_period = 0 #amount of pause between each frame
for spintimes in range(1,numberofspins):
for y in range(wheelh,showsize,-speed): #spin to end of image, from bottom to top
cropped = im.crop((0, y-showsize, wheelw, y)) #crop which part of wheel is seen
tk_im = ImageTk.PhotoImage(cropped)
canvas.create_image(bx1, by, image=tk_im) #display image
canvas.update() # This refreshes the drawing on the canvas.
canvas.after(cycle_period) # This makes execution pause
for y in range (speed,showsize,speed): #add 2nd image to make spin loop
cropped1 = im.crop((0, 0, wheelw, showsize-y)) #img crop 1
cropped2 = im.crop((0, wheelh - y, wheelw, wheelh)) #img crop 2
tk_im1 = tk_im2 = None
tk_im1 = ImageTk.PhotoImage(cropped1)
tk_im2 = ImageTk.PhotoImage(cropped2)
#canvas.delete(ALL)
canvas.create_image(bx1, by, image=tk_im2) ###THIS IS WHERE THE PROBLEM IS..
canvas.create_image(bx1, by + y, image=tk_im1) ###PROBLEM
#For some reason these 2 lines are overdrawing where they should be. as y increases, the cropped img size should decrease, but doesn't
canvas.update() # This refreshes the drawing on the canvas
canvas.after(cycle_period) # This makes execution pause
root.mainloop()
if __name__ == '__main__':
main()
It's simpler, and much faster and smoother if you don't recreate your images on every cycle. Here's my solution using canvas.move(). Notice that I moved the canvas.create_image calls outside of the for loop. I also put the code in a class and added a 'spin' button, and added something to make it exit without errors.
from Tkinter import *
from PIL import Image, ImageTk
import sys
class SlotMachine():
def __init__(self):
root = Tk()
root.title = ("Slot Machine")
self.canvas = Canvas(root, width=1200, height=800)
self.canvas.grid(column=0,row=0)
button = Button(root, text="Spin!", width=20, command = self.spin)
button.grid(column=0,row=1)
self.alive = True
root.protocol("WM_DELETE_WINDOW", self.die)
root.mainloop()
def spin(self):
im = Image.open("colors.png")
wheelw = im.size[0] #width of source image
wheelh = im.size[1] #height of source image
showsize = 400 # amount of source image to show at a time -
# part of 'wheel' you can see
speed = 3 #spin speed of wheel
bx1 = 250 #Box 1 x - where the box will appear on the canvas
by = 250 #box 1 y
numberofspins = 100 #spin a few times through before stopping
cycle_period = 3 #amount of pause between each frame
tk_im1 = ImageTk.PhotoImage(im)
tk_im2 = ImageTk.PhotoImage(im)
im1id = self.canvas.create_image(bx1, by + showsize, image=tk_im1)
im2id = self.canvas.create_image(bx1, by + showsize + wheelh,
image=tk_im2)
for spintimes in range(1,numberofspins):
for y in range(wheelh,0,-speed):
if self.alive:
self.canvas.move(im1id, 0, -speed)
self.canvas.move(im2id, 0, -speed)
self.canvas.update()
self.canvas.after(cycle_period)
else:
sys.exit()
self.canvas.move(im1id, 0, wheelh)
self.canvas.move(im2id, 0, wheelh)
def die(self):
self.alive = False
if __name__ == '__main__':
mySlotMachine = SlotMachine()
This will place parts of the wheel outside of its box, but you can just put your slot machine texture on top of that.