how to draw rectangle over Tkinter scale? - python

I have the following GUI as shown in the Figure below. Instead of the black box, I wanted to draw a transparent rectangle on top of all the scales covering values 1, 0, and -1 of the scale.
Is there a way to make the Tkinter canvas transparent? If it is not the correct way to do it, what are the alternatives that I could try? I shared the sample code I use. That can be used to reproduce this GUI.
from tkinter import *
import itertools
root = Tk()
root.geometry('840x420')
root.title('Test Window')
variables = {"var1", "var2", "var3", "var4"}
pair_list = list(itertools.combinations(list(variables), 2))
pair_result_dictionary = dict.fromkeys(pair_list)
my_canvas = Canvas()
my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
my_scrollbar = Scrollbar(root, orient=tk.VERTICAL, command=my_canvas.yview)
my_scrollbar.pack(side=RIGHT, fill=Y)
my_canvas.configure(yscrollcommand=my_scrollbar.set)
my_canvas.bind('<Configure>', lambda e: my_canvas.configure(scrollregion=my_canvas.bbox("all")))
second_frame = Frame(my_canvas)
my_canvas.create_window((0, 0), window=second_frame, anchor="nw")
i = 0
heading_label = Label(second_frame, text="Test Window", font=('Arial',16))
heading_label.grid(column=0, row=0, sticky=tk.NW, columnspan=2, padx=(52, 0), pady=(20, 10))
for pair in pair_list:
sample_scale = tk.Scale(second_frame, from_=9, to=-9, length=600, orient=tk.HORIZONTAL, font=('Arial', 15),
tickinterval=1,resolution=1)
label_left = tk.Label(second_frame, text=pair[0], font=('Arial', 15))
label_left.grid(column=0, row=2 + i, sticky=tk.W, padx=(52, 0), pady=5)
sample_scale.set(((sample_scale['from'] - sample_scale['to']) / 2) + sample_scale['to'])
sample_scale.grid(column=1, row=2 + i, sticky=tk.W, padx=(5, 0), pady=5)
label_right = tk.Label(second_frame, text=pair[1], font=('Arial', 15))
label_right.grid(column=2, row=2 + i, sticky=tk.W, padx=(5, 5), pady=5)
i = i + 100
rectangle_holder_canvas = tk.Canvas(second_frame, width=100, height=70, bd=0, background='#000000')
rectangle_holder_canvas.grid(column=1, row=2, sticky=tk.S, padx=(0, 0), pady=0)
rec = rectangle_holder_canvas.create_rectangle(3, 3, 100, 70, outline='blue', fill='')
rectangle_holder_canvas.tag_raise(rec, 'all')
root.mainloop()
If I use the following code lines, it makes the entire square transparent and will see what's underneath the main window which is not what I want sadly.
root.wm_attributes("-transparentcolor", 'grey')
rectangle_holder_canvas = tk.Canvas(second_frame, width=100, height=70, bd=0, bg="grey")
Appreciate your thoughts and time on how to achieve this.
I read that, "tag_raise" method does not affect canvas window items. To change a window item's stacking order, use a lower or lift method on the window.. So I tried to draw a rectangle on my_canvas by setting second_frame.lower() as shown below code. Then I only see the rectangle but not the scales or labels on the second frame.
my_canvas.create_window((0, 0), window=second_frame.lower(), anchor=CENTER)
rec = my_canvas.create_rectangle(50, 50, 200, 200, outline='blue', fill='blue')

