events on widgets that are drawn on a canvas? - python

I have placed some widgets on a canvas (ultimately to enable scrolling). Here is the simplified code:
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.geometry('400x400')
canvas = tk.Canvas(root, background = 'white')
canvas.pack(expand = True, fill = 'both')
canvas.create_window((10,30), window = ttk.Label(root, text = 'A label'))
canvas.bind('<MouseWheel>', lambda event: print(event))
root.mainloop()
The mousewheel event does work on the canvas but does not work on the area where with the widget. Is there a way around it?
One solution I thought might work was to put the canvas in a container and run an event on that:
import tkinter as tk
from tkinter import ttk
class ScrollContainer(ttk.Frame):
def __init__(self, parent):
super().__init__(master = parent)
canvas = tk.Canvas(self, background = 'white')
canvas.pack(expand = True, fill = 'both', side = 'left')
canvas.create_window((10,30), window = ttk.Label(root, text = 'A label'))
self.bind('<MouseWheel>', lambda event: print(event))
self.pack(expand = True, fill = 'both')
root = tk.Tk()
root.geometry('400x400')
ScrollContainer(root)
root.mainloop()
But that one doesn't trigger an event at all. Can someone help with this problem? I just want to be able to trigger an event when the mouse is over the frame or canvas regardless of the children (or canvas windows). It does work to create an event on the root itself but I need this to be more focused.

The second solution does not work because the canvas is above the Frame so that the event happens in the canvas and not the frame and there is no trigger defined for canvas event. The solution may be using bind_all to trigger events to all canvas items
canvas.bind_all('<MouseWheel>', lambda event: print(event))
Only be careful that the x and y coordinates captured from the event will be the item coordinates and not the canvas ones

Related

TKinter button over transparent background

I am trying to understand how to apply a button to a transparent background while keeping its shape. When I generate the code below, there is a gray background around the border that appears, and it also looks like it loses its shape.
Colors Used
Sidebar: #2E3A4B at 53%
Button: #2C2F33 at 100%
from tkinter import *
def btn_clicked():
""" Prints to console a message every time the button is clicked """
print("Button Clicked")
root = Tk()
# Configures the frame, and sets up the canvas
root.geometry("1440x1024")
root.configure(bg="#ffffff")
canvas = Canvas(root, bg="#ffffff", height=1024, width=1440, bd=0, highlightthickness=0, relief="ridge")
canvas.place(x=0, y=0)
background_img = PhotoImage(file=f"background.png")
background = canvas.create_image(719.5, 512.5, image=background_img)
img0 = PhotoImage(file=f"img0.png")
alarm_button = Button(image=img0, borderwidth=0, highlightthickness=0, command=btn_clicked, relief="flat")
alarm_button.place(x=9, y=119, width=90, height=90)
root.resizable(False, False)
root.mainloop()
Required Images
How it looks:
How it should look:
Good news! I was able to get that answer to a related question you found to work. To make it easier to reuse I've converted it into a formal class and added a couple of methods. In addition I made it flash the image off and back on when it's clicked to give the user some visual feedback like "real" tkinter Buttons do.
Note that it responds to mouse button <ButtonRelease-1> events. That's a better choice in most cases than the <Button-1> event because if the user accidentally presses the button, they can move the mouse off the widget image to avoid setting off the event.
Turns out that using the PIL module was unnecessary. Here's the code:
import tkinter as tk # PEP 8 recommends avoiding `import *`.
class CanvasButton:
""" Create leftmost mouse button clickable canvas image object.
The x, y coordinates are relative to the top-left corner of the canvas.
"""
flash_delay = 100 # Milliseconds.
def __init__(self, canvas, x, y, image_path, command, state=tk.NORMAL):
self.canvas = canvas
self.btn_image = tk.PhotoImage(file=image_path)
self.canvas_btn_img_obj = canvas.create_image(x, y, anchor='nw', state=state,
image=self.btn_image)
canvas.tag_bind(self.canvas_btn_img_obj, "<ButtonRelease-1>",
lambda event: (self.flash(), command()))
def flash(self):
self.set_state(tk.HIDDEN)
self.canvas.after(self.flash_delay, self.set_state, tk.NORMAL)
def set_state(self, state):
""" Change canvas button image's state.
Normally, image objects are created in state tk.NORMAL. Use value
tk.DISABLED to make it unresponsive to the mouse, or use tk.HIDDEN to
make it invisible.
"""
self.canvas.itemconfigure(self.canvas_btn_img_obj, state=state)
BGR_IMG_PATH = "sunset_background.png"
BUTTON_IMG_PATH = "alarm_button.png"
def btn_clicked():
""" Prints to console a message every time the button is clicked """
print("Button Clicked")
root = tk.Tk()
background_img = tk.PhotoImage(file=BGR_IMG_PATH)
bgr_width, bgr_height = background_img.width(), background_img.height()
root.geometry(f'{bgr_width}x{bgr_height}')
root.title("TKinter button over transparent background")
root.configure(bg="white")
canvas = tk.Canvas(root, bg="white", height=bgr_height, width=bgr_width, bd=0,
highlightthickness=0, relief="ridge")
canvas.place(x=0, y=0)
background = canvas.create_image(0, 0, anchor='nw', image=background_img)
canvas_btn1 = CanvasButton(canvas, 0, 128, BUTTON_IMG_PATH, btn_clicked)
canvas_btn2 = CanvasButton(canvas, 0, 256, BUTTON_IMG_PATH, btn_clicked)
root.resizable(False, False)
root.mainloop()
Screenshot of the result:
Close up:

