how do I get mouse position while pressing a button in tkinter? - python

I'm trying to get the mouse position while pressing a button, but instead of getting the position of the mouse on Tkinter root, it gives me the position of the mouse on the button. For example, if the button is placed on 200, 200, and I press the top left of the button, it prints 0, 0 instead of 200, 200.
import Tkinter as tk
def leftclick(event):
print("left")
x, y = event.x, event.y
print('{}, {}'.format(x, y))
root = tk.Tk()
add_user = tk.Button(root, height=63, width=195 ,text="sign up a user")
add_user.place(x= 20, y = 30)
root.bind("<Button-1>", leftclick)
root.mainloop()

You can call the winfo_pointerx and winfo_pointery methods of a widget to get each individual coordinate, or you can call winfo_pointerxy to get them both:
def leftclick(event):
x, y = event.widget.winfo_pointerxy()
print('{}, {}'.format(x, y))

Related

tkinter drag-and-drop when clicking near linear objects

In my user interface I want to allow a user to drag a crosshair over a canvas which will be displaying a photo. I observed that it is unreasonably difficult for user to click on the lines of a crosshair to get a "direct hit". I want to be able to drag if the users click is anywhere within the RADIUS of the crosshair, not just on one of the lines. The code here demonstrates this not working, even though my click function is carefully and successfully detecting a hit within that radius.
I would also like for the user to be able to click in a new location, have the crosshair appear there and then be able to drag it from that point.
So far, failing on both counts. You have to hit it directly on the circle or one of the lines in order to drag it. Can anyone suggest a clean fix?
import tkinter as tk
import math
CROSS_HAIR_SIZE = 30
class View(tk.Canvas):
def __init__(self, parent, width=1000, height=750):
super().__init__(parent, width=width, height=height)
self.pack(fill="both", expand=True)
self.crosspoint = [-100, -100]
self.bind("<Button-1>", self.click)
def crosshair(self, x, y, size, color, tag="cross"):
a = self.create_line(x - size, y, x + size, y, fill=color, width=1, tag=tag)
b = self.create_line(x, y - size, x, y + size, fill=color, width=1, tag=tag)
self.create_oval(x - size, y - size, x + size, y + size, outline=color, tag=tag)
self.crosspoint = [x, y]
def click(self, event):
click1(self, event, (event.x, event.y))
def startDragging(self, event, tag):
# note: dragPoint is carried in physical coordinates, not image coordinates
self.tag_bind(tag, "<ButtonRelease-1>", self.dragStop)
self.tag_bind(tag, "<B1-Motion>", self.drag)
self.dragPoint = (event.x, event.y)
self.dragTag = tag
def dragStop(self, event):
if not self.dragPoint: return
self.crosspoint = (event.x, event.y)
self.dragPoint = None
def drag(self, event):
if not self.dragPoint: return
# compute how much the mouse has moved
xy = (event.x, event.y)
delta = sub(xy, self.dragPoint)
self.move(self.dragTag, delta[0], delta[1])
self.dragPoint = xy
def sub(v1, v2):
return (v1[0] - v2[0], v1[1] - v2[1])
def click1(view, event, xy):
if math.dist(view.crosspoint, xy) <= CROSS_HAIR_SIZE:
view.startDragging(event, "cross")
print("drag inside radius")
else:
view.crosspoint = xy
# it's simplest to just start over with the crosshair
x, y = xy
view.delete("cross")
view.crosshair(x, y, CROSS_HAIR_SIZE, "red", tag="cross")
view.startDragging(event, "cross")
print("drag outside radius")
root = tk.Tk()
root.geometry("600x600")
view = View(root)
cross = view.crosshair(150, 150, CROSS_HAIR_SIZE, "red")
root.mainloop()

stop canvas shape from following cursor after a mouse click

I am making a GUI to draw weighted graphs using Tkinter, so I made a button that when clicked creates a circle(graph vertex) using canvas. then the circle should follow the cursor to any position on the canvas and stop when the mouse clicks.
I managed to make the the circle follow the cursor, but I have no idea how to make it stop following.
this is the function I made
def buttonClick():
def Mouse_move(event):
x,y = event.x , event.y
canvas.moveto(vertex,x,y )
vertex= canvas.create_oval(650, 100, 750, 200)
canvas.bind("<Motion>", Mouse_move)
You can bind the mouse click event <Button-1> and unbind the <Motion> event in the callback:
def buttonClick():
def mouse_move(event):
x, y = event.x, event.y
canvas.moveto(vertex, x, y)
def mouse_click(event):
canvas.unbind("<Motion>")
vertex = canvas.create_oval(650, 100, 750, 200)
canvas.bind("<Motion>", mouse_move)
canvas.bind("<Button-1>", mouse_click)

How to draw a square on canvas when he user clicks 2 locations with tkinter