Your question has no easy answer.
The organisation of canvas objects is divided in two groups.
Ordinary graphical objects like rectangle, polygon etc are stacked in the order
of their creation, with the first object in the background and the last object
in the foreground. The stacking order of such objects can be changed via
canvas.lift, canvas.lower, canvas.tag_raise and canvas.tag_lower.
The second group are the canvas window objects, these are created using a
fixed stacking order that ALLWAYS sit above graphical objects.
Therefore it is not possible to place ordinary graphical objects above window
objects.
This would seem to make your objective impossible, however there is (at least)
one solution.
The following code achieves your objective by using a Toplevel window that is
positioned above the canvas. This window uses wm_attributes("-transparentcolor", color)
and overrideredirect(1), it also sets wm_attributes("-topmost", True).
The main window and top window are then bound together via the "Configure"
binding so that moving main window will automatically move top window.
This creates the effect you are looking for, although it may not suit your needs.
I've used one of your Scale objects placed in a canvas window object to simulate
a fragment of your code.
import tkinter as tk
color = "red"
class transParent(tk.Tk):
def __init__(self):
super().__init__()
self.withdraw()
self.columnconfigure(0, weight = 1)
wide, high = 606, 106
self.geometry(f"{wide}x{high}+20+20")
self.canvas = tk.Canvas(
self, background = color, highlightthickness = 0, borderwidth = 0)
self.canvas.grid(sticky = tk.NSEW)
self.s_scale = tk.Scale(
self.canvas, from_ = 9, to = -9, length = 600,
orient = tk.HORIZONTAL, font = "Arial 15",
takefocus = 1, tickinterval = 1, resolution = 1)
self.s_scale.set(0)
self.s_scale.grid(
column = 0, row = 0, sticky = tk.W, padx = 5, pady = 5)
self.sample_window = self.canvas.create_window(
0, 0, window = self.s_scale, anchor = tk.NW)
self.deiconify()
self.wait_visibility()
# build transparency toplevel : width, height, borderwidth
w, h, bw = 100, 100, 4
self.top = tk.Toplevel(
self, padx = 0, pady = 0, background = color,
highlightthickness = 0, relief = "solid", borderwidth = bw)
self.X, self.Y = self.winfo_x, self.winfo_y
self.top.geometry(f"{w}x{h}+{self.X()}+{self.Y()}")
self.top.wm_attributes("-transparentcolor", color)
self.top.overrideredirect(1)
self.top.wm_attributes("-topmost", True)
# estimate top position > trial & error?
self.xx, self.yy = int(wide / 2 - w / 2.4), int(high / 3.4)
# primary bindings
self.bind("<Configure>", self.moveit)
self.top.event_add(
"<<HOOD>>", "<Button-1>", "<Button-2>", "<Button-3>")
self.top.bind("<<HOOD>>", self.focal)
self.focus_force()
def moveit(self, ev):
self.top.geometry(f"+{self.X()+self.xx}+{self.Y()+self.yy}")
self.top.lift()
def focal(self, ev):
self.s_scale.focus_set()
if __name__ == "__main__":
app = transParent()
app.mainloop()

Related

Can't properly set an image as canvas background in tkinter

