How to display canvas coordinates when hovering cursor over canvas? - python

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()

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:

Tkinter - On which widget is mouse pointer currently?

I want to ask u guys if there is a way of getting name or some id of widget which is mouse pointer currently on. Is there a way of doing this? Thanks for any response.
Normally you get this information from a binding. However, if you want to poll the system at any point to find out which widget is under the mouse, you can use winfo_pointerxy to get the coordinates of the mouse, and then pass those to winfo_containing to get the widget under those coordinates.
Here's an example program that continuously prints out the widget under the mouse:
import tkinter as tk
def print_widget_under_mouse(root):
x,y = root.winfo_pointerxy()
widget = root.winfo_containing(x,y)
print("widget:", widget)
root.after(1000, print_widget_under_mouse, root)
root = tk.Tk()
label_foo = tk.Label(root, text="Foo", name="label_foo")
label_bar = tk.Label(root, text="Bar", name="label_bar")
button = tk.Button(root, text="Button", name="button")
button.pack(side="bottom")
label_foo.pack(fill="both", expand=True)
label_bar.pack(fill="both", expand=True)
print_widget_under_mouse(root)
root.mainloop()

Why does this scrollbar not work after inserting widgets into a listbox?

Basically, I am trying to add a scrollbar to a window containing widgets. I am able to successfully add a scrollbar to a Listbox widget and after inserting stuff, the program works just the way I want it to. But, I face a problem when I place widgets into the Listbox. The scrollbar appears but it seems to be disabled.
from tkinter import *
root = Tk()
root.geometry("640x480")
root.resizable(0,0)
myscrollbar = Scrollbar(root)
myscrollbar.pack(side=RIGHT, fill=Y)
mylist = Listbox(root, width=640, height=480, yscrollcommand=myscrollbar.set)
mylist.pack(fill=BOTH, expand=True)
for x in range(1, 101):
mylist.insert(END, Label(mylist, text="Label: "+str(x)).grid(row=x, column=0))
myscrollbar.config(command = mylist.yview)
root.mainloop()
Any way to fix this code?
from tkinter import *
def myScrollcmd(event):
mycanvas.config(scrollregion=mycanvas.bbox('all'))
root = Tk()
mycanvas = Canvas(root)
mycanvas.pack(fill=BOTH, expand=True)
myFrame = Frame(mycanvas)
mycanvas.create_window((0, 0), window=myFrame, anchor=NW)
myScrollbar = Scrollbar(mycanvas, orient=VERTICAL, command=mycanvas.yview)
myScrollbar.pack(side=RIGHT, fill=Y)
mycanvas.config(yscrollcommand=myScrollbar.set)
mycanvas.bind("<Configure>", myScrollcmd)
for x in range(100):
Label(myFrame, text="Text "+str(x)).pack()
root.mainloop()
This works. However, there is one problem that might not be a major one. The problem is, when my cursor is on the canvas, and I'm moving my mouse wheel, the scrollbar doesn't budge. However the scroll bar moves along with my mouse wheel when my cursor is on top of the scroll bar. I can drag the scroll box to scroll up and down and use the scroll buttons to scroll up and down but the mouse wheel only works when my cursor is hovering over the scrollbar widget.

Dual cursors in twin Tkinter Canvases

I've got two Canvases displaying images (one is the source, and the other is slightly modified).
I'd like to sync both Canvases' cursors, i.e when hovering over one, a cursor will also show on the other in the same position.
I've already done this by drawing a custom 'plus' cursor (2 intersecting lines), but I'm not satisfied with the result.
Is there a way to 'fake' the mouse hovering over the canvas at a certain position?
Edit:
My relevant code, as per request:
self.canvas_image.bind("<Motion>", self.processMouseEvent)
def processMouseEvent(self):
self.cursorSync.Sync(event)
The Motion event will have an x,y attribute assigned to it. Why not do something like this, where your cursor could be any object that can be managed by the place geometry manager:
def move_cursor(event):
cursor.place(x=event.x, y=event.y) # set x,y to cursor
root = Tk()
left = Canvas(root, width=100, height=100, bg='white')
right = Canvas(root, width=100, height=100, bg='black')
left.pack(fill=BOTH, expand=1, side=LEFT)
right.pack(fill=BOTH, expand=1, side=RIGHT)
cursor = Label(right, width=2, bg='red') # create cursor. this could be an image or whatever
left.bind('<Motion>', move_cursor)
mainloop()
No, there is no way to have more than one cursor active at a time. Your only option is to simulate a second cursor by either drawing one on the canvas, or using a small widget that you place over the canvas.

How to make a Button using the tkinter Canvas widget?

I want to obtain a button out of a Canvas. I've tried to pack the canvas in the button widget, but that didn't work. Googling a bit, I've found (here: How do you create a Button on a tkinter Canvas?) that the Canvas method create_window might help. But there should be something wrong in the way I'm using it.
import Tkinter
DIM = 100
root = Tkinter.Tk()
frame = Tkinter.Frame(root)
button = Tkinter.Button(None, width=DIM, height=DIM, command=root.quit)
circle = Tkinter.Canvas(frame, width=DIM, height=DIM)
circle.create_oval(5, 5, DIM-5, DIM-5, fill="red")
circle.create_window(0, 0, window=button)
frame.grid()
circle.grid(row=1, column=1)
root.mainloop()
If I erase the create_window line, I can se my painting but I can't (obviously) click on it. But in this way, the button widget cover my circle and shows a sad empty button.
Basically, I want to create a button with a red circle painted inside.
Tkinter doesn't allow you to directly draw on widgets other than the canvas, and canvas drawings will always be below embedded widgets.
The simple solution is to create the effect of a button using just the canvas. There's really nothing special about doing this: just create a canvas, then add bindings for ButtonPress and ButtonRelease to simulate a button being pressed.
Here's a rough idea:
class CustomButton(tk.Canvas):
def __init__(self, parent, width, height, color, command=None):
tk.Canvas.__init__(self, parent, borderwidth=1,
relief="raised", highlightthickness=0)
self.command = command
padding = 4
id = self.create_oval((padding,padding,
width+padding, height+padding), outline=color, fill=color)
(x0,y0,x1,y1) = self.bbox("all")
width = (x1-x0) + padding
height = (y1-y0) + padding
self.configure(width=width, height=height)
self.bind("<ButtonPress-1>", self._on_press)
self.bind("<ButtonRelease-1>", self._on_release)
def _on_press(self, event):
self.configure(relief="sunken")
def _on_release(self, event):
self.configure(relief="raised")
if self.command is not None:
self.command()
To complete the illusion you'll want to set a binding on <Enter> and <Leave> (to simulate the active state), and also make sure that the cursor is over the button on a button release -- notice how real buttons don't do anything if you move the mouse away before releasing.
What you can do is bind the canvas to the mouse:
import Tkinter
DIM = 100
root = Tkinter.Tk()
frame = Tkinter.Frame(root)
circle = Tkinter.Canvas(frame)
circle.create_oval(5, 5, DIM-5, DIM-5, fill="red")
frame.grid()
circle.grid(row=1, column=1)
##################################
def click(event):
root.quit()
circle.bind("<Button-1>", click)
##################################
root.mainloop()
Now, if a user clicks inside the canvas, function click will be called (essentially, the canvas has now been made a button).
Notice though that function click will be called if a user clicks anywhere in the canvas. If you want to make it so that click is only called when a user clicks in the circle, you can use event.x and event.y to get a hold of the x and y coordinates of the click. Once you have those, you can run a calculation to determine whether those coordinates are within the circle. Here is a reference on that.

Categories

Resources