I am writing an app where the user can "paint" a canvas and now I want to add support for shapes.
I have a canvas called paint_canvas and I want to draw a shape between the 2 points the user clicks
def draw_square(event):
x1 = event.x
y1 = event.y
# now I want to get the next two points the user clicks
# how???????
paint_canvas = Canvas(root)
paint_canvas.bind('<Button-3>', draw_square)
You can make two events,
One for the press event ans one for release.
In the first event you store x and y mouse position in another scope (startx , starty)
And in the second event you store mouse position store x and y mouse position in another scope (endx, endy) and then draw your shape with this coordinates
See : https://docstore.mik.ua/orelly/other/python/0596001886_pythonian-chp-16-sect-9.html
And : https://www.codegrepper.com/code-examples/delphi/how+to+draw+rectangle+in+tkinter
If you want to show your rect animation you can use Motion events
Mouse Position Python Tkinter
You don't have to work with double-events. You can just as well store the previously clicked coordinates and draw a rectangle (and reset the previously stored coordinates) as soon as you have two clicks:
from tkinter import *
def draw_square(event):
x1 = event.x
y1 = event.y
w = event.widget ## Get the canvas object you clicked on
if w.coords == None:
w.coords = (x1,y1)
else:
w.create_rectangle(w.coords[0],w.coords[1],x1,y1,fill="#ff0000")
w.coords = None
root = Tk()
paint_canvas = Canvas(root,width=400,height=400,bg="#ffffff")
paint_canvas.pack()
paint_canvas.bind('<Button-3>', draw_square)
paint_canvas.coords=None
root.mainloop()
You could even create a temporary point to mark the first click, which may then be removed as soon as you hit the second one. This point (w.temp in the example below) can also be an attribute of the canvas, so you can access it easily via the click:
def draw_square(event):
x1 = event.x
y1 = event.y
w = event.widget ## Get the canvas object you clicked on
if w.coords == None:
w.coords = (x1,y1)
w.temp = w.create_oval(x1-1,y1-1,x1+1,y1+1,fill="#00ff00")
else:
w.create_rectangle(w.coords[0],w.coords[1],x1,y1,fill="#ff0000")
w.delete(w.temp)
w.coords = None

Tkinter making image variable into a class

I was making a game recently using tkinter and it's Canvas widget, but ran into a problem when trying to move an image. I set the canvas.create_image to a variable called character but noticed it turned it into an object. This causes issues as when I try and use the canvas.move(character), it comes back with "character not defined". Here is the full code:
from tkinter import *
from PIL import ImageTk, Image
import pygame
import threading
import time
gamestate= 'title'
waiting = False
#window init
root = Tk()
WIDTH=1280
HEIGHT=720
canvas = Canvas(root, width=WIDTH, height=HEIGHT, bg='black')
canvas.pack(expand=1, fill=BOTH)
logo = PhotoImage(file='Ghost clipart.png')
root.iconphoto(False, logo)
#setting up all the images
title_image = PhotoImage(file='title.png')
board_image = Image.open('board.png')
done_board = board_image.resize((1920,1080), Image.ANTIALIAS)
board_image = ImageTk.PhotoImage(done_board)
character_image = ImageTk.PhotoImage(file='first_run_left.png')
#display the title screen
def title_screen():
title = canvas.create_image(WIDTH/2,HEIGHT/2, anchor=CENTER, image=title_image)
#define the events after clicking play
def play():
canvas.delete("all")
board = canvas.create_image(WIDTH/2 + 100,HEIGHT/2 - 360, anchor=CENTER, image=board_image)
character = canvas.create_image(556,304,anchor=CENTER,image=character_image)
#testing for what should happen the the mouse button is clicked
def click(event):
if gamestate == 'title':
print(event.x, event.y)
if event.x > 475 and event.x < 804 and event.y > 213 and event.y < 337:
play()
elif event.x > 475 and event.x < 804 and event.y > 404 and event.y < 527:
print("skin")
def up(event):
canvas.move(character,0,10)
root.bind('<Button-1>', click)
root.bind('<Up>', up)
#audio module
def test_loop():
while True:
pygame.mixer.init()
pygame.mixer.music.load("nokia-ringtone-arabic.mp3")
pygame.mixer.music.play()
time.sleep(23)
thread = threading.Thread(target=test_loop)
thread.daemon = True
thread.start()
title_screen()
root.mainloop()
Here is a simple script that uses the arrow keys to move an image on the canvas. Note that the variable img (in your case character) must be accessible from the functions that act upon it, therefore its scope must be above these functions.
You can use this as a starting point to solve your problem.
image used:
import tkinter as tk
def up(event):
canvas.move(img, 0, -10)
def down(event):
canvas.move(img, 0, 10)
def left(event):
canvas.move(img, -10, 0)
def right(event):
canvas.move(img, 10, 0)
width = 500
height = 500
window = tk.Tk()
window.title("Moving image")
canvas = tk.Canvas(window, width=500, height=500)
canvas.pack()
my_image = tk.PhotoImage(file="pix.png")
img = canvas.create_image(260, 125, anchor=tk.NW, image=my_image)
window.bind("<Up>", up)
window.bind("<Down>", down)
window.bind("<Left>", left)
window.bind("<Right>", right)
window.mainloop()
Since character is a local variable inside play() function, it cannot be accessed inside up() function.
However you can use tags option of create_image():
def play():
...
canvas.create_image(556,304,anchor=CENTER,image=character_image, tags='character')
Then you can refer to that image item using the same tag:
def up(event):
canvas.move('character',0,-10) # use tag 'character'
create_image doesn't turn anything into an object. It simply returns an integer identifier. The original object is unchanged. character is undefined because it's a local variable and thus only visible in the method that created it.

