I am creating a canvas that overlays everything else on the screen using a widget without a window that is lifted with the topmost attribute.
However, I would like a transparent background.
Here is what I've got:
import Tkinter
vsize = vw, vh = 600, 350
w = Tkinter.Canvas(width=vw, height=vh, highlightthickness=0)
w.master.overrideredirect(True)
w.master.geometry("+0+0")
w.master.lift()
w.master.wm_attributes("-topmost", True)
w.master.wm_attributes("-disabled", True)
w.create_rectangle(0, 0, vw, vh, fill="black")
w.pack()
w.mainloop()
I tried adding the following attribute to the canvas and adding stipple="gray25" to the rectangle:
w.master.wm_attributes("-transparentcolor", "gray")
This for some reason didn't make the gray transparent, and even if this did work it would look awful using stipple.
Does anyone have any ideas on how to achieve this?
EDIT:
I tried configuring the background as black, and giving it some transparency, but I would only like the background to be black so I can have non-transparent items inside of the canvas.
w.configure(background="black")
w.master.wm_attributes("-alpha", 0.5)
There is currently no way to achieve this in Tkinter I believe.
If you are looking for a way to overlay things on your screen, why not use individual windows placed on the screen? Else, I would suggest looking at other solutions outside of Tkinter.
Related
python 3.10, 3.11, windows 10
short:
transparency is affecting title bar when it shouldn't, simple code with example below, move the window to the middle of the screen, maximise and restore to reproduce the behaviour
long:
I know it looks so simple, but please bear with me. This thing is driving me nuts. I'm not sure what I could be doing wrong, it looks like a bug, maybe?
Maximising (or restoring to normal state after maximise) breaks the title bar. Title bar is not registering mouse clicks, I can't close the window or resize it because click goes through. It acts as it if it was transparent. In the main app I am using another hidden window with -alpha to grab mouse events on transparent canvas. Both windows are bound together and act as one. Having second window behind this one also doesn't help. Both windows are affected and unclickable (well, -alpha part of the window is clickable, but not the title bar).
I made a very short code to reproduce this behaviour. You can try it with frame instead of a canvas, or a button. Result is the same. Curious thing I have also discovered when using #000000 for colour - it breaks the window in even weirder way. Not sure what to make of it. #000001 works the same as yellow (or blue, red, etc.).
example:
from tkinter import Canvas, Tk
root = Tk()
root.attributes('-transparentcolor', 'yellow', '-topmost', True)
root.geometry("600x600")
x = Canvas(root, bg='yellow')
x.pack(expand=1, fill='both')
root.mainloop()
If I try different width and height values, sometimes I can grab only half of the title bar or close the window, but not minimise. It seems to depend on where the window is on the screen and the size of the object. Feels like the transparent part of the object is extending through the title bar making it unresponsive. I tried separating the title bar from the rest of the window with frames or shapes but it doesn't help (sometimes it works, but is dependent on size and location of the window - you may get lucky and not notice the behaviour)
x = Canvas(root, bg='yellow', height=600, width=600)
x.pack()
The best solution I've come up with so far:
def refresh(self):
self.state('iconic')
if self.state() == 'iconic':
self.state('normal')
self.focus_force() # needed for Entry widget
This function assigned to the button which is minimising the window to the taskbar and then returning it to normal state immediately. Obviously this is far from elegant, because the user have to perform an unnecessary action. I could use overrideredirect and hopefully recreate resize and close functionality of the window but it seems like an overkill for rather simple app.
Not sure what else to say. It's late, bye
edit:
trying this now and it somehow works, but sometimes the window blinks uncomfortably.
from tkinter import Canvas, Tk
class Window(Tk):
def __init__(self):
super().__init__()
self.bind("<Map>", self.refresh)
self.canv = Canvas(self)
self.canv.pack(expand=1, fill='both')
def refresh(self, event):
if self.state() == 'normal':
self.attributes('-transparentcolor', 'yellow', '-topmost', True)
self.canv.configure(bg='yellow')
elif self.state() == 'zoomed':
self.attributes('-transparentcolor', 'blue', '-topmost', True)
self.canv.configure(bg='blue')
if __name__ == '__main__':
w = Window()
w.mainloop()
I'm trying to display an image on the screen, without any window/application popping up/containing it. I'm pretty close with TKinter, but the method for removing the background color of the canvas is hacky and has some undesired effects.
import tkinter as tk
import ctypes
user32 = ctypes.windll.user32
screen_size = user32.GetSystemMetrics(0), user32.GetSystemMetrics(1)
root = tk.Tk()
root.overrideredirect(True)
root.config(bg="blue", bd=0, highlightthickness=0)
root.attributes("-transparentcolor", "#FEFCFD")
root.attributes("-topmost", True)
tk_img = tk.PhotoImage(file="image.png")
canvas = tk.Canvas(root, bg="#FEFCFD", bd=0, highlightthickness=0, width=screen_size[0], height=screen_size[1])
canvas.pack()
img = canvas.create_image(0, 0, image=tk_img, anchor="nw")
root.mainloop()
The -transparentcolor flag mostly removes the background, but if an image has any partially transparent pixels it will tint them. Plus, if that color exists in the image, it will be removed; that choice of color was in hopes of minimizing exact matches in an image while also being mostly white, to hopefully have the least noticeable affect on the images. Here's an image of what it looks like currently; very close to what I want, but you can see some missing pixels in the white areas of the dice, and they all seem to have a white border around them due to their edges being partially transparent. This is what the image should look like.
I've also tried to achieve this effect using wxPython, but I can't remove the background of the window, leading to transparent images always being backed by some color. I used this answer; I've modified it slightly but nothing I've done has improved it.
So, is there a way to draw an image on the screen without any background at all with Python?
Thanks to the suggestion from Kartikeya, I was able to solve my own question.
Using PyQt5, this code will display an image with transparency and no border or background at all
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QMainWindow, QApplication, QLabel
app = QApplication(sys.argv)
window = QMainWindow()
window.setAttribute(Qt.WA_TranslucentBackground, True)
window.setAttribute(Qt.WA_NoSystemBackground, True)
window.setWindowFlags(Qt.FramelessWindowHint)
label = QLabel(window)
pixmap = QPixmap('image.png')
label.setPixmap(pixmap)
label.setGeometry(0, 0, pixmap.width(), pixmap.height())
window.label = label
window.resize(pixmap.width(),pixmap.height())
window.show()
sys.exit(app.exec_())
Once I was looking for PyQt5, I found this question and only needed to modify the code slightly. Here is what it looks like now.
So, is there a way to draw an image on the screen without any
background at all with Python?
Using Tkinter, for this image, no, you cannot achieve the desired result. (You can look for other modules like 'PyQT5', 'Kivy', 'wxPython', or 'turtle' maybe.)
See, transparentcolor Specifies the transparent color index of the toplevel.
If you want to do the best in Tkinter, here are some changes to your code:
root.attributes('-transparentcolor', '#d4d4e2')
root.attributes("-topmost", True)
tk_img = tk.PhotoImage(file="image.png")
canvas = tk.Canvas(root, bg="#d4d4e2", bd=0, highlightthickness=0, width=screen_size[0], height=screen_size[1])
So, this will display a window that contains a canvas with transparent background. Very close to what you wanted, you can see very less missing pixels in the white areas of the dice, but still, this isn't the solution.
but if an image has any partially transparent pixels it will tint them
Yes, it's true, if that color exists in the image, it will be removed, as that color is being used to mark what needs to be used as transparent color.
that choice of color was in hopes of minimizing exact matches in an
image while also being mostly white, to hopefully have the least
noticeable effect on the images.
For the edges/borders of this image to retain the partially 'white' transparent background, the choice of color needs to be some shade of 'white'. So, the color used to make it transparent is #d4d4e2 (For this image, there was only one place where this color pixel was used, so it goes unnoticable.) Still, the edges will have sharp corners and cuts.
I have a canvas with a background image in Tkinter. I want to add a frame with no background so that I can arrange elements in the window, but, still see the background behind these elements. When I use something similar to the code below, i.e., without specifying the bg color, I get a frame with a grey background. How do I turn it to no background at all?
import tkinter as tk
from PIL import Image,ImageTk
root=tk.Tk()
root.geometry("800x560")
bgImg=Image.open("data/bg.png")
bgImg=ImageTk.PhotoImage(bgImg)
canvas=tk.Canvas(root,width=800,height=560)
canvas.pack(expand = False, fill = "both")
canvas.create_image(0, 0, image=bgImg, anchor="nw")
frame=tk.Frame(canvas,width=50,height=50)
frame.place(relx=0.5,rely=0.5,anchor="center")
root.mainloop()
There may* be platform-dependent options, like this.
(* In Lubuntu 18.04 & python 3.6.9, they don't seem to work,
so I can't test them.)
One cross-platform(?) option, is to use Canvas.
Then draw images* on the canvas, instead of using widgets.
(*whose alpha channel, determines their transparent pixels)
This will draw foreground/background elements correctly,
& Canvas has enough functionality to control elements on it
(but will need work, if you want to emulate full widgets).
Canvas offers various create_xyz methods, where xyz:
arc, line, rectangle, bitmap, oval, text, image, polygon,
window (<- read 'widget')*.
These return an id, that represents the item in the canvas.
Items can also be associated in groups, represented by tags.
* When using widgets in Canvas, there are some restrictions:
https://tcl.tk/man/tcl8.6/TkCmd/canvas.htm#M163
"Note: due to restrictions in the ways that windows are managed,
it is not possible to draw other graphical items
(such as lines and images) on top of window items.
A window item always obscures any graphics that overlap it,
regardless of their order in the display list."
Items in a canvas, can be configured & have event-handlers:
itemconfig / itemcget
tag_bind / tag_unbind
These can use either an individual item-id, or* a group-tag.
(* despite the 'tag_' in the name, they also take item-ids)
Many other Canvas methods work with item-ids or group-tags
(e.g. move, delete,).
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, width=300, height=300)
canvas.pack()
# Note:
# If the images overlap exactly (same position & extent),
# the bottom one will never? get any events.
# Maybe events can be propagated, not sure right now.
fgImg = tk.PhotoImage(master=root, file='media/fg.png')
fgImgId = canvas.create_image(
80, 80, anchor=tk.NW, image=fgImg
)
canvas.tag_bind(
fgImgId,
'<ButtonRelease-1>',
lambda e: canvas.tag_raise(fgImgId)
)
bgImg = tk.PhotoImage(master=root, file='media/bg.png')
bgImgId = canvas.create_image(
0, 0, anchor=tk.NW, image=bgImg
)
canvas.tag_bind(
bgImgId,
'<ButtonRelease-1>',
lambda e: canvas.tag_raise(bgImgId)
)
root.mainloop()
There are more StackOverflow questions about this, e.g.:
transparent-background-in-a-tkinter-window
python-tkinter-label-background-transparent
configure-tkinter-ttk-widgets-with-transparent-backgrounds-ttk-frame-background
and more.
You might also want to look into other gui toolkits.
(e.g. wxpython*, pyqt, )
(*Here it says, that it has a SetTransparent command.)
I am making a chess program and I want to be able to drag the pieces. In order to do this, I put the image of the piece on a Canvas so it can be dragged (I can also use a Label if I want). However, when I drag the piece there is a white square that surrounds the image of the piece.
When I researched the problem, many people gave this solution:
drag_canvas = Canvas(self, height=80, width=80, bg="yellow")
root.wm_attributes("-transparentcolor", "yellow")
This caused the background to be transparent but it was not the chessboard that was visible, it was the program behind the GUI
.
Is there any way I can have the background be transparent and show the chessboard behind rather than the program behind the tkinter window?
Note: I do not mind using any other widget (e.g. a Label) but they must use modules that come default with Python (so no PIL) as this program needs to be used in an environment where I cannot download other modules.
Question: How to make a tkinter canvas background transparent?
The only possible config(... option, to set the background to nothing
c.config(bg='')
results with: _tkinter.TclError: unknown color name ""
To get this result:
you have to hold the chess board and figures within the same .Canvas(....
self.canvas = Canvas(self, width=500, height=200, bd=0, highlightthickness=0)
self.canvas.create_rectangle(245,50,345,150, fill='white')
self.image = tk.PhotoImage(file='chess.png')
self.image_id = self.canvas.create_image(50,50, image=self.image)
self.canvas.move(self.image_id, 245, 100)
Tested with Python: 3.5 - TkVersion: 8.6
A windows only solution is to use the pywin32 module that can be installed with:
pip install pywin32
With pywin32 you can alter the window exstyle and set the canvas to a layered window. A layered window can have a transparent colorkey and is done in the example below:
import tkinter as tk
import win32gui
import win32con
import win32api
root = tk.Tk()
root.configure(bg='yellow')
canvas = tk.Canvas(root,bg='#000000')#full black
hwnd = canvas.winfo_id()
colorkey = win32api.RGB(0,0,0) #full black in COLORREF structure
wnd_exstyle = win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE)
new_exstyle = wnd_exstyle | win32con.WS_EX_LAYERED
win32gui.SetWindowLong(hwnd,win32con.GWL_EXSTYLE,new_exstyle)
win32gui.SetLayeredWindowAttributes(hwnd,colorkey,255,win32con.LWA_COLORKEY)
canvas.create_rectangle(50,50,100,100,fill='blue')
canvas.pack()
Explaination:
First we need the handle of the window which is called hwnd and we can get it in tkinter by .winfo_id().
Next we get the actual extended window style by GetWindowLong and ask specific for extended style information with win32con.GWL_EXSTYLE.
After that we do a bitwise operation in hexadezimal to alter the style with wnd_exstyle | win32con.WS_EX_LAYERED the result is our new_style.
Now we can set the extended style to the window with SetWindowLong. Finally we have our LayeredWindow which has additional Attributes we can work with. A transparent ColorKey can be set with SetLayeredWindowAttributes while we just use LWA_COLORKEY the alpha parameter has no use to us.
Important note: After defining a transparent colorkey, everything in that canvas with that color will be transparent.
I have ran into a problem that i suspect has to do painting/drawing elements in cairo.
I have a borderless window in pygtk, but i draw two rectangles with cairo.a black rectangle, and a grey rectangle inside. When the window is resized, there seems to be parts of the inner rectangle doesn't get drawn/painted. I have included 3 screenshots to show this issue.
As you can see in the second and third picture, some pieces of the window do not get painted grey. One way to fix this, is to call pygtk's window's present() method..but this makes my program extrmely slow, as the height of the window changes with pretty much each keystroke. So i was wondering what alternatives i have to fix this.
below is the relevant cairo code i use
def expose(self, widget, e):
cr = widget.window.cairo_create()
# Draw the background
cr.set_operator(cairo.OPERATOR_SOURCE)
# Create black rectangle with 60% opacity (serves as border)
(width, height) = widget.get_size()
cr.set_source_rgba(0, 0, 0, 0.6)
cr.rectangle(0, 0, width, height)
cr.fill()
# Inside the black rectangle, put a lighter one (will hold widgets)
(width, height) = widget.get_size()
cr.set_source_rgb(205/255, 205/255, 193/255)
cr.rectangle(10, 10, width-20, height-20)
cr.fill()
return False
def screen_changed(self, widget, old_screen = None):
screen = widget.get_screen()
colormap = screen.get_rgba_colormap()
widget.set_colormap(colormap)
It's basically a GTK+ bug, I believe. When a window is resized, GTK+ doesn't always queue the entire window for repainting. As a workaround, you can call window.queue_draw() at the place where you cause the window to be resized.
Try using the following right after you create your cairo widget:
cr.set_source_rgb(0,0,0)
cr.paint()
This will ensure you are always having a clean canvas.