How to move a rectangle to the mouse position with Tkinter/Python? - python

I'm using Tkinter/Python's Canva class' coord() method to move a rectangle. What should I pass as paramters in order to make it work?
from tkinter import *
root = Tk()
def key(event):
print ("pressed", repr(event.char))
def callback(event):
position = (event.x,event.y)
event.widget.coords(item, position)
canvas= Canvas(root, width=100, height=100)
canvas.bind("<Key>", key)
canvas.bind("<Button-1>", callback)
item = canvas.create_rectangle(10,10,5,5)
canvas.pack()

move widget using mouse
from tkinter import *
import pyautogui
def on_move(event):
component=event.widget
locx, locy = component.winfo_x(), component.winfo_y()
w , h =master.winfo_width(),master.winfo_height()
mx ,my =component.winfo_width(),component.winfo_height()
xpos=(locx+event.x)-(15)
ypos=(locy+event.y)-int(my/2)
if xpos>=0 and ypos>=0 and w-abs(xpos)>=0 and h-abs(ypos)>=0 and xpos<=w-5 and ypos<=h-5:
component.place(x=xpos,y=ypos)
master = Tk()
master.geometry("%dx%d+0+0" % (500,500))
msg = Label(master, text = "Click & Move")
msg.config(bg='lightgreen', font=('times', 24, 'italic'))
msg.bind('<B1-Motion>',on_move)
msg.place(x=10,y=20)
mainloop()

This seems your first post. Welcome to SO :D
Updated answer: After some research and testing, it seems that you just need to pass the coordenates without the tuple. Storing x and y in a tuple is a problem, but also not providing the values for x2 and y2.
def callback(event):
event.widget.coords(item, event.x + 5, event.y + 5, event.x, event.y)
You can learn more here
Original wrong answer:
You can't move items on tk. Maybe try to clean the canvas and create the item at the new coordinates.
canvas.delete("all")
canvas.create_rectangle(event.x + 5,event.y + 5, position)

Related

How to delete a polygon in tkinter?

I'm trying to make a basic game in Tkinter that involves pressing a start button and making a shape appear which is working, then when you click on the shape it gets deleted and moved to a different random location.
I am getting NameError: name 'square' is not defined when I try to run it.
root=Tk()
frame=Frame(root)
can = Canvas(root, width=400, height=400)
can.pack(side=TOP)
def makeShape():
xpos = random.randint(1, 400)
ypos = random.randint(1, 400)
square=can.create_polygon(xpos, ypos, xpos + 40, ypos, xpos + 40, ypos + 40,
xpos, ypos + 40, fill="blue")
can.tag_bind(square,"<Button-1>",deleteShape)
def deleteShape(event):
can.delete(square)
but1 = Button(frame, text="Start", command=makeShape)
but1.grid(row=1, column=2)
frame.pack(side=BOTTOM)
root.mainloop()
It is because square is a local variable inside makeShape(), so it cannot be accessed outside the function.
You can use tags option in create_polygon() instead. If you want to move the square when it is clicked, deleteShape() is not necessary at all. Just using makeShape() is enough:
from tkinter import *
import random
root=Tk()
frame=Frame(root)
can = Canvas(root, width=400, height=400)
can.pack(side=TOP)
def makeShape():
# delete existing square
can.delete("square")
# create square at random position
xpos = random.randint(1, 360)
ypos = random.randint(1, 360)
can.create_polygon(xpos, ypos, xpos+40, ypos, xpos+40, ypos+40, xpos, ypos+40,
fill="blue", tags="square")
# call makeShape() when the square is clicked
can.tag_bind("square", "<Button-1>", lambda e: makeShape())
but1 = Button(frame, text="Start", command=makeShape)
but1.grid(row=1, column=2)
frame.pack(side=BOTTOM)
root.mainloop()
While it's not good practice, if you add line global square to makeShape() it will run as expected.
That's because if the name is assigned first time inside a block, it won't be visible to parent or sibling blocks.
There are alternatives, considered better for readability and more practical, but my suggestion is the quickest fix to your problem.

How to determine the cursor's position on the canvas after scrolling?