I want my Tkinter window to have four buttons, each with its own background image, disposed as a 2x2 grid. I tried using frames but you can't set an image as frame background so I had to switch to Canvas. This is the code for one of the four blocks.
from tkinter import *
from PIL import ImageTk, Image
root = Tk()
root.geometry("500x200")
root.wm_title("KnowYourArt")
styles_bg = ImageTk.PhotoImage(Image.open("images/styles_bg.png"))
canvas_styles = Canvas(root)
bg = canvas_styles.create_image(100, 100, image=styles_bg)
width = 4
height = 2
Button(canvas_styles, text="Styles", width=width, height=height).pack(side=TOP)
Label(canvas_styles, text="Gain info about styles").pack(side=BOTTOM)
canvas_styles.grid(row=0, column=0)
This is the output I get:
In my intentions, the background image dimensions should cover both the button and the label, and the image's dimensions should be independent from the dimensions of the widgets. Instead, I obtain a canvas where the height of the BG image is the same as the height of the button, and the width is the same as the label's width. I tried many alternative possibilities, including setting a width and height for the canvas, and none of them properly worked. What am I doing wrong?
I managed to find a solution. Here is the code:
from tkinter import *
from PIL import ImageTk, Image
width = 4
height = 1
def add_canvas(frame, img, x, y, text):
c = Canvas(frame, width=300, height=300)
c.focus()
c.pack(fill=BOTH, expand=True)
bg = c.create_image(x, y, image=img)
btn = Button(frame, text="Go!", width=width, height=height)
c.create_text(150, 190, text=text, font=('Helvetica', 15), fill='white')
c.create_window(100, 200, anchor="nw", window=btn)
return c
root = Tk()
root.geometry("600x600")
root.wm_title("Title")
f_1 = Frame(root)
f_2 = Frame(root)
f_3 = Frame(root)
f_4 = Frame(root)
bg1 = ImageTk.PhotoImage(Image.open("images/im1.png"))
bg2 = ImageTk.PhotoImage(Image.open("images/im2.png"))
bg3 = ImageTk.PhotoImage(Image.open("images/im3.png"))
bg4 = ImageTk.PhotoImage(Image.open("images/im4.jpg"))
canvas_1 = add_canvas(f_1, bg1, 0, 0, "foo")
canvas_2 = add_canvas(f_2, bg2, 240, 240, "foo")
canvas_3 = add_canvas(f_3, bg3, 120, 120, "foo")
canvas_4 = add_canvas(f_4, bg4, 100, 100, "foo")
f_1.grid(row=0, column=0)
f_2.grid(row=0, column=1)
f_3.grid(row=1, column=0)
f_4.grid(row=1, column=1)
And here's the output

Python how to use .pack for an other monitor resolution?