Not Displaying the Keyboard Inputs as Labels in the Window in Python

How can I display the letters entered from the keyboard as labels in the gui window?
I can only view it on the console right now.
from tkinter import*
from tkinter import ttk
window = Tk()
def letter(event):
a=str(print (repr(event.char)," key pressed."))
label=Label(window,text=a)
label.place(x=15,y=15)
def clicked(event):
frame.focus_set()
print(event.x,event.y ,"coordinate clicked.")
frame =Frame(window, width=500, height=500)
frame.bind("<Key>",letter)
frame.bind("<Button-1>", clicked)
frame.pack()
window.mainloop()
I see a few problems with your code.
What do you think str(print (repr(event.char)," key pressed.")) returns? It returns a None value which is then put into the label and the label is showing "None".
In the function letter you are creating a Label every time the function is called, which means it is just overlapping the label on a label created before. So create one label outside the function and then update that label into that function.
Complete code:
from tkinter import*
from tkinter import ttk
window = Tk()
def letter(event):
a = "'%s' key pressed" %event.char
print(a)
# Update the text of label.
label['text'] = a
def clicked(event):
frame.focus_set()
print(event.x,event.y ,"coordinate clicked.")
frame =Frame(window, width=500, height=500)
frame.bind("<Key>",letter)
frame.bind("<Button-1>", clicked)
frame.pack()
label=Label(window, text='Key')
label.place(x=15, y=15)
window.mainloop()

Can you set a tkinter background image without adding a new canvas element?

I have a Python tkinter program simplified to
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, height=200, width=200, bg="salmon")
canvas.pack(fill=tk.BOTH, expand=tk.YES)
def click(event):
print(event.x)
print(event.y)
def release(event):
print(event.x)
print(event.y)
canvas.bind("<Button-1>", click)
canvas.bind("<ButtonRelease-1>", release)
root.mainloop()
with a Canvas as the main element. Canvas has click/release events bound to it (e.g. returning event.x and event.y). I want to add a background image to the canvas in this manner:
canvas = tk.Canvas(root, bg='/path/to/image.png')
I have managed to set a background image by creating an image on the canvas using canvas.create_image method, as explained in Adding a background image in python. However, this broke my program as event.x and event.y return position of the background image.
I am looking for a solution that would force me to change the least of the existing code.
The only way to create a background image on a canvas is to create an image object on the canvas. Doing so will not affect the coordinates returned by the bound functions in your example.
We need to use PhotoImage to load the image for use then we use create_image to set that image to the canvas.
Give this a shot:
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, height=200, width=200, bg="salmon")
canvas.pack(fill=tk.BOTH, expand=tk.YES)
def click(event):
print(event.x)
print(event.y)
def release(event):
print(event.x)
print(event.y)
canvas.bind("<Button-1>", click)
canvas.bind("<ButtonRelease-1>", release)
my_image = tk.PhotoImage(file='/path/to/image.png')
canvas.create_image(10, 10, image=my_image, anchor='nw')
root.mainloop()

How to display canvas coordinates when hovering cursor over canvas?

When I hover over the canvas I want some labels on top of the canvas to display x,y coordinates which stay the same if I keep my cursor still but change when I move it. How would I do this?
You can use a callback method and bind it to a Motion event.
import tkinter
root = tkinter.Tk()
canvas = tkinter.Canvas(root)
canvas.pack()
def moved(event):
canvas.itemconfigure(tag, text="(%r, %r)" % (event.x, event.y))
canvas.bind("<Motion>", moved)
tag = canvas.create_text(10, 10, text="", anchor="nw")
root.mainloop()
Also use <Enter> event. So when you switch between windows (<Alt>+<Tab> hotkey), your cursor will show the correct coordinates.
For example, you put your cursor on the canvas and <Motion> event will track it, but when you press <Alt>+<Tab> and switch to another window, then move your cursor a bit and <Alt>+<Tab> on your canvas again -- coordinates of the your cursor will be wrong, because <Motion> event doesn't track switches between windows. To fix it use <Enter> event.
import tkinter
root = tkinter.Tk()
canvas = tkinter.Canvas(root)
canvas.pack()
def get_coordinates(event):
canvas.itemconfigure(tag, text='({x}, {y})'.format(x=event.x, y=event.y))
canvas.bind('<Motion>', get_coordinates)
canvas.bind('<Enter>', get_coordinates) # handle <Alt>+<Tab> switches between windows
tag = canvas.create_text(10, 10, text='', anchor='nw')
root.mainloop()

