How to move multiple objects together in tkinter canvas? [duplicate] - python

This question already has an answer here:
How do I move multiple objects at once on a Tkinter canvas?
(1 answer)
Closed last year.
Im trying to move some rectangles with text in them around a canvas with mouse dragNdrop. Im using find_overlapping to select rectangles to be moved. This means the text originally created as part of class object Rect is not moved. Is there a way to modify my code to move all objects in a class object or perhaps find the class object ID using find_overlapping?
Text on rectangles can be identical, as shown in example. Tagging all elements in the class object with a random tag to group them together was my first idea, but retrieving such tag info using find_ovelapping has not been succesful.
import tkinter as tk
root=tk.Tk()
PAB=tk.Canvas(width=400, height=400)
#checks if a certain canvas object has a certain tag
def hastag(tag, id):
if any(tag in i for i in PAB.gettags(id)):return True
else:return False
class Rect:
def __init__(self, x1, y1, name):
rec = PAB.create_rectangle(x1,y1,x1+40,y1+40, fill='#c0c0c0', tag=('movable', name))
text = PAB.create_text(x1+20,y1+20, text=name)
#mouse click find object to move
def get_it(event):
delta=5
global cur_rec
for i in PAB.find_overlapping(event.x-delta, event.y-delta, event.x+delta, event.y-delta):
if hastag('movable', i):
cur_rec = i
PAB.bind('<Button-1>', get_it)
#mouse movement moves object
def move_it(event):
xPos, yPos = event.x, event.y
xObject, yObject = PAB.coords(cur_rec)[0],PAB.coords(cur_rec)[1]
PAB.move(cur_rec, xPos-xObject, yPos-yObject)
PAB.bind('<B1-Motion>', move_it)
#test rects
bob = Rect(20,20,'Bob')
rob = Rect(80,80,'Rob')
different_bob = Rect(160,160,'Bob')
PAB.pack()
root.mainloop()
Thanks. If any clarifications are neccesary Id be happy to help.

A better way would be to use the same tag for all the items that you want to move together so in your case both rectangle and text must have the same tag.
import tkinter as tk
root=tk.Tk()
PAB=tk.Canvas(width=400, height=400, bg="gray")
class Rect:
def __init__(self, x1, y1, name):
tag = f"movable{id(self)}"
rec = PAB.create_rectangle(x1,y1,x1+40,y1+40, fill='#c0c0c0', tag=(tag, ))
text = PAB.create_text(x1+20,y1+20, text=name, tag=(tag,))
def in_bbox(event, item): # checks if the mouse click is inside the item
bbox = PAB.bbox(item)
return bbox[0] < event.x < bbox[2] and bbox[1] < event.y < bbox[3]
#mouse click find object to move
def get_it(event):
delta=5
global cur_rec
cur_rec = PAB.find_closest(event.x, event.y) # returns the closest object
if not in_bbox(event, cur_rec): # if its not in bbox then sets current_rec as None
cur_rec = None
#mouse movement moves object
def move_it(event):
if cur_rec:
xPos, yPos = event.x, event.y
xObject, yObject = PAB.coords(cur_rec)[0],PAB.coords(cur_rec)[1]
PAB.move(PAB.gettags(cur_rec)[0], xPos-xObject, yPos-yObject)
PAB.bind('<Button-1>', get_it)
PAB.bind('<B1-Motion>', move_it)
#test rects
bob = Rect(20,20,'Bob')
rob = Rect(80,80,'Rob')
different_bob = Rect(160,160,'Bob')
PAB.pack()
root.mainloop()

This will work but I'm not sure it's the best way to do it.
Basically it uses the fact that the text is added to the canvas after the rectangle so it can be identified using cur_rec+1.
def move_it(event):
xPos, yPos = event.x, event.y
xObject, yObject = PAB.coords(cur_rec)[0],PAB.coords(cur_rec)[1]
# move rectangle
PAB.move(cur_rec, xPos-xObject, yPos-yObject)
# move text associated with rectangle
PAB.move(cur_rec+1, xPos-xObject, yPos-yObject)