I'm using a canvas and I want to know the cursor's position, I use canvas.bind("<Button-1>", callback) and in the callback event.x and event.y
But my issue comes when I use a Scrollbar to move the content inside the canvas: I want the cursor's position to be adjusted by the Scrollbar offset.
def callback(event):
x = event.x
y = event.y
#need these x and y to by adjusted with the Scrollbar offset
root = tk.Tk()
yscrollbar = tk.Scrollbar(root)
canvas = tk.Canvas(root, yscrollcommand = yscrollbar
canvas.pack()
canvas.bind("<Button-1>", callback)
yscrollbar.config(command = canvas.yview)
yscrollbar.pack()
As indicated you can use the canvas.canvasx / canvas.canvasy methods:
class MyApp:
def __init__(self, app):
# Initialize canvas with scrollable region and mouse down binding
self.canvas = ...
# Exemplaric mouse down method
def onMouseDown(self, event):
# Get the position on the scrollable canvas
xOnCanvas = self.canvas.canvasx(event.x)
yOnCanvas = self.canvas.canvasy(event.y)
Reference answer: How to convert Tkinter canvas coordinate to window?
It's all right there in the documentation. canvasx and canvasy methods. – Bryan Oakley

place objects in canvas where clicked using binding python

I want to make an object appear in the tkinter window where the user clicks.
I have this code:
from tkinter import *
class Storage:
def __init__(self):
x = None
circle = None
w=None
class Game:
def Start():
#make object appear where clicked
root = Tk()
w = Canvas(root, width=200, height=100)
w.pack()
w.bind("<Button-1>", Start)
start = Button(text="Start!", command=Game.Start)
start.pack()
root.mainloop()
Any help would be appreciated thanks.
first of all you should put the canvas object on the frame, then bind the canvas to the event requred
from tkinter import *
class Storage:
def __init__(self):
x = None
circle = None
w=None
class Game:
def Start(event):
print("clicked at", event.x, event.y)
x = event.x
y = event.y
w.create_rectangle(x, y, 100, 100, fill="blue")
root = Tk()
frame = Frame(root, width=200, height=200)
frame.pack()
w = Canvas(frame, width=200, height=100)
w.pack()
w.bind("<Button-1>", Game.Start)
root.mainloop()

How to use write a loop for list append

from Tkinter import *
import csv
root = Tk()
def click(event):
global x,y
x, y= event.x,event.y
frame = Frame(root, width=100, height=100)
frame.bind("<Button-1>", click)
frame.pack()
root.mainloop()
row=[]
col=[]
row.append(x)
col.append(y)
Please! How do I write a loop, so that the two list can contain all x, and y that I clicked.
There's no reason to use an explicit loop here, one is already provided by root.mainloop, which calls your handler for you on every click event. Writing:
from Tkinter import *
root = Tk()
row = []
col = []
def click(event):
row.append(event.x)
col.append(event.y)
frame = Frame(root, width=100, height=100)
frame.bind("<Button-1>", click)
frame.pack()
root.mainloop()
will leave row and col populated with all of the x and y coordinates from each click once root.mainloop completes. There's also no reason to make x and y global: their global values will just always hold the values from the last call to click (or give you an undefined variable error if you never clicked at all).
As it is, you are only appending x and y once. You can make the append happen on click event - no loop required!
from tkinter import *
import csv
root = Tk()
coords = []
def click(event):
global x,y
x, y= event.x,event.y
coords.append([x, y])
print("Clicked at: ", x, y)
frame = Frame(root, width=100, height=100)
frame.bind("<Button-1>", click)
frame.pack()
root.mainloop()

Drag window when using overrideredirect

I know how to remove a border from a Tkinter window using overrideredirect, but whenever I do that the window becomes unresponsive. I can't move it using alt and dragging, or any other method.
I want to make an application that looks like one of those "riced" applications that are just a bare window, and obviously I can't get very far if it just sits unresponsive in the upper-left corner. So, how do I do this?
To make the window draggable, put bindings for <Button-1> (mouse clicks) and <B1-Motion> (mouse movements) on the window.
All you need to do is store the x and y values of a mouse down event and then during mouse motion events, you position the window based on the current pointer x and y, delta the original event x and y.
The handler for the mouse click binding stores the original event x and y.
The handler for the mouse movement binding calls the TopLevel method geometry() to reposition the window, based on current mouse position and the offset you have stored from the most recent mouse click. You supply a geometry string to the geometry method.
Here is a very minimal example which does not take into account the edges of the screen:
import tkinter
class Win(tkinter.Tk):
def __init__(self,master=None):
tkinter.Tk.__init__(self,master)
self.overrideredirect(True)
self._offsetx = 0
self._offsety = 0
self.bind('<Button-1>',self.clickwin)
self.bind('<B1-Motion>',self.dragwin)
def dragwin(self,event):
x = self.winfo_pointerx() - self._offsetx
y = self.winfo_pointery() - self._offsety
self.geometry('+{x}+{y}'.format(x=x,y=y))
def clickwin(self,event):
self._offsetx = event.x
self._offsety = event.y
win = Win()
win.mainloop()
EDIT by TheLizzard:
The code above works but doesn't behave correctly when there is more than one widget so this is the fixed code:
import tkinter as tk
class Win(tk.Tk):
def __init__(self):
super().__init__()
super().overrideredirect(True)
self._offsetx = 0
self._offsety = 0
super().bind("<Button-1>" ,self.clickwin)
super().bind("<B1-Motion>", self.dragwin)
def dragwin(self,event):
x = super().winfo_pointerx() - self._offsetx
y = super().winfo_pointery() - self._offsety
super().geometry(f"+{x}+{y}")
def clickwin(self,event):
self._offsetx = super().winfo_pointerx() - super().winfo_rootx()
self._offsety = super().winfo_pointery() - super().winfo_rooty()
root = Win()
label_1 = tk.Label(root, text="Label 1")
label_1.pack(side="left")
label_2 = tk.Label(root, text="Label 2")
label_2.pack(side="left")
root.mainloop()
Thanks to #dusty's answer, it had a jumping problem, and I solved it by saving the window location.
import tkinter
class Win(tkinter.Tk):
def __init__(self,master=None):
tkinter.Tk.__init__(self,master)
self.overrideredirect(True)
self._offsetx = 0
self._offsety = 0
self._window_x = 500
self._window_y = 100
self._window_w = 500
self._window_h = 500
self.geometry('{w}x{h}+{x}+{y}'.format(w=self._window_w,h=self._window_h,x=self._window_x,y=self._window_y))
self.bind('<Button-1>',self.clickwin)
self.bind('<B1-Motion>',self.dragwin)
def dragwin(self,event):
delta_x = self.winfo_pointerx() - self._offsetx
delta_y = self.winfo_pointery() - self._offsety
x = self._window_x + delta_x
y = self._window_y + delta_y
self.geometry("+{x}+{y}".format(x=x, y=y))
self._offsetx = self.winfo_pointerx()
self._offsety = self.winfo_pointery()
self._window_x = x
self._window_y = y
def clickwin(self,event):
self._offsetx = self.winfo_pointerx()
self._offsety = self.winfo_pointery()
win = Win()
win.mainloop()
self._window_x and self._window_y are the primary position of the window.
self._window_h and self._window_w are the height and width of the window.
This solution is works for me:
from tkinter import *
import mouse
global x, y
def standard_bind():
root.bind('<B1-Motion>', lambda e: event(e, Mode=True))
def event(widget, Mode=False):
global x, y
if Mode:
x = widget.x
y = widget.y
root.bind('<B1-Motion>', lambda e: event(e))
root.geometry('+%d+%d' % (mouse.get_position()[0]-x, mouse.get_position()[1]-y))
root = Tk()
root.overrideredirect(True)
root.bind('<B1-Motion>', lambda e: event(e, Mode=True))
root.bind('<ButtonRelease-1>', lambda e: standard_bind())
root.geometry('%dx%d+%d+%d' % (600, 60, 50, 50))
mainloop()
Here a bit more sophisticated method which assumes that you don't want to just click any where on the tkinter app to move it, but rather clicking on the title bar to move the app around while retaining the familiar "X" to close the app.
Works for python3.0 and later
Since tkinter does not (by default) allow you to directly achieve this, we must:
Remove the tkinter frame's title bar
Create our own title bar and recreate the "x" for closing the app
bind the event for clicking, such that the app moves when dragged
from tkinter import *
root = Tk()
root.title('The Name of Your Application')
root.geometry("500x300")
# remove title bar
root.overrideredirect(True)
def move_app(e):
root.geometry(f'+{e.x_root}+{e.y_root}')
def quitter(e):
root.quit()
#root.destroy()
# Create Fake Title Bar
title_bar = Frame(root, bg="darkgreen", relief="raised", bd=0)
title_bar.pack(expand=1, fill=X)
# Bind the titlebar
title_bar.bind("<B1-Motion>", move_app)
# Create title text
title_label = Label(title_bar, text=" My Awesome App!!", bg="darkgreen", fg="white")
title_label.pack(side=LEFT, pady=4)
# Create close button on titlebar
close_label = Label(title_bar, text=" X ", bg="darkgreen", fg="white", relief="sunken", bd=0)
close_label.pack(side=RIGHT, pady=4)
close_label.bind("<Button-1>", quitter)
my_button = Button(root, text="CLOSE!", font=("Helvetica, 32"), command=root.quit)
my_button.pack(pady=100)
root.mainloop()

Categories