The function is copied from Tying to set non-interactable (click-through) overlay with TkInter
Not only is the window not click through, the png is also not transparent.
PNG is here: https://drive.google.com/file/d/1tlLl2hjPq38mc_c_PpMhkKDlP1HqvDY5/view
This is what the window looks like:
What am I missing?
from tkinter import*
import win32gui
from win32gui import GetForegroundWindow, ShowWindow, FindWindow, SetWindowLong, GetWindowLong, SetLayeredWindowAttributes
from win32con import SW_MINIMIZE, WS_EX_LAYERED, WS_EX_TRANSPARENT, GWL_EXSTYLE
def setClickthrough(hwnd):
try:
styles = GetWindowLong(hwnd, GWL_EXSTYLE)
styles |= WS_EX_LAYERED | WS_EX_TRANSPARENT
SetWindowLong(hwnd, GWL_EXSTYLE, styles)
SetLayeredWindowAttributes(hwnd, 0, 255, win32con.LWA_ALPHA)
except Exception as e:
print(e)
root = Tk()
root.geometry("100x100")
root.overrideredirect(1)
root.attributes('-topmost', 1)
pic = PhotoImage(file=r'on2.png')
root.wm_attributes("-transparentcolor", 'white')
boardbutton = Label(root, image=pic, bd=0,
bg='white')
boardbutton.pack()
setClickthrough(root.winfo_id())
root.mainloop()
I have took the code of the linked question and made it work. See code below:
from tkinter import *
from PIL import Image, ImageTk
import win32gui
import win32con
def setClickthrough(hwnd):
print("setting window properties")
try:
styles = win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE)
styles = win32con.WS_EX_LAYERED | win32con.WS_EX_TRANSPARENT
win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, styles)
win32gui.SetLayeredWindowAttributes(hwnd, 0, 255, win32con.LWA_ALPHA)
except Exception as e:
print(e)
# Dimensions
width = 1920 #self.winfo_screenwidth()
height = 1080 #self.winfo_screenheight()
root = Tk()
root.geometry('%dx%d' % (width, height))
root.title("Applepie")
root.attributes('-transparentcolor', 'white', '-topmost', 1)
root.config(bg='white')
root.attributes("-alpha", 0.25)
root.wm_attributes("-topmost", 1)
bg = Canvas(root, width=width, height=height, bg='white')
setClickthrough(bg.winfo_id())
frame = ImageTk.PhotoImage(file="example.png")
bg.create_image(1920/2, 1080/2, image=frame)
bg.pack()
root.mainloop()
The important difference between your try and the working example seems to be that there was in use the hwnd of a canvas instead of a window.
I'm not abel to do exactly what you wish for but I have provided some code that outputs me this and hopefully sadisfy your needs. The extra code just removes the decoration (overrideredirect(1)) and resizes the window to the img size, also it places it in the middle of the screen.:
from tkinter import *
from PIL import Image, ImageTk
import win32gui
import win32con
def setClickthrough(hwnd):
print("setting window properties")
try:
styles = win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE)
styles = win32con.WS_EX_LAYERED | win32con.WS_EX_TRANSPARENT
win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, styles)
win32gui.SetLayeredWindowAttributes(hwnd, 0, 255, win32con.LWA_ALPHA)
except Exception as e:
print(e)
def size_position_for_picture():
bbox = bg.bbox(img_id)
w,h = bbox[2]-bbox[0],bbox[3]-bbox[1]
x,y = sw/2-w/2,sh/2-h/2
root.geometry('%dx%d+%d+%d' % (w,h, x,y))
bg.configure(width=w,height=h)
root = Tk()
sw = root.winfo_screenwidth()
sh = root.winfo_screenheight()
root.overrideredirect(1)
root.attributes("-alpha", 0.75)
root.attributes('-transparentcolor', 'white', '-topmost', 1)
bg = Canvas(root,bg='white',highlightthickness=0)
root.config(bg='white')
setClickthrough(bg.winfo_id())
frame = ImageTk.PhotoImage(file="example.png")
img_id = bg.create_image(0,0, image=frame,anchor='nw')
bg.pack()
size_position_for_picture()
setClickthrough(bg.winfo_id())
root.mainloop()
I have a function doing this for an application of mine as well, the code looks very similar to yours with some minor tweaks. You can try it out:
def set_clickthrough(hwnd, root):
# Get window style and perform a 'bitwise or' operation to make the style layered and transparent, achieving
# the clickthrough property
l_ex_style = win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE)
l_ex_style |= win32con.WS_EX_TRANSPARENT | win32con.WS_EX_LAYERED
win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, l_ex_style)
# Set the window to be transparent and appear always on top
win32gui.SetLayeredWindowAttributes(hwnd, win32api.RGB(0, 0, 0), 190, win32con.LWA_ALPHA) # transparent
win32gui.SetWindowPos(hwnd, win32con.HWND_TOPMOST, root.winfo_x(), root.winfo_y(), 0, 0, 0)
And also, the following function to disable the clickthrough again
def disable_clickthrough(hwnd, root):
# Calling the function again sets the extended style of the window to zero, reverting to a standard window
win32api.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, 0)
# Remove the always on top property again, in case always on top was set to false in options
win32gui.SetWindowPos(hwnd, win32con.HWND_NOTOPMOST, root.winfo_x(), root.winfo_y(), 0, 0, 0)
You can get the window handle (hwnd) by calling win32gui.FindWindow(None, root.title()).
Related
I am doing a painting program where one can draw things, change the background color and save it as a file on the computer. Everything works except that changing the background color takes way too much time.
This is the code:
from tkinter import *
import tkinter.filedialog as tk
from tkinter import Menu
from tkinter.colorchooser import askcolor
from tkinter.filedialog import asksaveasfile,askopenfilename
import os
from PIL import Image as im
from PIL import ImageTk,ImageDraw,ImageColor
class P:
x=y=None
image=None
my_image=im.new("RGB",(600,600),(255,255,255))
draw=ImageDraw.Draw(my_image)
def __init__(self,window):
self.window = window
self.upper_frame = Frame(window)
self.upper_frame.grid(row=0,column=0, padx=10, pady=5,sticky="ew")
self.lower_frame = Frame(window)
self.lower_frame.grid(row=2, column=0, padx=10, pady=5,sticky="ew")
self.canvas= Canvas(self.lower_frame,width=700,height=530,bg="white")
self.canvas.grid()
self.bg = Button(self.upper_frame,text="Background",command= self.bgcolor) #change bg color
self.bg.grid(row=2,column=1,padx=(100,10))
self.upper_menu()
def bgcolor(self):
chosen_color = askcolor(color=self.canvas["bg"])[1]
self.canvas.configure(bg=chosen_color)
color_RGB = ImageColor.getcolor(chosen_color, "RGB")
img = self.my_image
for i in range(0,600):#pixels in width
for j in range(0,600): #height = 600 pix
data = img.getpixel((i,j)) #gives color of pixel
if (data[0]==255 and data[1]==255 and data[2]==255): #data represent RGB color
img.putpixel((i,j),color_RGB) #changes pixel color
def save_pic(self,event=None): #save image on comp.
my_file=asksaveasfile(mode="w",defaultextension=".png",filetypes=[("png files","*.png")],
initialdir="/home/b/",parent=window)
if my_file is not None:
path=os.path.abspath(my_file.name)
self.my_image.save(path)
def upper_menu(self):
self.menubar = Menu(window)
self.menu1 = Menu(self.menubar, tearoff=0)
self.menu1.add_command(label="Save pic", command=self.save_pic)
self.menu1.add_separator()
self.menu1.add_command(label="Exit", command=window.destroy)
self.menubar.add_cascade(label="Settings", menu=self.menu1)
self.menu2 = Menu(self.menubar, tearoff=0)
self.window.config(menu=self.menubar)
window = Tk()
window.geometry("720x590")
p = P(window)
window.mainloop()
I use the method bgcolor to change the background. How to make it work faster?
I suspect the problem is with calling putpixel 360,000 times. Instead, try creating the color data in the loop and then call putdata once after the loops have finished.
I'm not overly familiar with PIL, but this makes a huge difference when doing similar things with the tkinter PhotoImage class: doing one pixel at a time is slow, doing an array of pixels is fast.
I tried to build off of the solution here. My code is:
from tkinter import mainloop, Tk, Frame, Button, Label, Canvas, PhotoImage, NW
from tkinter import ttk
from tkinter import filedialog
import tkinter as tk
from PIL import Image, ImageTk
class my_class(tk.Tk):
def __init__(self):
super().__init__()
self.geometry=('1400x1400')
self.filename = ''
my_notebook = ttk.Notebook(self)
my_notebook.pack(pady=5)
self.selections = Frame(my_notebook, width = 1100, height = 700)
self.selections.pack(fill = "both", expand=1)
my_notebook.add(self.selections, text = "Selections")
Button(self.selections, text = "Select an Image", command = self.get_image).place(x=10,y=40)
self.image_frame = Frame(my_notebook, width = 1100, height = 700)
self.image_frame.pack(fill = "both", expand=1)
my_notebook.add(self.image_frame, text = "Image")
self.my_canvas = Canvas(self.image_frame, width=800, height=600, bg="white")
self.my_canvas.pack()
self.rgb_var = tk.StringVar(self.image_frame, '0 0 0')
self.rgb_label = tk.Label(self.image_frame, textvariable = self.rgb_var)
self.rgb_label.pack()
self.image_frame.bind('<Motion>', lambda e: self.get_rgb(e))
def get_image(self):
self.filename = filedialog.askopenfilename(initialdir="D:/Python", title="select a file", filetypes = (("png files","*.png"),("jpg files","*.jpg")))
self.img = Image.open(self.filename)
self.img_rgb = self.img.convert('RGB')
dim_x, dim_y = self.img_rgb.size
self.img_tk = ImageTk.PhotoImage(self.img_rgb.resize((dim_x, dim_y)))
self.my_canvas.create_image(dim_x // 2, dim_y // 2, image = self.img_tk)
def get_rgb(self, event):
x, y = event.x, event.y
try:
rgb = self.img_rgb.getpixel((x, y))
self.rgb_var.set(rgb)
except IndexError:
pass # ignore errors if cursor is outside the image
if __name__ == '__main__':
app = my_class()
app.geometry=('1200x900')
app.mainloop()
I can use the button to select an image. Then I click the (Image) tab and see the selected image on the canvas.
I expected the (rgb_var) displayed under the image to update as I move the mouse pointer across the image. Instead the numbers under the image only update when the mouse pointer is in the frame, but outside the canvas. Also the numbers displayed seem to be unrelated to pixels in the image. How can I display the RGB values of a pixel that is (under the mouse pointer) when the mouse pointer is over the image?
I am doing a people counter in raspberry pi. I want to display an one image if someone comes in, and another one if someone comes out. Right now i am using the code below (that i took from another question here xd) to change the image that tkinter is displaying. The problem with this is thay it only shows the picture cat.jpg for a second, and then it shows a black screen and nothing happends.
import sys
if sys.version_info[0] == 2: # the tkinter library changed it's name from Python 2 to 3.
import Tkinter
tkinter = Tkinter #I decided to use a library reference to avoid potential naming conflicts with people's programs.
else:
import tkinter
from PIL import Image, ImageTk
import time
def updateRoot(root,imagen):
pilImage = Image.open(imagen)
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.overrideredirect(1)
root.geometry("%dx%d+0+0" % (w, h))
root.focus_set()
root.bind("<Escape>", lambda e: (e.widget.withdraw(), e.widget.quit()))
canvas = tkinter.Canvas(root,width=w,height=h)
canvas.pack()
canvas.configure(background='black')
imgWidth, imgHeight = pilImage.size
if imgWidth > w or imgHeight > h:
ratio = min(w/imgWidth, h/imgHeight)
imgWidth = int(imgWidth*ratio)
imgHeight = int(imgHeight*ratio)
pilImage = pilImage.resize((imgWidth,imgHeight), Image.ANTIALIAS)
image = ImageTk.PhotoImage(pilImage)
imagesprite = canvas.create_image(w/2,h/2,image=image)
root.update()
root = tkinter.Tk()
updateRoot(root,"Cat.jpg")
time.timesleep(5)
updateRoot(root,"Dog.jpg")
Before this I used this code
import tkinter
from PIL import Image, ImageTk
from tkinter import ttk
def updateRoot(root,imagen):
image1 = Image.open(imagen)
image2 = ImageTk. PhotoImage(image1)
image_label = ttk. Label(root , image =image2)
image_label.place(x = 0 , y = 0)
root.update()
That works fine, but it's not full screen.
First you should do the followings outside updateRoot():
make root window fullscreen (you can simply use root.attributes('-fullscreen', 1))
bind the <Escape> key
create the canvas and create_image() (you can use Label to do the same thing)
Then just update the image inside updateRoot().
Also you should use after() instead of time.sleep().
Below is an example:
try:
import Tkinter as tkinter
except:
import tkinter
from PIL import Image, ImageTk
def updateRoot(imagen):
# resize the image to fill the whole screen
pilImage = Image.open(imagen)
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
image = ImageTk.PhotoImage(pilImage.resize((w,h)))
# update the image
canvas.itemconfig(imgbox, image=image)
# need to keep a reference of the image, otherwise it will be garbage collected
canvas.image = image
root = tkinter.Tk()
root.attributes('-fullscreen', 1)
root.bind('<Escape>', lambda _: root.destroy())
canvas = tkinter.Canvas(root, highlightthickness=0)
canvas.pack(fill=tkinter.BOTH, expand=1)
imgbox = canvas.create_image(0, 0, image=None, anchor='nw')
# show the first image
updateRoot('Cat.jpg')
# change the image 5 seconds later
root.after(5000, updateRoot, 'Dog.jpg')
root.mainloop()
Fixed your Black issue using labels, try this. i think you still need to resize image to fit screen
import sys
if sys.version_info[0] == 2: # the tkinter library changed it's name from Python 2 to 3.
import Tkinter
tkinter = Tkinter #I decided to use a library reference to avoid potential naming conflicts with people's programs.
else:
import tkinter
from PIL import Image, ImageTk
import time
from tkinter import *
import PIL.Image
def updateRoot(root,imagen):
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.overrideredirect(1)
root.geometry("%dx%d+0+0" % (w, h))
root.focus_set()
root.bind("<Escape>", lambda e: (e.widget.withdraw(), e.widget.quit()))
img = PIL.Image.open(imagen)
root.tkimage = ImageTk.PhotoImage(img)
Label(root,image = root.tkimage).place(x=0, y=0, relwidth=1, relheight=1)
root.update()
root = tkinter.Tk()
updateRoot(root,"Cat.jpg")
time.sleep(3)
updateRoot(root,"Dog.jpg")
root.mainloop()
I've gone through several other posts regarding similar issues and all seem to point to this code. Since, I'm working on creating a desktop overlay, I setup a short program to create a top window and hopefully isolate the issue but, despite my efforts I can't figure out why this seemingly widely accepted solution is failing for me or what I could be missing.
I've verified that the handle being retrieved is referencing the correct window, trimmed away unnecessary functions, and explored other variations of setting window styles through personal trial and error and mimicking some examples found through programcreek.com.
from tkinter import *
from PIL import Image, ImageTk
from win32gui import GetForegroundWindow, ShowWindow, FindWindow,
SetWindowLong, GetWindowLong
from win32con import SW_MINIMIZE, WS_EX_LAYERED, WS_EX_TRANSPARENT, GWL_EXSTYLE
def setClickthrough(root, window="Applepie"):
hwnd = FindWindow(None, window)
styles = GetWindowLong(hwnd, GWL_EXSTYLE)
styles |= WS_EX_LAYERED | WS_EX_TRANSPARENT
print(SetWindowLong(hwnd, GWL_EXSTYLE, styles))
# Dimensions
width = 1920 #self.winfo_screenwidth()
height = 1080 #self.winfo_screenheight()
root = Tk()
root.geometry('%dx%d' % (width, height))
root.title("Applepie")
root.attributes('-transparentcolor', 'white', '-topmost', 1)
root.config(bg='white')
root.attributes("-alpha", 0.25)
root.wm_attributes("-topmost", 1)
root.bg = Canvas(root, width=width, height=height, bg='white')
setClickthrough(root)
frame = ImageTk.PhotoImage(file="Resources/Test/Test-0000.gif")
root.bg.create_image(1920/2, 1080/2, image=frame)
root.bg.pack()
root.mainloop()
The TkInter window was successfully made transparent and clickable-through using the solution provided by acw1668 using:
root.attributes('-transparentcolor', 'white', '-topmost', 1)
root.config(bg='white')
root.bg = Canvas(root, width=width, height=height, bg='white')
Problem persists with creating an image in the canvas. Need to be able to have additional images be clickable through as well:
frame = ImageTk.PhotoImage(file="Resources/Test/Test-0000.gif")
root.bg.create_image(1920/2, 1080/2, image=frame)
Turns out the handle was not being captured properly using FindWindow and using alternatives such as root.frame() or root.winfo_id() did not match the handle of the window for some reason. By passing in the winfo_id() of the Canvas, I was able to get the below code to work:
self.root.config(bg='#000000')
self.root.wm_attributes("-topmost", 1)
self.root.attributes('-transparentcolor', '#000000', '-topmost', 1)
print("Configuring bg")
self.bg = Canvas(self.root, highlightthickness=0)
self.setClickthrough(self.bg.winfo_id())
Calling:
def setClickthrough(self, hwnd):
print("setting window properties")
try:
styles = GetWindowLong(hwnd, GWL_EXSTYLE)
styles = WS_EX_LAYERED | WS_EX_TRANSPARENT
SetWindowLong(hwnd, GWL_EXSTYLE, styles)
SetLayeredWindowAttributes(hwnd, 0, 255, LWA_ALPHA)
except Exception as e:
print(e)
I am currently controlling a game with python by sending mouse and keystroke commands. What I am looking to do is have a transparent Tkinter window lay overtop of the game to provide some information such as mouse location and pixel color.
I am familiar with changing the window's alpha attribute to make it transparent but have no idea how to always keep that window in front and have mouse clicks pass through it.
My current method of controlling the game involves taking screenshots in certain locations and analyzing the color content. I will also need some way to do this without the Tkinter window interfering.
Pyscreenshot is used for screenshots
win32api is used for clicking
Thank you,
Alec
you can use the SetWindowLong function of win32gui module. If you want a transparent click through window you have to apply GWL_EXSTYLE's ony our window. Therefore you need the windowhandle of your Window.
hwnd = win32gui.FindWindow(None, "Your window title") # Getting window handle
# hwnd = root.winfo_id() getting hwnd with Tkinter windows
# hwnd = root.GetHandle() getting hwnd with wx windows
lExStyle = win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE)
lExStyle |= win32con.WS_EX_TRANSPARENT | win32con.WS_EX_LAYERED
win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE , lExStyle )
If you want to change the transparency of your window via winapi use SetLayeredWindowAttributes.
EDIT: Examplecode for an overlay always-on-top transparent window, which pass through clicks. It gets the current desktopimage and creates a transparent overlay, so you can enjoy your desktop background image.
from win32api import GetSystemMetrics
import win32con
import win32gui
import wx
def scale_bitmap(bitmap, width, height):
image = wx.ImageFromBitmap(bitmap)
image = image.Scale(width, height, wx.IMAGE_QUALITY_HIGH)
result = wx.BitmapFromImage(image)
return result
app = wx.App()
trans = 50
# create a window/frame, no parent, -1 is default ID
# change the size of the frame to fit the backgound images
frame1 = wx.Frame(None, -1, "KEA", style=wx.CLIP_CHILDREN | wx.STAY_ON_TOP)
# create the class instance
frame1.ShowFullScreen(True)
image_file = win32gui.SystemParametersInfo(win32con.SPI_GETDESKWALLPAPER,0,0)
bmp1 = wx.Image(image_file, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
bmp1 = scale_bitmap(bmp1,GetSystemMetrics(1)*1.5,GetSystemMetrics(1))
bitmap1 = wx.StaticBitmap(frame1, -1, bmp1, (-100, 0))
hwnd = frame1.GetHandle()
extendedStyleSettings = win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE)
win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, extendedStyleSettings | win32con.WS_EX_LAYERED | win32con.WS_EX_TRANSPARENT)
win32gui.SetLayeredWindowAttributes(hwnd, 0, 255, win32con.LWA_ALPHA)
frame1.SetTransparent(trans)
def onKeyDown(e):
global trans
key = e.GetKeyCode()
if key==wx.WXK_UP:
print trans
trans+=10
if trans >255:
trans = 255
elif key==wx.WXK_DOWN:
print trans
trans-=10
if trans < 0:
trans = 0
try:
win32gui.SetLayeredWindowAttributes(hwnd, 0, trans, win32con.LWA_ALPHA)
except:
pass
frame1.Bind(wx.EVT_KEY_DOWN, onKeyDown)
app.MainLoop()
You can dynamically change the transparency with the arrow keys Up/Down.
Notice, the windowframe is created with 'wx', but should work with tkinter also.
Feel free to use the code as you like.