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.)
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 am working on a tkinter project, where I set the background image (not a solid color). I was using .grid() for my widgets, but then I realized I need to display a widget at the very bottom of the tkinter window. To continue using grid for this, I separated my widgets into two frames; 1 frame that contained most of the widgets and one that just contained the widget I want to be displayed at the bottom. Then, I packed my frames, one on the top and the other at the bottom.
However, after I did this, my background image went on top of my existing widgets and blocked them out, so they can't be seen anymore. What can I do to fix this?
Here is the method I used to display the background photo:
from tkinter import *
filename = PhotoImage(file="image.png")
background_label = Label(root, image=filename) # root is the Tk() object
background_label.place(x=0, y=0, relwidth=1, relheight=1)
# Other code here
mainloop()
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'm using a tkinter canvas and trying to make a chat box on the right side of my game. However, I found that when I do...
import turtle
import tkinter as tk
master = tk.Tk()
w = tk.Canvas(master,width=1155,height=600,cursor='cross_reverse', bg='#101010')
shift = 1.000
sc = turtle.TurtleScreen(w)
tu = turtle.RawTurtle(sc)
e = tk.Entry(master, bg = '#000', fg = '#03f', font = 'Courier', justify='right', insertbackground = '#101010',width='115')
lb = tk.Listbox(master,height=3)
#e.grid(row=3,column=3)
sc.bgcolor("#101010")
txt = tk.Text(master,state="disabled")
txt.pack()
lb.pack()
w.pack()
sc.tracer(100)
drawcontinents() #Draws stuff with turtle, works just fine
e.pack()
tk.mainloop()
... a few things go wrong.
1.Text and Entry do not seem to want to coexist. I seem to be only able to have one or the other. My plan was to use entry as a chat entry, and display messages in Text. My backup plan is to append messages to label.
2.Text, entry, and Label box take up the entire window in whatever rows they are in, which blocks out the rest of what I am trying to draw. In other words,it puts the text box in the center, with a big gray stripe from side to side across whatever I've drawn. Is there any way to just display the box, and put it to the right?
3.Whenever I try to use the grid system, my whole computer freezes and I have to restart. Is this because the program is taking up more space than I have available, or is this a known bug or problem with installation?
You cannot use both pack and grid at the same time for the same containing widget (ie: for all widgets inside the same frame, toplevel or root window).
What happens is this: grid lays out all the widgets, potentially changing the size of some widgets based on your options (ie: it may grow a widget to stick to the sides of the cell). pack then notices that some widgets changed size in the containing widget it thinks it is responsible for, so it redoes what it thinks is the proper layout. This may change the size of some widgets based on your options. grid then notices that some widgets it thinks it is responsible for change size so it redoes what it does, potentially changing the size of some widgets. pack notices and re-adjusts, grid notices and re-adjusts, pack notices, ... until the end of time.
The solution is simple: only use grid, or only use pack, for all widgets that have a common parent. In this case, all your widgets share the root window as their parent, so they all need to use grid, or they all need to use pack.
I just came across a strange behavior of Tkinter when debugging my program. If a Frame object is created before a Canvas object and later inserted into that Canvas, it can't be displayed. However if the creation order is inverted (firstly Canvas and then Frame), contents in the Frame is displayed correctly.
For example, the following code works well:
from Tkinter import *
app = Frame()
canvas = Canvas(app)
frame = Frame(app)
Label(frame, text = 'aaaa').pack()
Label(frame, text = 'bbbb').pack()
canvas.create_window(0, 0, anchor = NW, window = frame)
canvas.grid()
app.grid()
app.mainloop()
But if the initialization order is inverted, like:
frame = Frame(app)
canvas = Canvas(app)
you get nothing but a blank window.
Is this a intentionally designed behavior (If so, why?), or I just found a bug in Tkinter?
It is a feature. Widgets have a stacking order that defaults to the order that they were created. You can adjust this stacking order with the lift and lower methods.
For example, you can create the frame first and then the canvas, so that the canvas has a higher stacking order. As you observe, you don't see the frame because it is behind the canvas. To make it visible, you can lift it:
frame.lift(canvas)
Doing so will give the same visual effect as if you had created the canvas first.
This technique can be useful to hide and show widgets. For example, you can create a notebook-like widget by stacking several frames on top of each other, and then using lift to bring the one you want to be visible to the top of the order.