Python Tkinter Scrollbar for entire window

Is there a way to add a scrollbar to my entire window without putting everything into a frame? I've set everything up with .grid, and I don't like the idea of wrapping a frame around everything.
root = Tk()
root.maxsize(900,600)
circus()#calls the function to set up everything
root.mainloop()
How to add scrollbars to full window in tkinter ?
here is the answer for python 3...
from tkinter import *
from tkinter import ttk
root = Tk()
root.title('Full Window Scrolling X Y Scrollbar Example')
root.geometry("1350x400")
# Create A Main frame
main_frame = Frame(root)
main_frame.pack(fill=BOTH,expand=1)
# Create Frame for X Scrollbar
sec = Frame(main_frame)
sec.pack(fill=X,side=BOTTOM)
# Create A Canvas
my_canvas = Canvas(main_frame)
my_canvas.pack(side=LEFT,fill=BOTH,expand=1)
# Add A Scrollbars to Canvas
x_scrollbar = ttk.Scrollbar(sec,orient=HORIZONTAL,command=my_canvas.xview)
x_scrollbar.pack(side=BOTTOM,fill=X)
y_scrollbar = ttk.Scrollbar(main_frame,orient=VERTICAL,command=my_canvas.yview)
y_scrollbar.pack(side=RIGHT,fill=Y)
# Configure the canvas
my_canvas.configure(xscrollcommand=x_scrollbar.set)
my_canvas.configure(yscrollcommand=y_scrollbar.set)
my_canvas.bind("<Configure>",lambda e: my_canvas.config(scrollregion= my_canvas.bbox(ALL)))
# Create Another Frame INSIDE the Canvas
second_frame = Frame(my_canvas)
# Add that New Frame a Window In The Canvas
my_canvas.create_window((0,0),window= second_frame, anchor="nw")
for thing in range(100):
Button(second_frame ,text=f"Button {thing}").grid(row=5,column=thing,pady=10,padx=10)
for thing in range(100):
Button(second_frame ,text=f"Button {thing}").grid(row=thing,column=5,pady=10,padx=10)
root.mainloop()
you might be able to set a scrollbarr to root.
scrollderoot = tkinter.Scrollbar(orient="vertical", command=root.yview)
scrollderoot.grid(column=5, row=0, sticky='ns', in_=root) #instead of number 5, set the column as the expected one for the scrollbar. Sticky ns will might be neccesary.
root.configure(yscrollcommand=scrollderoot.set)
Honestly i didn't tried this but "should" work. Good luck.
This approach uses no Frame objects and is different in that it creates a very large Canvas with Scrollbars and asks you for an image to display on it.
The screen is then set with self.root.wm_attributes("-fullscreen", 1)
and self.root.wm_attributes("-top", 1)
Press the Escape key or Alt-F4 to close.
import tkinter as tk
from tkinter import filedialog as fido
class BigScreen:
def __init__( self ):
self.root = tk.Tk()
self.root.rowconfigure(0, weight = 1)
self.root.columnconfigure(0, weight = 1)
w, h = self.root.winfo_screenwidth(), self.root.winfo_screenheight()
self.canvas = tk.Canvas(self.root, scrollregion = f"0 0 {w*2} {h*2}")
self.canvas.grid(row = 0, column = 0, sticky = tk.NSEW)
self.makescroll(self.root, self.canvas )
self.imagename = fido.askopenfilename( title = "Pick Image to View" )
if self.imagename:
self.photo = tk.PhotoImage(file = self.imagename).zoom(2, 2)
self.window = self.canvas.create_image(
( 0, 0 ), anchor = tk.NW, image = self.photo)
self.root.bind("<Escape>", self.closer)
self.root.wm_attributes("-fullscreen", 1)
self.root.wm_attributes("-top", 1)
def makescroll(self, parent, thing):
v = tk.Scrollbar(parent, orient = tk.VERTICAL, command = thing.yview)
v.grid(row = 0, column = 1, sticky = tk.NS)
thing.config(yscrollcommand = v.set)
h = tk.Scrollbar(parent, orient = tk.HORIZONTAL, command = thing.xview)
h.grid(row = 1, column = 0, sticky = tk.EW)
thing.config(xscrollcommand = h.set)
def closer(self, ev):
self.root.destroy()
if __name__ == "__main__":
Big = BigScreen()
Big.root.mainloop()
My previous answer went well beyond the question asked so this is a cut-down version more accurately answers the question.
I did try the answer of Akash Shendage which didn't work for me out of the box. But with a few adjustments go it working.
#!/bin/env python3
from tkinter import ttk
import tkinter as tk
root = tk.Tk()
root.title('Full Window Scrolling X Y Scrollbar Example')
root.geometry("1350x400")
# Create A Main frame
main_frame = tk.Frame(root)
main_frame.pack(fill=tk.BOTH,expand=1)
# Create Frame for X Scrollbar
sec = tk.Frame(main_frame)
sec.pack(fill=tk.X,side=tk.BOTTOM)
# Create A Canvas
my_canvas = tk.Canvas(main_frame)
my_canvas.pack(side=tk.LEFT,fill=tk.BOTH,expand=1)
# Add A Scrollbars to Canvas
x_scrollbar = ttk.Scrollbar(sec,orient=tk.HORIZONTAL,command=my_canvas.xview)
x_scrollbar.pack(side=tk.BOTTOM,fill=tk.X)
y_scrollbar = ttk.Scrollbar(main_frame,orient=tk.VERTICAL,command=my_canvas.yview)
y_scrollbar.pack(side=tk.RIGHT,fill=tk.Y)
# Configure the canvas
my_canvas.configure(xscrollcommand=x_scrollbar.set)
my_canvas.configure(yscrollcommand=y_scrollbar.set)
my_canvas.bind("<Configure>",lambda e: my_canvas.config(scrollregion= my_canvas.bbox(tk.ALL)))
# Create Another Frame INSIDE the Canvas
second_frame = tk.Frame(my_canvas)
# Add that New Frame a Window In The Canvas
my_canvas.create_window((0,0),window= second_frame, anchor="nw")
for thing in range(100):
tk.Button(second_frame ,text=f"Button {thing}").grid(row=5,column=thing,pady=10,padx=10)
for thing in range(100):
tk.Button(second_frame ,text=f"Button {thing}").grid(row=thing,column=5,pady=10,padx=10)
root.mainloop()
From the great effbot docs:
In Tkinter, the scrollbar is a separate widget that can be attached to
any widget that support the standard scrollbar interface. Such widgets
include:
the Listbox widget.
the Text widget.
the Canvas widget
the Entry widget
So, you cannot directly use a scrollbar in a Frame. It may be possible to create your own Frame sub-class that supports the scrollbar interface.
Out of the 4 widgets listed above, only one allows other widgets within it: the Canvas. You can use a Canvas to have scrollable content, but placing widgets within a Canvas does not use pack or grid, but uses explicit pixel locations (i.e. painting on the Canvas).
Here's a class, and some example usage, that uses the .place method to add a scrollbar for the whole window. You can create a Frame object, and place it at your desired (x, y) coordinates. Then, simply pass your Frame object in place of root in main.frame to create a scrollable window at your desired coordinates.
from tkinter import *
class ScrollableFrame:
"""A scrollable tkinter frame that will fill the whole window"""
def __init__ (self, master, width, height, mousescroll=0):
self.mousescroll = mousescroll
self.master = master
self.height = height
self.width = width
self.main_frame = Frame(self.master)
self.main_frame.pack(fill=BOTH, expand=1)
self.scrollbar = Scrollbar(self.main_frame, orient=VERTICAL)
self.scrollbar.pack(side=RIGHT, fill=Y)
self.canvas = Canvas(self.main_frame, yscrollcommand=self.scrollbar.set)
self.canvas.pack(expand=True, fill=BOTH)
self.scrollbar.config(command=self.canvas.yview)
self.canvas.bind(
'<Configure>',
lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))
)
self.frame = Frame(self.canvas, width=self.width, height=self.height)
self.frame.pack(expand=True, fill=BOTH)
self.canvas.create_window((0,0), window=self.frame, anchor="nw")
self.frame.bind("<Enter>", self.entered)
self.frame.bind("<Leave>", self.left)
def _on_mouse_wheel(self,event):
self.canvas.yview_scroll(-1 * int((event.delta / 120)), "units")
def entered(self,event):
if self.mousescroll:
self.canvas.bind_all("<MouseWheel>", self._on_mouse_wheel)
def left(self,event):
if self.mousescroll:
self.canvas.unbind_all("<MouseWheel>")
# Example usage
obj = ScrollableFrame(
master,
height=300, # Total required height of canvas
width=400 # Total width of master
)
objframe = obj.frame
# use objframe as the main window to make widget

Categories

Resources