python tkinter - track and save mousecklicks to build canvas items

I programmed a method like this on a canvas:
When I press button1, the variable "state" changes to 0 and every click on the canvas results in a circle
When I press button2, the variable "state" changes to 1. When the item that I clicked is a circle, my variable "selected" changes from None to 1 and I am also saving the coordinates of my mouseclick
My question: how do I code, that python should wait for the second click, and look if its another circle too? And if so, how can I draw a line between them?
def newKnotornewEdge(event):
if self.state == 0:
self.canvas.create_oval(event.x-25,event.y-25,event.x+25, event.y+25, fill="blue")
self.canvas.create_text(event.x, event.y, text="A", fill="white")
elif self.state == 1:
if self.canvas.itemcget(self.canvas.find_overlapping(event.x, event.y, event.x, event.y), "fill") == "blue":
self.selected = 1
start_x = event.x
start_y = event.y
else:
self.selected = None
if self.selected == 1 and #second mouseclick is a circle too:
#draw line that connects those circels
self.canvas.bind("<Button -1>", newKnotornewEdge)
The following canvas screenshot shows the app with 8 circles drawn; 2 and 4 of them are joined with lines; the red circle has been selected (this is what the color red indicates), and is ready to be joined to another circle, on the next click; one circle is not joined and not selected.
make_circle:
a click on the button selects the action 'draw a circle', and purges the circles that may have already been selected.
when this action is active, a click on the canvas draws a blue circle.
join_circles:
When clicked, it activates the action 'join circle' and purges the circles that may have already been selected.
The first click on a circle on the canvas selects this circle whose color changes to red; the second click on a circle on canvas selects the second circle, joins the two with a line, resets the colors to blue, and purges the selection.
A click on an empty part of the canvas does nothing.
You will need to keep track of which action to perform: draw a new circle, or select two circles to join. You also need to keep track of how many circles have been selected.
Then, after successfully drawing a line between circles, you will need to purge the selection.
I chose to use the "functions as a first class object" capability of python to avoid messy state accounting: you select the action to be performed by clicking the relevant button, then, clicks on the canvas will be related to this action.
The following code does that on the canvas, and prints which action is selected, and which is performed in the console:
import tkinter as tk
class CommandButtons(tk.Frame):
def __init__(self, master):
self.master = master
super().__init__(self.master)
self.make_circle_btn = tk.Button(root, text='make_circle', command=make_circle)
self.make_circle_btn.pack()
self.join_circles_btn = tk.Button(root, text='join_circles', command=select_circles)
self.join_circles_btn.pack()
self.pack()
class CircleApp(tk.Frame):
def __init__(self, master):
self.master = master
super().__init__(self.master)
self.canvas = tk.Canvas(root, width=600, height=600, bg='cyan')
self.canvas.pack(expand=True, fill=tk.BOTH)
self.pack()
def make_circle():
_purge_selection()
print('cmd make_circle selected')
c_app.canvas.bind('<Button-1>', _make_circle)
def _make_circle(event):
c_app.canvas.create_oval(event.x - 25, event.y - 25, event.x + 25, event.y + 25, fill="blue")
def select_circles():
_purge_selection()
print('cmd join_circles selected')
c_app.canvas.bind('<Button-1>', _select_circles)
def _select_circles(event):
print(f'select circle {event}')
x, y = event.x, event.y
selection = c_app.canvas.find_overlapping(x, y, x, y)
if selection is not None and len(selected_circles) < 2:
selected_circles.append(selection)
c_app.canvas.itemconfigure(selection, fill='red')
if len(selected_circles) == 2:
if all(selected_circles):
_join_circles()
_purge_selection()
def _join_circles():
coordinates = []
for item in selected_circles:
x, y = find_center(item)
print(x, y)
coordinates.append(x)
coordinates.append(y)
c_app.canvas.create_line(coordinates)
def _purge_selection():
global selected_circles
for item in selected_circles:
c_app.canvas.itemconfigure(item, fill='blue')
selected_circles = []
def find_center(item):
x0, y0, x1, y1 = c_app.canvas.bbox(item)
return (x0 + x1) / 2, (y0 + y1) / 2
if __name__ == '__main__':
selected_circles = []
root = tk.Tk()
command_frame = CommandButtons(root)
c_app = CircleApp(root)
root.mainloop()
A better version would use a class to encapsulate the selected items; here I used a collection as global variable. It would also properly handle overlapping circle selection.

Categories