I need the images to change if the cursor is inside the window without moving, but I managed to make the image changes only if the cursor is moving inside the window, how can I change the code?
from itertools import cycle
import tkinter as tk
from PIL import ImageTk, Image
import glob
image_files = glob.glob("*.jpg")
root = tk.Toplevel()
root.geometry("1600x900")
pictures = cycle((ImageTk.PhotoImage(file=image), image) for image in image_files)
picture_display = tk.Label(root)
picture_display.pack()
def show_slides(event):
img_object, img_name = next(pictures)
root.after(500, picture_display.config(image=img_object))
root.bind("<Motion>", show_slides)
root.mainloop()
You could bind the <Enter> and <Leave> events and use a flag to control the call, followed by using the after method to loop the function.
def show_slides(event=None):
global change_slide
img_object, img_name = next(pictures)
picture_display.config(image=img_object)
change_slide=root.after(500,show_slides)
def stop_slides(event):
root.after_cancel(change_slide)
root.bind("<Enter>", show_slides)
root.bind("<Leave>", stop_slides)
UPDATE
Using a flag might cause multiple calls being scheduled it the events happen during the 500ms delay, you can use after_cancel to terminate it.
You can calculate cursor position in loop and show image if it's located within tkinter window:
import glob
import tkinter as tk
from PIL import ImageTk
from itertools import cycle
class App(object):
def __init__(self):
self.root = tk.Tk()
self.root.geometry('900x600')
self.lbl = tk.Label(self.root)
self.lbl.pack()
files = glob.glob('*.jpg')
self.images = cycle((ImageTk.PhotoImage(file=f), f) for f in files)
self.show()
self.root.mainloop()
def show(self):
abs_coord_x = self.root.winfo_pointerx() - self.root.winfo_rootx()
abs_coord_y = self.root.winfo_pointery() - self.root.winfo_rooty()
if 0 <= abs_coord_x <= self.root.winfo_width() and 0 <= abs_coord_y <= self.root.winfo_height():
img_object, img_name = next(self.images)
self.lbl.config(image=img_object)
self.root.after(1000, self.show)
App()
Related
I have created a simple program to download from a private azure container and rename a series of .jpg files listed in a csv file. I'm still learning python so I am sure the code is a bit on the rough side! That said, the code works fine and the files download correctly. However, I would like to display a pop up progress bar showing the current progress. I have looked at a number of examples but I'm not sure how best to approach this. Could anyone offer some pointers on the best way? Thanks.
from tkinter import messagebox
import urllib.request
import csv
from datetime import datetime, timedelta
from azure.storage.blob import BlockBlobService
from azure.storage.blob.models import BlobPermissions
from azure.storage.blob.sharedaccesssignature import BlobSharedAccessSignature
account_name = '***'
account_key = '***'
top_level_container_name = '***'
blob_service = BlockBlobService(account_name, account_key)
blob_shared = BlobSharedAccessSignature(account_name, account_key)
root = Tk()
root.withdraw()
csvDir = filedialog.askopenfilename(initialdir="/", title="SELECT CSV FILE", filetypes=(("CSV files", "*.csv"), ("all files", "*.*")))
imageDir = filedialog.askdirectory()
with open(csvDir) as images:
images = csv.reader(images)
img_count = 1
for image in images:
sas = blob_shared.generate_blob(container_name=top_level_container_name, blob_name=image[0], permission=BlobPermissions.READ, start=datetime.now(), expiry=datetime.now() + timedelta(hours=1))
sas_url = 'https://' + account_name + '.blob.core.windows.net' + '/' + top_level_container_name + '/' + image[0] + '?' + sas
print(sas_url)
urllib.request.urlretrieve(sas_url, imageDir + "/{}.jpg".format(image[1]))
img_count += 1
messagebox.showinfo("Complete", "Print images have been downloaded")
root.mainloop()
The Base Code
The base code that this project uses as its baseline is Shichao's Blog Post, Progress/speed indicator for urlretrieve() in Python and my version of it is simply a tkinter wrapper is big props to Shichao for the amazing Blog Post.
The Tkinter Code
If you merely wish to see the code without the breakdown, you simply see it and copy and paste the given example:
import time
import urllib.request
import tkinter as tk
import tkinter.ttk as ttk
class download_toplevel:
def __init__(self, master):
self.master = master
self.download_button = tk.Button(self.master, text="Download", command=self.download)
self.download_button.grid(row=0, column=0)
self.root = tk.Toplevel(self.master)
self.progress_bar = ttk.Progressbar(self.root, orient="horizontal",
length=200, mode="determinate")
self.progress_bar.grid(row=0, column=0)
self.progress_bar.grid_rowconfigure(0, weight=1)
self.progress_bar.grid_columnconfigure(0, weight=1)
self.progress_bar["maximum"] = 100
self.root.withdraw()
# See https://blog.shichao.io/2012/10/04/progress_speed_indicator_for_urlretrieve_in_python.html
def reporthook(self, count, block_size, total_size):
print(count, block_size, total_size)
if count == 0:
self.start_time = time.time()
return
duration = time.time() - self.start_time
progress_size = int(count * block_size)
speed = int(progress_size / (1024 * duration))
percent = min(int(count*block_size*100/total_size), 100)
print(percent)
self.progress_bar["value"] = percent
self.root.update()
def save(self, url, filename):
urllib.request.urlretrieve(url, filename, self.reporthook)
def download(self):
self.progress_bar["value"] = 0
self.root.deiconify()
self.save("https://files02.tchspt.com/storage2/temp/discord-0.0.9.deb", "discord-0.0.9.deb")
self.root.withdraw()
def main():
root = tk.Tk()
#root.withdraw()
downloader = download_toplevel(root)
root.mainloop()
if __name__ == '__main__':
main()
The Breakdown
The imports
import time
import urllib.request
import tkinter as tk
import tkinter.ttk as ttk
The import of tkinter.ttk is important as the Progress Bar widget is not found in the default tkinter module.
The main loop
def main():
root = tk.Tk()
downloader = download_toplevel(root)
root.mainloop()
if __name__ == '__main__':
main()
The line if __name__ == '__main__' means if the code is run directly without being imported by another python program execute the following statement, so on you running $ python3 main.py, the code will run the main function.
The main function works by creating the main window root, and passing the window into a variable within the class download_toplevel. This is a neater way of writing python code and is more versatile when working with tkinter as it simplifies the code when looking at it in the future.
The Download Button
self.master = master
self.download_button = tk.Button(self.master, text="Download", command=self.download)
self.download_button.grid(row=0, column=0)
This code adds a button labelled download bound to the command download
The Toplevel Window
self.root = tk.Toplevel(self.master)
This creates a toplevel window that appears on top of the master window
The Progress Bar
self.progress_bar = ttk.Progressbar(self.root, orient="horizontal",
length=200, mode="determinate")
self.progress_bar.grid(row=0, column=0)
self.progress_bar.grid_rowconfigure(0, weight=1)
self.progress_bar.grid_columnconfigure(0, weight=1)
self.progress_bar["maximum"] = 100
This creates a ttk.Progressbar onto the Toplevel window, oriented in the x plane, and is 200px long. The grid_*configure(0, weight=1) means that the progress bar should grow to fit the given space, to make sure this happens you should also include , sticky='nsew' in the grid command. self.progress_bar["maximum"] = 100 means that the max value is 100%.
Withdraw
self.root.withdraw()
This withdraws the Toplevel window until it is actually required, i.e. when the download button is pressed.
The Report Hook
def reporthook(self, count, block_size, total_size):
print(count, block_size, total_size)
if count == 0:
self.start_time = time.time()
return
duration = time.time() - self.start_time
progress_size = int(count * block_size)
speed = int(progress_size / (1024 * duration))
percent = min(int(count*block_size*100/total_size), 100)
This code is taken directly from Shichao's Blog Post, and simply works out the percentage completed.
self.progress_bar["value"] = percent
self.root.update()
The Progress Bar is then set to the current percentage and the Toplevel is updated, otherwise it remains as a black window (tested on Linux).
The save function
def save(self, url, filename):
urllib.request.urlretrieve(url, filename, self.reporthook)
This bit of code has been straight up taken from the Blog Post and simply downloads the file and on each block being downloaded and written the progress is sent to the self.reporthook function.
The Download Function
def download(self):
self.progress_bar["value"] = 0
self.root.deiconify()
self.save("https://files02.tchspt.com/storage2/temp/discord-0.0.9.deb", "discord-0.0.9.deb")
self.root.withdraw()
The download function resets the Progress Bar to 0%, and then the root window is raised (outside of the withdraw). Then the save function is run, you may wish to rewrite the self.save call to read self.after(500, [the_function]) so that your main window is still updated as this program is run. Then the Toplevel window is withdrawn.
Hope this helps,
James
My plotting library needs to be able to show multiple plots at the same time, each of which is represented as a PIL image, and each of which should show up as its own window. The windows should be independent, so closing any one of them should not affect the others, but when all of them have been closed the main loop should exit. This behavior was easy to achieve in qt and wx, but in qt it's proving difficult so far.
Here's the closest I've come so far:
from six.moves import tkinter
from PIL import ImageTk
class Window:
def __init__(self, img):
self.window = tkinter.Toplevel()
self.window.minsize(img.width, img.height)
self.canvas = tkinter.Canvas(self.window, width=img.width, height=img.height)
self.canvas.pack()
self.canvas.configure(background="white")
self.photo = ImageTk.PhotoImage(img)
self.sprite = self.canvas.create_image(0, 0, image=self.photo, anchor=tkinter.NW)
windows = []
for img in imgs:
windows.append(Window(img))
if len(windows) > 0: windows[0].window.mainloop()
This displays an image in each window, and each of those windows can be closed independently. But it also displays an empty root window which needs to be closed for the main loop to exit, and which will cause all windows to close when closed, which is not the behavior I want.
If I replace tkinter.Toplevel() with tkinter.Tk(), then create_image fails for the second window with an obscure "pyimageX does not exist" error message, where X is an incrementing integer.
Will I have to make an invisible root window, and then manually count how many child windows have closed and trigger destruction of the invisible root window when all of them have closed in order to get the behavior I'm looking for? Or is there a simple way to achieve this?
Edit: Just to clarify: My program is not mainly a Tk app. It spends almost all its time doing other stuff, and only temporarily uses Tk in a single function to display some plots. That's why it's important that the main loop exits after the plots have been closed, to the program can resume its normal operation. Think about how show() in matplotlib works for an example of this scenario.
Here is an example of how you might want to do this. This example uses the root window to house a button that will open up all images at the top level.
Make sure you change self.path to your image folder.
import tkinter as tk
import os
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
tk.Button(self, text="Open Images", command=self.open_images).pack()
self.path = ".\RGB"
def open_images(self):
directory = os.fsencode(self.path)
for file in os.listdir(directory):
filename = os.fsdecode(file)
if filename.endswith(".gif"):
print(filename)
top = tk.Toplevel(self)
img = tk.PhotoImage(file="{}\{}".format(self.path, filename))
lbl = tk.Label(top, image=img)
lbl.image = img
lbl.pack()
if __name__ == '__main__':
app = App()
app.mainloop()
Here is my 2nd example where you can hide the root window and when the last top level window is closed the tkinter instance is also destroyed. This is maned with a simple tracking variable.
import tkinter as tk
import os
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.top_level_count = 0
self.path = ".\RGB"
self.open_images()
self.withdraw()
def open_images(self):
directory = os.fsencode(self.path)
for file in os.listdir(directory):
filename = os.fsdecode(file)
if filename.endswith(".gif"):
self.top_level_count += 1
image_top(self, self.path, filename)
def check_top_count(self):
print(self.top_level_count)
if self.top_level_count <= 0:
self.destroy()
class image_top(tk.Toplevel):
def __init__(self, controller, path, filename):
tk.Toplevel.__init__(self, controller)
self.controller = controller
self.protocol("WM_DELETE_WINDOW", self.handle_close)
img = tk.PhotoImage(file="{}\{}".format(path, filename))
lbl = tk.Label(self, image=img)
lbl.image = img
lbl.pack()
def handle_close(self):
self.controller.top_level_count -= 1
self.destroy()
self.controller.check_top_count()
if __name__ == '__main__':
app = App()
app.mainloop()
Ok so here's a couple of classes I came up with to solve this problem:
class ImgRoot(tkinter.Tk):
def __init__(self, imgs):
super(ImgRoot, self).__init__()
for i in imgs:
Window(self, i)
self.withdraw()
self.open=True
self.tick()
def tick(self):
if not self.open:
self.destroy()
self.open=False
self.after(100, self.tick)
def checkin(self):
self.open=True
class Window(tkinter.Toplevel):
def __init__(self, root, img):
super(Window, self).__init__()
self.root=root
self.tick()
self.minsize(img.width, img.height)
self.canvas = tkinter.Canvas(self, width=img.width, height=img.height)
self.canvas.pack()
self.canvas.configure(background="white")
self.photo = ImageTk.PhotoImage(img)
self.sprite = self.canvas.create_image(0, 0, image=self.photo, anchor=tkinter.NW)
def tick(self):
self.root.checkin()
self.after(100, self.tick)
The idea here is to create a main class (ImgRoot) which handles the whole thing. Then, every 0.1 seconds (100 miliseconds), it will check if any of the image windows have told it that they are still alive, and, if not, close. The image windows (Windows) do this by setting the ImgRoot's open attribute to True every 0.1 seconds that they are alive. Here is an example usage:
import tkinter
#above classes go here
ImgRoot(imgs) #imgs is a list as defined in your question
tkinter.mainloop()
print('done') #or whatever you want to do next
I want to make a class that has a picture and it is changed to the next one by mouse click.I'm new to oop, my idea here was to make class similar to real life where there is new class instance for every new picture, is it possible to do it this way? Here is my code
import tkinter as tk
from PIL import Image,ImageTk
class Picture():
_count=1
def __init__(self,window):
self.id=Picture._count
Picture._count+=1
self.img=Image.open(r'C:\ImgArchive\img%s.png' % self.id)
self.pimg = ImageTk.PhotoImage(self.img)
self.lab=tk.Label(window,image=self.pimg)
self.lab.pack()
self.lab.bind('<1>',self.click)
def click(self,event):
self.lab.destroy()
self=self.__init__(window)
window = tk.Tk()
window.title('Album')
window.geometry('1200x900')
pic=Picture(window)
window.mainloop()
It works fine, but i'm not sure that old instances of my class is deleted, are they? And i use self.lab.destroy() because if i dont new picture appears down, like this
instead of this
So why it happens?What is elegant way for it?
Below example produces a simple image viewer tested with path of C:\Users\Public\Pictures\Sample Pictures, let me know if anything's unclear:
import tkinter as tk
from PIL import Image, ImageTk
#required for getting files in a path
import os
class ImageViewer(tk.Label):
def __init__(self, master, path):
super().__init__(master)
self.path = path
self.image_index = 0
self.list_image_files()
self.show_image()
self.bind('<Button-1>', self.show_next_image)
def list_files(self):
(_, _, filenames) = next(os.walk(self.path))
return filenames
def list_image_files(self):
self.image_files = list()
for a_file in self.list_files():
if a_file.lower().endswith(('.jpg', '.png', '.jpeg')):
self.image_files.append(a_file)
def show_image(self):
img = Image.open(self.path + "\\" + self.image_files[self.image_index])
self.img = ImageTk.PhotoImage(img)
self['image'] = self.img
def show_next_image(self, *args):
self.image_index = (self.image_index + 1) % len(self.image_files)
self.show_image()
root = tk.Tk()
mypath = r"C:\Users\Public\Pictures\Sample Pictures"
a = ImageViewer(root, mypath)
a.pack()
root.mainloop()
I am having two or more python Tkinter files. Each file is opening one window, how can run all the Tkinter windows functionality in one main window.
Ex : I have two files one is usbcam.py which will open USB camera and give the video steaming and the other one is ipcam.py it opens the IP camera and give the live streaming.This two files are opening in two windows how can make this to work in one window
usbcam.py
import cv2
import PIL.Image
import PIL.ImageTk
import Tkinter as tk
def update_image(image_label, cv_capture):
cv_image = cv_capture.read()[1]
cv_image = cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB)
pil_image = PIL.Image.fromarray(cv_image)
pil_image.save('image3.jpg')
tk_image = PIL.ImageTk.PhotoImage(image=pil_image)
image_label.configure(image=tk_image)
image_label._image_cache = tk_image # avoid garbage collection
root.update()
def update_all(root, image_label, cv_capture):
if root.quit_flag:
root.destroy() # this avoids the update event being in limbo
else:
update_image(image_label, cv_capture)
root.after(10, func=lambda: update_all(root, image_label, cv_capture))
if __name__ == '__main__':
cv_capture = cv2.VideoCapture()
cv_capture.open(0) # have to use whatever your camera id actually is
root = tk.Tk()
setattr(root, 'quit_flag', False)
def set_quit_flag():
root.quit_flag = True
root.protocol('WM_DELETE_WINDOW', set_quit_flag) # avoid errors on exit
image_label = tk.Label(master=root) # the video will go here
image_label.pack()
root.after(0, func=lambda: update_all(root, image_label, cv_capture))
root.mainloop()
ipcam.py
import cv2
import numpy as np
import PIL.Image
import PIL.ImageTk
import Tkinter as tk
import urllib
stream = urllib.urlopen("http://192.168.2.195:80/capture/scapture").read()
bytes_ = ''
def update_image(image_label):
global bytes_
bytes_ += stream.read(1024)
a = bytes_.find('\xff\xd8')
b = bytes_.find('\xff\xd9')
if (a != -1) and (b != -1):
jpg = bytes_[a:b+2]
bytes_ = bytes_[b+2:]
cv_image = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8),
cv2.CV_LOAD_IMAGE_COLOR)
cv_image = cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB)
pil_image = PIL.Image.fromarray(cv_image)
tk_image = PIL.ImageTk.PhotoImage(image=pil_image)
image_label.configure(image=tk_image)
image_label._image_cache = tk_image # avoid garbage collection
root.update()
def update_all(root, image_label):
if root.quit_flag:
print "coming if"
root.destroy() # this avoids the update event being in limbo
else:
print "coming else"
update_image(image_label)
root.after(1, func=lambda: update_all(root, image_label))
def timer(interval = 100):
root.after(0, func=lambda: update_all(root, image_label))
#.................................................................................................
root.after(interval, timer)
if __name__ == '__main__':
root = tk.Tk()
setattr(root, 'quit_flag', False)
def set_quit_flag():
root.quit_flag = True
root.protocol('WM_DELETE_WINDOW', set_quit_flag)
image_label = tk.Label(master=root) # label for the video frame
image_label.pack()
root.after(2, func=lambda: update_all(root, image_label))
# timer()
root.mainloop()
You need to designate one script as the main script, and in that one you can import the other. Here's an example of doing this using a simple subclass of the Frame widget:
The primary script (tkA.py):
from Tkinter import *
from tkB import Right # bring in the class Right from secondary script
class Left(Frame):
'''just a frame widget with a white background'''
def __init__(self, parent):
Frame.__init__(self, parent, width=200, height=200)
self.config(bg='white')
if __name__ == "__main__":
# if this script is run, make an instance of the left frame from here
# and right right frame from tkB
root = Tk()
Left(root).pack(side=LEFT) # instance of Left from this script
Right(root).pack(side=RIGHT) # instance of Right from secondary script
root.mainloop()
The secondary script (tkB.py):
from Tkinter import *
class Right(Frame):
'''just a frame widget with a black background'''
def __init__(self, parent):
Frame.__init__(self, parent, width=200, height=200)
self.config(bg='black')
if __name__ == "__main__":
# if this script is run, just do this:
root = Tk()
Right(root).pack()
root.mainloop()
Hope that helps.
First, you need to include it in the top. So in the top of ipcam.py write 'import usbcam' (without quotations).
Ok, I've got the GUI in tkinter working, and I'm trying to grab and image every 5 seconds and display it in a Label named Picturelabel.
from Tkinter import *
from PIL import ImageGrab
import cStringIO, base64, time, threading
class PictureThread(threading.Thread):
def run(self):
print "test"
box = (0,0,500,500) #x,x,width,height
MyImage = ImageGrab.grab(box)
fp = cStringIO.StringIO()
MyImage.save(fp, 'GIF')
MyPhotoImage = PhotoImage(data=base64.encodestring(fp.getvalue()))
time.sleep(5)
PictureThread().run() #If I get rid of this then it just display one image
return MyPhotoImage
MyVeryNewImage = PictureThread().run()
Picturelabel = Label(BalanceFrame, image=MyVeryNewImage)
Picturelabel.grid(row=3, column=2, columnspan=3)
Picturelabel.image = MyVeryNewImage
window.mainloop()
Firstly how can I clean up this code, as starting a thread inside another thread can't be good practice.
Also when I run this it prints "test" in the console, but it does not bring up the GUI.
If I comment out the commented text (PictureThread().run() where I'm creating yet another thread inside it.) then it displays the first image, but not any more.
You should call start() instead of run(). From the Documentation:
Once a thread object is created, its
activity must be started by calling
the thread’s start() method. This
invokes the run() method in a separate
thread of control.
I see you're invoking a new thread inside your run() method. This will cause you to spawn infinite threads!
EDIT: I'm not sure if this works:
from Tkinter import *
from PIL import ImageGrab
import cStringIO, base64, time, threading
Picturelabel = Label(BalanceFrame)
Picturelabel.grid(row=3, column=2, columnspan=3)
class PictureThread(threading.Thread):
def run(self):
print "test"
box = (0,0,500,500) #x,x,width,height
fp = cStringIO.StringIO()
while(1):
MyImage = ImageGrab.grab(box)
MyImage.save(fp, 'GIF')
self.image = PhotoImage(data=base64.encodestring(fp.getvalue()))
Picturelabel.image = self.image
fp.reset() # reset the fp position to the start
fp.truncate() # and truncate the file so we don't get garbage
time.sleep(5)
PictureThread().start()
window.mainloop()
The problem is that you return the new image from the PictureThread().run() in the method, but you never save it.
How about:
from Tkinter import *
from PIL import ImageGrab
import cStringIO, base64, time, threading
box = (0,0,500,500) #x,x,width,height
MyImage = ImageGrab.grab(box)
fp = cStringIO.StringIO()
MyImage.save(fp, 'GIF')
MyPhotoImage = PhotoImage(data=base64.encodestring(fp.getvalue()))
Picturelabel = Label(BalanceFrame, image=MyPhotoImage)
Picturelabel.grid(row=3, column=2, columnspan=3)
class PictureThread(threading.Thread):
def run(self):
while True:
box = (0,0,500,500) #x,x,width,height
MyImage = ImageGrab.grab(box)
fp = cStringIO.StringIO()
MyImage.save(fp, 'GIF')
MyPhotoImage = PhotoImage(data=base64.encodestring(fp.getvalue()))
time.sleep(5)
Picturelabel.image = MyPhotoImage
PictureThread().start()
window.mainloop()