Apply the same tag to both the text and the rectangle. Then, use the tag when calling move. Here's one way to do it:
class Rect:
def __init__(self, x1, y1, name):
identifier = f"id:{id(self)}"
rec = PAB.create_rectangle(x1,y1,x1+40,y1+40, fill='#c0c0c0', tags=('movable', name, identifier))
text = PAB.create_text(x1+20,y1+20, text=name, tags=('movable', identifier))
You can then return the identifier rather than then index of the selected item:
def get_it(event):
delta=5
global cur_rec
for i in PAB.find_overlapping(event.x-delta, event.y-delta, event.x+delta, event.y-delta):
if hastag('movable', i):
identifier = [tag for tag in PAB.gettags(i) if tag.startswith("id:")][0]
cur_rec = identifier

Related

Tkinter widgets created in an Update function run by tk.after function do not create untill the aforementioned Update ends

I intend to make a Py code which creates a tkinter dot that turns on a key press and deletes on a key press of couple keys.
The dot already is functional but i need it switch on and off on certain keypresses/mouse clicks which means i need an outside tkinter.mainloop() Update function.
The Update function with a while in it to constantly check if conditions to turn it off/on are present. But the Tkinter widget Somehow gets applied to the screen Only when the function nds. Like widget could be created but it will only take effect when function ends. And i need to turn it off/on dynamically.
I have tried to use a tkinter.after() with additional one at the end of called function only to find out an error of Recursion depth. What i expected to happen was that the function would be called over and over again, instead it runs that function like a while loop. I also have tried Asyncio.run() but it would result not making it visible till the function ends at least once. And I need to change it dynamically.
from tkinter import *
from tkinter import Canvas
from winsound import Beep
from time import sleep
import asyncio
import keyboard
import mouse
root = Tk()
width = root.winfo_screenwidth()
height = root.winfo_screenheight()
class tk_Dot():
def __init__(self,x=-1,y=-1,radius=4,color="red"):
self.x = x
if x == -1:
self.x = width/2-radius//2
print(self.x)
self.y = y
if y == -1:
self.y = height/2+radius//2
print(self.y)
self.radius=radius
self.color = color
self.lines = []
self.count = 1
def line(self,i):
return canvas.create_line(self.x, self.y-i, self.x+self.radius, self.y-i, fill=self.color)
def create(self):
self.lines = []
for i in range(0,self.radius):
self.lines.append(self.line(i))
def delete(self):
for i in range(0,self.radius):
canvas.delete(self.lines[i])
canvas.dtag(self.lines[i])
opacity_of_tk_window = 1 # From 0 to 1 0 meaning completely transparent 1 meaning everything created in canvas will give the color it was given
root.attributes('-alpha',opacity_of_tk_window)
# Invisible Tkinter window label
root.overrideredirect(True)
# Makes Transparent background
transparent_color = '#f0f0f0'
root.wm_attributes('-transparent', transparent_color)
canvas = Canvas()
# Rectangle filled with color that is specified above as the transparent color so practically making transparent background
canvas.create_rectangle(0, 0, width, height, fill=transparent_color)
canvas.pack(fill=BOTH, expand=1)
radius = 2
radius = 1+radius\*2
# Create a dot class
game_dot = tk_Dot(width/2-radius//2+1,height/2+1+radius//2,radius,"Red")
# Create a Dot at the middle of the calorant crosshair
# game_dot.create()
# Delete the dot
# game_dot.delete()
def Update():
game_dot.create()
print("Dot should be visible by now")
print("Is it?")
sleep(5) #sec
print("Oh yeah after the function ends.") # the problem
def Delete():
game_dot.delete()
root.geometry('%dx%d+%d+%d' % (width, height, -2,-2))
# Tkinter window always on top
root.attributes('-topmost',True)
root.after(1000,Update())
root.mainloop()

Canvas.create_image(), draggable image seems not to be moving from its place?

Im working with Tkinter currently and im trying to make an draggable canvas image, the code i've "made"/edited from another stack overflow question, the second one to be particular. There code works for me when im using tk.Frame(), but it gives an error for Canvas.create_image(). So i edited it a little but now it seems to to be moving the image at all when i drag it
My code:
from tkinter import *
import pyautogui
x,y=pyautogui.size()
tk = Tk()
c = Canvas(tk, width=x, height=y)
c.place(x=-2,y=-3)
img = ImageTk.PhotoImage(PIL.Image.open(r"Data\backgrounds\background.jpg"))
c.create_image(0, 0, image=img, anchor=NW)
def command6():
print("command6")
def make_draggable(widget,btn="<Button-1>",motion="<B1-Motion>"):
def __draggable__(widget):
c.tag_bind(btn,btn,on_drag_start)
c.tag_bind(motion,motion,on_drag_motion)
def on_drag_start(event):
widget = event.widget
widget._drag_start_x = event.x
widget._drag_start_y = event.y
def on_drag_motion(event):
widget = event.widget
x = widget.winfo_x() - widget._drag_start_x + event.x
y = widget.winfo_y() - widget._drag_start_y + event.y
widget.move(widget,x, y)
__draggable__(widget)
APP_6 = r'apps\File.png'
APP_6 = PIL.Image.open(APP_6)
APP_6 = APP_6.resize((48,48),PIL.Image.ANTIALIAS)
APP_6 = ImageTk.PhotoImage(APP_6)
image_6 = ImageTk.PhotoImage(PIL.Image.open(r"apps\File.png"))
image_id_6 = c.create_image(48,48, image=APP_6)
c.move(image_id_6, 1,y-735)
c.tag_bind(image_id_6, '<Button-1>',command6)
make_draggable(image_id_6)
tk.mainloop()
this gives no errors or any unwanted output in the console.
There are few issues inside make_draggable() function:
first argument of .tag_bind() is the item ID of a canvas item. For your case, it is widget argument of make_draggable(). So the following lines:
c.tag_bind(btn,btn,on_drag_start)
c.tag_bind(motion,motion,on_drag_motion)
should be changed to
c.tag_bind(widget, btn, on_drag_start)
c.tag_bind(widget, motion, on_drag_motion)
Same apply to c.move(...) inside on_drag_motion()
the logic/calculation on moving the canvas item is wrong
Below is the modified make_draggable() function:
def make_draggable(widget, btn="<Button-1>", motion="<B1-Motion>"):
def __draggable__(widget):
c.tag_bind(widget, btn, on_drag_start)
c.tag_bind(widget, motion, on_drag_motion)
c._item_id = widget # save the item ID for later use
def on_drag_start(event):
widget = event.widget
# get the top-left coordinates of the selected item
x, y, *_ = widget.bbox(widget._item_id)
# save the offset of current mouse position from the top-left coordinates
widget._dx = event.x - x
widget._dy = event.y - y
def on_drag_motion(event):
widget = event.widget
# calculate the top-left coordinates of the item that the item to be moved to
x = event.x - widget._dx
y = event.y - widget._dy
# move the item using moveto() instead of move()
widget.moveto(widget._item_id, x, y)
__draggable__(widget)
As the above logic applies only on the item of the last call of make_draggable(). It is better to use class instead of function:
class make_draggable():
def __init__(self, item_id, btn="<Button-1>", motion="<B1-Motion>"):
self.item_id = item_id
c.tag_bind(item_id, btn, self.on_drag_start, add=True)
c.tag_bind(item_id, motion, self.on_drag_motion, add=True)
def on_drag_start(self, event):
x, y, *_ = event.widget.bbox(self.item_id)
self.dx, self.dy = event.x-x, event.y-y
def on_drag_motion(self, event):
event.widget.moveto(self.item_id, event.x-self.dx, event.y-self.dy)

Monopoly python game using canvas player movement error

I am creating an online monopoly game using canvas and tkinter and having trouble trying to make the "player1.png" .ie my character to move across the board. Please help!
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Monopoly Physics Edition")
self.pack(fill=BOTH, expand=1)
canvas = Canvas(self)
global player1
load= Image.open("player1.png")
player1 = ImageTk.PhotoImage(load)
img = Label(image=player1)
img.place(x=834, y=60)
def main():
canvas = Tk()
ex = Example()
canvas.geometry("1150x820+800+700")
if __name__ == '__main__':
main()
Ok so I programmed in the basics here:
import tkinter as tk
from PIL import Image, ImageTk
class Player:
def __init__(self, canvas, sprite, pos):
self.posx, self.posy = pos
self.canvas = canvas
self.sprite = sprite
self.tk_image = None
self.canvas_id = None
def display(self):
# This will display the train card on the board
self.load_sprite()
# If we already displayed the object hide it first
if self.canvas_id is not None:
self.hide_from_canvas()
# We need to create a new canvas object and get its id
# so that later we can move/remove it from the canvas
self.canvas_id = self.canvas.create_image(self.posx, self.posy, image=self.tk_image)
def hide_from_canvas(self):
# This will remove the sprite from the canvas
self.canvas.delete(self.canvas_id)
def load_sprite(self):
# If we already loaded the sprite just don't do anything
if self.tk_image is not None:
return None
# Later you can determine the filename from `self.name`
filename = "img_small.png"
pillow_image = Image.open(filename)
# You must keep a reference to this `tk_image`
# Otherwise the image would show up on the board
self.tk_image = ImageTk.PhotoImage(pillow_image)
def move(self, change_x, change_y):
# Move the object by change_x, change_y
self.canvas.move(self.canvas_id, change_x, change_y)
class Property:
def __init__(self, canvas, name, cost, pos):
self.posx, self.posy = pos
self.canvas = canvas
self.name = name
self.cost = cost
self.tk_image = None
self.canvas_id = None
def display(self):
# This will display the train card on the board
self.load_sprite()
# If we already displayed the object hide it first
if self.canvas_id is not None:
self.hide_from_canvas()
# We need to create a new canvas object and get its id
# so that later we can move/remove it from the canvas
self.canvas_id = self.canvas.create_image(self.posx, self.posy, image=self.tk_image)
def hide_from_canvas(self):
# This will remove the sprite from the canvas
self.canvas.delete(self.canvas_id)
def load_sprite(self):
# If we already loaded the sprite just don't do anything
if self.tk_image is not None:
return None
# Later you can determine the filename from `self.name`
filename = "img_small.png"
pillow_image = Image.open(filename)
# You must keep a reference to this `tk_image`
# Otherwise the image would show up on the board
self.tk_image = ImageTk.PhotoImage(pillow_image)
def move(self, change_x, change_y):
# Move the object by change_x, change_y
self.canvas.move(self.canvas_id, change_x, change_y)
class App(tk.Canvas):
def __init__(self, master, **kwargs):
# Create the canvas
super().__init__(master, **kwargs)
# lets show all of the monopoly properties on the screen
self.init()
# I will also bind to key presses (You might want to use them later)
master.bind("<Key>", self.on_key_press)
# I will also bind to mouse presses
super().bind("<Button-1>", self.on_mouse_press)
def on_key_press(self, event):
print("You pressed this character:", repr(event.char))
print("For non character keys (like Escape):", repr(event.keysym))
# For now I will move the player sprite by 10 pixels to the right
# when "d" or the right key is pressed
if (event.char == "d") or (event.keysym == "Right"):
# Move the first player of list
self.players[0].move(10, 0)
def on_mouse_press(self, event):
print("You clicked with the mouse at this position:", (event.x, event.y))
def init(self):
# A list that will hold all of the properties:
self.properties = []
# A list that will hold all of the players playing
self.players = []
# I will only create 1 property (You can add the rest)
# I will create 1 of the trains where the cost is 200 and
# its position is (50, 50). Note (0, 0) is the top left
# corner of the canvas
train1 = Property(self, "Train", 200, (50, 50))
train1.display()
self.properties.append(train1)
# I will also create a player at position (100, 100)
player1 = Player(self, "player_sprite1", (100, 100))
player1.display()
self.players.append(player1)
# Create the window
root = tk.Tk()
# Make it so that the user can't resize the window
# Otherwise you will have a lot of problems with your images not being in
# the correct places
root.resizable(False, False)
# Create the App, I also passed in `width=200, height=200` those are in pixels
main_app = App(root, width=200, height=200)
# Show the App on the screen:
main_app.pack()
# Enter tkinter's mainloop
root.mainloop()
It allows us to move/hide/display any of the sprites we create. I also added a way for use to detect key/mouse presses (might come in handy later). Using this it shouldn't be hard to create the full program. Note that most of the methods in the Player and Property class are the same so if you know how to use inheritance, you should be able to simplify my code by a lot.

Instance variable changing value when another instance of same class is modified, Python-Tkinter

The Point class has an instance variable point, which keeps tabs of each unique corner of the polygon. Problem is, when I hover the cursor over the second instance of the class, the point variable of the first instance changes. My code,
from tkinter import *
from tkinter import Tk,Canvas
import sys,os,string,time
class Point():
def __init__(self,root,canvas,pt,radius = 4):
self.canvas = canvas
self.root = root
self.point = self.canvas.create_oval(pt[0]-radius,pt[1]-radius,pt[0]+radius,pt[1]+radius,fill = "green", tag = "Point")
self.canvas.tag_bind("Point","<Enter>",self.enter)
self.canvas.tag_bind("Point","<Leave>",self.leave)
def enter(self,event):
print(self.point)
self.canvas.itemconfigure(CURRENT,fill="blue")
self.loc = 1
def leave(self,event):
self.canvas.itemconfigure(CURRENT,fill="green")
root = Tk()
root.title("Poly Draw")
canvas = Canvas(root,width = 256, height = 256, borderwidth = 1)
pt = [100,100]
point = Point(root,canvas,pt)
point2 = Point(root,canvas,[150,150])
print(point.point)
print(point2.point)
canvas.pack()
root.mainloop()
When you run the above piece of code you will see that the instance variable self.point is changing for the first instance when I hover over the second instance(I am printing self.point when cursor enters the widget). Noted that, before I run the mainloop() the instance variable is correct.
You're binding the tag "Point" sequentially, so only the last one is active.
I think you instead want to bind to the item id, not to the (non-unique) tag.
If so, change the binding instead to:
def __init__(self,root,canvas,pt,radius = 4):
self.canvas = canvas
self.root = root
self.point = self.canvas.create_oval(pt[0]-radius,pt[1]-radius,pt[0]+radius,pt[1]+radius,fill = "green", tag = "Point")
self.canvas.tag_bind(self.point,"<Enter>",self.enter)
self.canvas.tag_bind(self.point,"<Leave>",self.leave)

Tkinter Canvas - Draw text on top

I have this class to create a Statusbar:
class Statusbar(Canvas):
'''Creates a statusbar widget'''
def __init__(self, master = None, **options):
if not master: master = Tk()
self.master, self.options = master, options
self.barFill, self.addText, self.value = self.options.get('barFill', 'red'), self.options.get('addText', True), 0
for option in ('barFill', 'addText'):
if option in self.options: del self.options[option]
Canvas.__init__(self, master, **self.options)
self.offset = self.winfo_reqwidth() / 100
self.height = self.winfo_reqwidth()
if self.addText: self.text = self.create_text(self.winfo_reqwidth()/2, self.winfo_reqheight()/2, text = '0%')
self.bar = self.create_rectangle(0, 0, self.value, self.height, fill = self.barFill)
def setValue(self, value):
'''Sets the value of the status bar as a percent'''
self.value = value * self.offset
self.coords(self.bar, 0, 0, self.value, self.height)
if self.addText: self.itemconfigure(self.text, text = str(self.value/self.offset) + '%')
def change(self, value):
'''Changes the value as a percent'''
self.value += (value * self.offset)
self.coords(self.bar, 0, 0, self.value, self.height)
if self.addText: self.itemconfigure(self.text, text = str(self.value/self.offset) + '%')
My issue is that the text is always drawn under rectangle. So when the rectangle reaches the text, you can't see the text anymore. How can I fix this? Thanks in advance.
The fact that one object sits atop another is called the stacking order. By default, objects created later have a higher stacking order than those that were created earlier. So, one solution is to draw the rectangle and then draw the text.
You can also move things up or down the stacking order using the lift and lower commands of a canvas. You must give it an id or tag of what you want to lift or lower, and optionally an id or tag of the object you want the first object(s) to be above or below.
So, for example, you could raise the text above the rectangle like this:
self.lift(self.text, self.bar)
If you want to get really fancy, you can create the notion of layers. I gave an example in another answer, here: https://stackoverflow.com/a/9576938/7432
In my programming class, we said put whatever text you don't want to be blocked drawn last. So put the text at the bottom of what function you are using to draw with

Categories