Hi i can't solve this problem, i created a program in a 1920x1080 resolution monitor and when i run it on purpose on an other pc with 1366x768 resolution the position of the Labels is different, i picked the height that should be 768 in one case and 1080 in the other case with:
larghezza = window.winfo_screenheight()
and used in the pack placements:
bottone_reset.pack(side=BOTTOM,anchor=S,pady=(0,altezza*0.1))
bottone_premi.pack(side=BOTTOM,anchor=S,pady=(0,altezza*0.2))
etichetta_click.pack(side=BOTTOM,anchor=S,pady=(0,altezza*0.1))
etichetta_titolo.pack(side=BOTTOM,anchor=S,pady=(0,altezza*0.1))
Because in this way the formula changes according to the current monitor resolution but the Labels are still in a different position, how to handle this ? Thanks
So, the problem being that the location of the buttons and label changes based on window size. You're trying to modify the positioning manually based on the window size. While ingenious, it's obviously not working as you intend. There are, however, different options other than pack(). These are grid() and place().
(Links go to "geeksforgeeks" website, which is what I'm currently using as reference.)
'grid()' places elements in a grid using columns and rows, and is best used for elements that have the same dimensions (at least in the direction you're placing them, so if you want them all aligned on the X direction, they should have similar length to not look weird).
bottone_reset.grid(column = 0, row = 0, padx = 5, pady = 5)
bottone_premi.grid(column = 1, row = 0, padx = 5, pady = 5)
etichetta_click.grid(column = 2, row = 0, padx = 5, pady = 5)
etichetta_titolo.grid(column = 3, row = 0, padx = 5, pady = 5)
'place()' lets you determine the explicit location of something, which can be useful but in case of your choice of element placement would likely not help, since if the window shortens, place will not change location.
I would suggest creating a frame for the elements listed, then depending on what is most useful to you, use pack() or grid() to place the elements within that frame. In turn, place that frame where you want the buttons to end up.
frame.pack(side=BOTTOM, anchor = S)
bottone_reset = Button(frame,text = "reset")
bottone_premi = Button(frame, text = "premi")
etichetta_click = Label(frame, text = "click")
etichetta_titolo = Label(frame, text = "Title")
Ofc, your definition of the buttons and labels will look different, these are just placeholders.
The following code will always place the buttons in the bottom center of the window, regardless of what size that screen is (unless the total elements no longer fit). It's closest to what I could see you were trying to attempt. of course, you'll have to replace my manual input of window width and height with your screen size detection. I could not immediately get that to work with just tkinter, so I decided to change values manually.
from tkinter import *
window = Tk()
window.title("Pack Problem")
windowWidth = 1000
windowHeight = 768
windowSize = str(windowWidth) + "x" + str(windowHeight)
window.geometry(windowSize)
frame = Frame()
frame.pack(side=BOTTOM, anchor = S)
bottone_reset = Button(frame,text = "reset")
bottone_premi = Button(frame, text = "premi")
etichetta_click = Label(frame, text = "click")
etichetta_titolo = Label(frame, text = "Title")
bottone_reset.grid(column = 0, row = 0, padx = 5, pady = 5)
bottone_premi.grid(column = 1, row = 0, padx = 5, pady = 5)
etichetta_click.grid(column = 2, row = 0, padx = 5, pady = 5)
etichetta_titolo.grid(column = 3, row = 0, padx = 5, pady = 5)
window.mainloop()
The layout and makeup are obviously just placeholders, but this should function as a framework for what you want to do.
EDIT: With a better understanding of the problem, different approach. Leaving the original comment for those that could use it.
In order to change padding size based upon resolution, the fastest way I can think of is to bind a resize function to window configuration.
window.bind('<Configure>', function)
Sadly, attempts to make it a smooth adjustment have not been successful so far. I'll leave that up to you to attempt if you wish.
The solution I've gotten to work is this:
def padsize(e):
if e.height <= 1080 and e.height > 768:
bottone_reset.grid(column=0, row=0, padx=25, pady=25)
bottone_premi.grid(column=1, row=0, padx=25, pady=25)
etichetta_click.grid(column=2, row=0, padx=25, pady=25)
etichetta_titolo.grid(column=3, row=0, padx=25, pady=25)
#print("window height is larger than 768")
elif e.height <768 and e.height > 640:
bottone_reset.grid(column=0, row=0, padx=15, pady=15)
bottone_premi.grid(column=1, row=0, padx=15, pady=15)
etichetta_click.grid(column=2, row=0, padx=15, pady=15)
etichetta_titolo.grid(column=3, row=0, padx=15, pady=15)
#print("window height is larger than 768")
elif e.height <640 and e.height > 250:
bottone_reset.grid(column=0, row=0, padx=5, pady=5)
bottone_premi.grid(column=1, row=0, padx=5, pady=5)
etichetta_click.grid(column=2, row=0, padx=5, pady=5)
etichetta_titolo.grid(column=3, row=0, padx=5, pady=5)
#print("window height is smaller than 768")
window.bind('<Configure>', padsize)
As you can see, I made the pad size differences really drastic, in order to be able to see the change rather than needing a ruler to check. It should be far less noticeable at smaller size differences.

What causes this white border around tkinter label widgets?

I am creating a chess board and after creating Canvas widgets for each of the chess board pieces. I have put labels around the board however the show up with this weird white border than i have no idea how to remove.
def createGrid(self):
colour = True
self.grid_list = []
for rows in range(8):
label = Label(self.canvas, text='{}'.format(rows+1), bg='#727272', highlightthickness=0)
label.grid(row=rows, column=0)
row_list = []
self.grid_list.append(row_list)
colour = not colour
for columns in range(8):
label = Label(self.canvas, text='{}'.format(self.letters[columns]), bg='#727272',
fg='white', highlightthickness=0, height = 2)
label.grid(row=8, column=columns+1)
if colour:
grid_piece = Canvas(self.canvas, width=self.size / 8, height=self.size / 8, bg=self.colour1,
highlightthickness=0, highlightbackground='#727272')
grid_piece.grid(row=rows, column=columns+1, padx = 0, pady = 0)
else:
grid_piece = Canvas(self.canvas, width=self.size / 8, height=self.size / 8, bg=self.colour2,
highlightthickness = 0)
grid_piece.grid(row=rows, column=columns+1)
colour = not colour
row_list.append(grid_piece)
Board with weird borders
There isn't enough code in your question for others to run it, so I added my own and was able to reproduce the problem after a lot of trial and error. I think the border is being caused by not specify borderwidth=0, highlightthickness=0 everywhere it's needed, so I added it around the Frame subclass and the main Canvas it contains.
Here's the result:
And here's the code used:
from tkinter import *
class Foo(Frame):
def __init__(self, *args):
super(Foo, self).__init__(*args, bg='#727272', borderwidth=0,
highlightthickness=0)
self.pack()
self.canvas = Canvas(self, borderwidth=0, bg='#727272', highlightthickness=0)
self.canvas.pack()
self.letters = list('ABCDEFHI')
self.size = 768
self.colour1 = '#7C1900'
self.colour2 = '#FFDFC4'
def createGrid(self):
colour = True
self.grid_list = []
for rows in range(8):
label = Label(self.canvas, text='{}'.format(rows+1), bg='#727272',
highlightthickness=0)
label.grid(row=rows, column=0)
row_list = []
self.grid_list.append(row_list)
colour = not colour
for columns in range(8):
label = Label(self.canvas, text='{}'.format(self.letters[columns]),
bg='#727272', fg='white', highlightthickness=0, height=2)
label.grid(row=8, column=columns+1)
if colour:
grid_piece = Canvas(self.canvas, width=self.size / 8,
height=self.size / 8, bg=self.colour1,
highlightthickness=0,
highlightbackground='#727272')
grid_piece.grid(row=rows, column=columns+1, padx=0, pady=0)
else:
grid_piece = Canvas(self.canvas, width=self.size / 8,
height=self.size / 8, bg=self.colour2,
highlightthickness=0)
grid_piece.grid(row=rows, column=columns+1)
colour = not colour
row_list.append(grid_piece)
root = Tk()
foo = Foo()
foo.createGrid()
root.mainloop()

Scrollbar displaying but not scrolling the dynamically created combobox widgets

I have created a canvas, scrollbar on a label frame. Using a button, dynamically creating the combobox widgets on the canvas. But the scrollbar not scrolling through the dynamically created.
from tkinter import *
from tkinter import ttk
root = Tk()
root.geometry("1366x705+0+0")
ExtRole_Dest_LF = ttk.LabelFrame(root, text='ExternalRoles', width =600)
ExtRole_Dest_LF.place(relx=0.225, rely=0.113, relheight=0.376, relwidth=0.264)
canvas=Canvas(ExtRole_Dest_LF,bg='#FFFFFF', height = 110, width = 335, scrollregion=(0,0,500,800))
canvas.grid(column = 0, row = 0, sticky = 'news')
canvas.grid_propagate(0)
canvas.config(scrollregion=canvas.bbox("all"))
vbar=Scrollbar(ExtRole_Dest_LF,orient=VERTICAL, command=canvas.yview)
vbar.grid(row = 0, column = 1, sticky='ns')
canvas.configure(yscrollcommand=vbar.set)
global System_Dest_row
System_Dest_row = 1
def fn_SystemDest():
global System_Dest_row
System_Dest_col = 0
System_Dest_cb = ttk.Combobox(canvas, values=['a','s','d','g'], width=15)
System_Dest_cb.grid(row=System_Dest_row, column=System_Dest_col, padx=10, pady = 5)
deletebutton = Button(canvas, text="X")
deletebutton.grid(row=System_Dest_row, column=System_Dest_col + 1, padx=10, pady = 5)
System_Dest_row += 1
AddButton = Button(root, text = 'Add', command =fn_SystemDest )
AddButton.grid(column = 3,row = 3)
root.mainloop()
Can I make scrollbar to scroll through the dynamical combobox widgets
Can I grt solution in any otherway to scroll the combobox widgets on LabelFrame/Frame
You can't scroll things added to a canvas with grid. A canvas can only scroll items added to it with one of the create_ functions.

How to clear part of a tkinter Canvas and show something when submit is pressed?

I'm creating a simple madlib style game and I've come into a bit of a problem. I cannot get the canvas to clear and show the results.
The following code places an image as the background of a canvas. It then places labels and entry fields in 2 columns for all of the words to be inserted. There is a submit button at the bottom of the page. I can't figure out how to get it clear everything except the background image, so that it can display the story, with the users words inserted. If i place it in the callback(), it clears just the background and keeps everything else. I want the opposite.
from tkinter import *
from PIL import Image, ImageTk
canvas_width = 360
canvas_height = 525
file = r"C:\Users\kraak\Desktop\PyCharm Community Edition 2017.1.2\borderedpaper.GIF"
master = Tk()
canvas = Canvas(master, width=canvas_width, height=canvas_height)
old_img = PhotoImage(file=file)
new_img = old_img.subsample(3, 3)
canvas.create_image(-11, -10, anchor=NW, image=new_img)
canvas.create_window(0, 0, height=1, width=1, anchor=NW)
canvas.create_text(0, 0, text="Test")
e1 = Entry(canvas)
canvas.create_window(250, 60, window=e1, height=15, width=100)
label = Label(text="Enter an adjective.")
label.place(x=40, y=50)
e1.focus_set()
e2 = Entry(canvas)
canvas.create_window(250, 85, window=e2, height=15, width=100)
label = Label(text="Enter a nationality.")
label.place(x=40, y=75)
e2.focus_set()
def callback():
print("Pizza was invented by a " + (e1.get()) + " " + (e2.get()))
def answer():
button = Button(text="Submit.", command=callback)
button.place(x=150, y=460)
answer()
canvas.pack()
mainloop()
As Bryan Oakley suggested you can store the id's of the widgets you want to get rid of in a list to make it easier to destroy() them all in the callback() function. Here's showing the modification to your code that would do that—note the lines with a # ADDED comments.
from tkinter import *
from PIL import Image, ImageTk
canvas_width = 360
canvas_height = 525
file = r"C:\Users\kraak\Desktop\PyCharm Community Edition 2017.1.2\borderedpaper.GIF"
master = Tk()
canvas = Canvas(master, width=canvas_width, height=canvas_height)
canvas_entry_widgets = [] # ADDED
old_img = PhotoImage(file=file)
new_img = old_img.subsample(3, 3)
canvas.create_image(-11, -10, anchor=NW, image=new_img)
canvas.create_window(0, 0, height=1, width=1, anchor=NW)
canvas.create_text(0, 0, text="Test")
e1 = Entry(canvas)
canvas.create_window(250, 60, window=e1, height=15, width=100)
label = Label(text="Enter an adjective.")
label.place(x=40, y=50)
e1.focus_set()
canvas_entry_widgets.append(e1) # ADDED
e2 = Entry(canvas)
canvas.create_window(250, 85, window=e2, height=15, width=100)
label = Label(text="Enter a nationality.")
label.place(x=40, y=75)
e2.focus_set()
canvas_entry_widgets.append(e2) # ADDED
def callback():
print("Pizza was invented by a " + (e1.get()) + " " + (e2.get()))
# destroy the canvas entry widgets and clear the list # ADDED
while canvas_entry_widgets: # ADDED
widget = canvas_entry_widgets.pop() # ADDED
widget.destroy() # ADDED
def answer():
button = Button(text="Submit.", command=callback)
button.place(x=150, y=460)
answer()
canvas.pack()
mainloop()
Every widget has a destroy method which can be used to delete the widget. In your callback you can simply call this method for every widget:
def callback():
e1.destroy()
e2.destroy()
...
In your specific case, if you want to delete all the labels you will have to give them unique names. Or, to make this even easier, you can store all of your widgets and iterate over the list.

Categories