I'm creating two rectangles. I want to delete rectangles from the canvas by right-clicking. The code is able to delete only 1 rectangle but not the other one. I used tag_bind("Button1") function but only bottom one is getting deleted.
Tag_bind function should be able to get the id and delete any of the selected rectangles but it is not happening.
#import sys, os, string, time
import tkinter
tk = tkinter
root =tk.Tk()
root.title ("Drag-N-Drop Demo")
# A Python example of drag and drop functionality within a single Tk widget.
# The trick is in the bindings and event handler functions.
# Tom Vrankar twv at ici.net
canvas =tk.Canvas ( width =256, height =256,
relief =tk.RIDGE, background ="white", borderwidth =1)
class CanvasDnD (tk.Frame):
def __init__ (self, master):
self.master =master
self.loc =self.dragged =0
tk.Frame.__init__ (self, master)
id=canvas.create_rectangle(75,75,100,100,tags="DnD")
canvas.tag_bind(id,"<ButtonPress-1>")
id=canvas.create_rectangle(100,100,125,125,tags="DnD")
canvas.tag_bind(id,"<ButtonPress-1>")
canvas.pack (expand =1, fill =tk.BOTH)
canvas.tag_bind ("DnD", "<ButtonPress-1>", self.down)
canvas.tag_bind ("DnD", "<ButtonRelease-1>", self.chkup)
canvas.tag_bind ("DnD", "<Enter>", self.enter)
canvas.tag_bind ("DnD", "<Leave>", self.leave)
self.popup = tk.Menu(root, tearoff=0)
self.popup.add_command(label="delete",command=lambda: self.dele(id))
root.bind("<Button-3>", self.do_popup)
def do_popup(self,event):
# display the popup menu
try:
self.popup.tk_popup(event.x_root, event.y_root, 0)
finally:
# make sure to release the grab (Tk 8.0a1 only)
self.popup.grab_release()
# empirical events between dropee and target, as determined from Tk 8.0
# down.
# leave.
# up, leave, enter.
def down (self, event):
#print ("Click on %s" %event.widget.itemcget (tk.CURRENT, "text"))
self.loc =1
self.dragged =0
event.widget.bind ("<Motion>", self.motion)
def motion (self, event):
root.config (cursor ="exchange")
cnv = event.widget
cnv.itemconfigure (tk.CURRENT, fill ="blue")
x,y = cnv.canvasx(event.x), cnv.canvasy(event.y)
a,b = cnv.canvasx(event.x + 25), cnv.canvasy(event.y+25)
got = event.widget.coords (tk.CURRENT, x, y, a, b)
def leave (self, event):
self.loc =0
def enter (self, event):
self.loc =1
if self.dragged ==event.time:
self.up (event)
def chkup (self, event):
event.widget.unbind ("<Motion>")
root.config (cursor ="")
self.target =event.widget.find_withtag (tk.CURRENT)
#event.widget.itemconfigure (tk.CURRENT, fill =self.defaultcolor)
if self.loc: # is button released in same widget as pressed?
self.up (event)
else:
self.dragged =event.time
def up (self, event):
event.widget.unbind ("<Motion>")
if (self.target ==event.widget.find_withtag (tk.CURRENT)):
print("1")
# print ("Select %s" %event.widget.itemcget (tk.CURRENT, "text"))
else:
event.widget.itemconfigure (tk.CURRENT, fill ="blue")
self.master.update()
time.sleep (.1)
print ("%s Drag-N-Dropped onto %s" \
%(event.widget.itemcget (self.target, "text")),
event.widget.itemcget (tk.CURRENT, "text"))
event.widget.itemconfigure (tk.CURRENT, fill =self.defaultcolor)
def dele(self,id):
canvas.delete(id)
CanvasDnD (root).pack()
root.mainloop()
Question: first I will select that rectangle with "Button 1" and then I will right-click and delete
Create the rectangles ...
canvas.create_rectangle(75, 75, 100, 100, tags="DnD")
canvas.create_rectangle(100, 100, 125, 125, tags="DnD")
Bind event "<ButtonPress-1>" to the Canvas
canvas.bind("<ButtonPress-1>", self.on_button_1)
Prepare the popup, to delete items with tag='DELETE'
self.popup.add_command(label="delete",
command=lambda: canvas.delete(canvas.find_withtag('DELETE')))
Define the event "<ButtonPress-1>" callback.
Here, the matching item get added tags='DELETE' and outlined 'red'.
def on_button_1(self, event):
iid = canvas.find_enclosed(event.x - 26, event.y - 26, event.x + 26, event.y + 26)
canvas.itemconfigure(iid, tags='DELETE', outline='red')
Related
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.
I'm making a tkinter gui and I want it to work like this:
I have a frame, inside the frame there's a canvas and inside the canvas there are multiple rectangles
I want to make it that once I hover over a rectangle it's color will change from white, to green
simple, right?
so help me figure out what's wrong
Here's the class:
class guiSong:
def __init__(self, master: tkinter.Canvas, songobject: SongFile, x, y, rect=None):
self.master = master
self.songobject = songobject
self.x = x
self.y = y
self.rect = rect
def on_enter(self, event):
self.master.itemconfig(self.rect, fill='green')
print("Should change to green rect ", str(self.rect))
def on_leave(self, enter):
self.master.itemconfig(self.rect, fill='white')
def display(self):
self.rect = self.master.create_rectangle(self.x, self.y, self.x + 1150, self.y + 150, fill='white', tags = ['playbutton',self.songobject])
print("Self Rect is "+str(self.rect)+"!!!!!!!!!!!!!!!!!!!!!!!")
self.master.tag_bind('playbutton',"<Enter>", self.on_enter)
self.master.tag_bind('playbutton',"<Leave>", self.on_leave)
self.albumimg = Image.open(BytesIO(self.songobject.albumimage))
self.albumimg = ImageOps.expand(self.albumimg,border=5)
self.albumimg = self.albumimg.resize((120, 120), Image.ANTIALIAS)
self.img = ImageTk.PhotoImage(self.albumimg)
make_image(self.img, self.x + 25, self.y + 15, self.master)
print(f"Creating image {str(self.img)} at x",self.x+25, " y ",self.y+15 )
return self.img
#self.master.create_image(self.x + 25, self.y + 15, anchor = tkinter.W,image=img)
Don't bother the whole songobject stuff that's unrelated
I made a list of those objects and displayed them all inside a canvas one after another
The expected output is that once I hover over a rectangle it'll turn green
what happens in reality is that only the last rectangle created is colored once hovering over any rectangle.
Maybe this can help you, it's a rectangle that becomes green when you hover it with mouse, red when you leave it, and blue if you click on it.
Note : for blue color, I made an example with an argument in the callback function.
import tkinter as tk
class GUI(tk.Tk):
def __init__(self):
super().__init__()
self.can = tk.Canvas(self, width=200, height=200)
self.can.pack()
self.rect = self.can.create_rectangle(50, 50, 100, 100, fill="gray")
self.can.tag_bind(self.rect, '<Enter>', self.green_rect)
self.can.tag_bind(self.rect, '<Leave>', self.red_rect)
self.can.tag_bind(self.rect, '<Button-1>', lambda x:self.color_rect("blue"))
def color_rect(self, color):
self.can.itemconfigure(self.rect, fill=color)
def green_rect(self, event=None):
self.can.itemconfigure(self.rect, fill="green")
def red_rect(self, event=None):
self.can.itemconfigure(self.rect, fill="red")
gui = GUI()
gui.mainloop()
Since each rectangle is an instance of guiSong, you can directly bind to the canvas item rather than to a tag.
Here's a simplified version of your class:
class guiSong:
def __init__(self, master, songobject, x, y):
self.master = master
tags = ("playbutton", songobject)
self.rect = master.create_rectangle(x,y,x+100, y+100, tags=tags, fill="white")
self.master.tag_bind(self.rect, "<Enter>", self.on_enter)
self.master.tag_bind(self.rect, "<Leave>", self.on_leave)
def on_enter(self, event):
self.master.itemconfigure(self.rect, fill="red")
def on_leave(self, event):
self.master.itemconfigure(self.rect, fill="white")
If you wish to bind to the tag, you can use the tag "current" to refer to the object that received the event.
class guiSong:
...
def on_enter(self, event):
self.master.itemconfigure("current", fill="red")
def on_leave(self, event):
self.master.itemconfigure("current", fill="white")
My problem is that when I consult an image from D:/Folder/my_drawing.jpg after "cleaning" the canvas, the canvas is dirty with the previous drawn images. The canvas is visually clear, but accumulates the former drawn image and the new one. The goal is make a Paint like program, that allows save draws and with a button that clean all the canvas.
The behaviour is as follow:
First I draw the curved line, after I clean the canvas, and after that, I draw the line, and when I consult the image, opening the file, the image is composed as shown below:
This is the code, in Python:
import os
from tkinter import *
from PIL import Image, ImageDraw
class Paint(object):
def __init__(self):
self.root = Tk()
self.pen_button = self.use_pen
self.save_button = Button(self.root, text='Save', command=self.Save)
self.save_button.grid(row=0, column=3)
self.eraser_button = Button(self.root, text='Clean canvas', command=self.use_eraser)
self.eraser_button.grid(row=0, column=1)
self.c = Canvas(self.root, bg='white', width=600, height=600)
self.c.grid(row=1, columnspan=5)
self.setup()
self.root.mainloop()
def activate_button(self, some_button):
self.active_button = some_button
def use_pen(self):
self.activate_button(self.pen_button)
def setup(self):
self.path=''
self.old_x = None
self.old_y = None
self.image1 = Image.new("RGB",(600,600),'white')
self.draw = ImageDraw.Draw(self.image1)
self.active_button = self.pen_button
self.c.bind('<B1-Motion>', self.paint)
self.c.bind('<ButtonRelease-1>', self.reset)
def use_eraser(self):
self.c.delete(ALL)
def Save(self):
self.c.postscript(file="my_drawing.jpg", colormode='color')
filename = "my_drawing.jpg"
self.image1.save(filename)
def paint(self, event):
self.line_width = 2.0
paint_color = 'black'
if self.old_x and self.old_y:
self.c.create_line(self.old_x, self.old_y, event.x, event.y,
width=self.line_width, fill=paint_color, dash=(),capstyle=ROUND, smooth=TRUE, splinesteps=36)
self.draw.line([self.old_x, self.old_y, event.x, event.y], fill="black", width=5)
self.old_x = event.x
self.old_y = event.y
def reset(self, event):
self.old_x, self.old_y = None, None
if __name__ == '__main__':
Paint()
Yes, well..., you never actually display the image on the canvas.
In the function paint() you first draw a line segmant on the canvas, then you draw it in the image.
The function use_eraser() clears the canvas but does nothing with the image self.image1.
So, don't draw anything on the canvas but only on the image, and then display the image on the canvas. I have not worked much with PIL but I should think the image display will update automatically as you draw on it.
I've solved it with this changes. I hope it serves to another with the same problem.
import io
def Save(self):
ps=self.c.postscript(colormode='color')
img = Image.open(io.BytesIO(ps.encode('utf-8')))
img.save('D:/Folder/my_drawing.jpg')
def paint(self, event):
self.line_width = 2.0
paint_color = self.color
if self.old_x and self.old_y:
self.c.create_line(self.old_x, self.old_y, event.x, event.y,
width=self.line_width, fill=paint_color, dash=(),
capstyle=ROUND, smooth=TRUE, splinesteps=36)
self.old_x = event.x
self.old_y = event.y
I wrote a class in python tkinter that you can use to create buttons that change their images on leave, enter and button-1 event.
The script:
from tkinter import *
class ImageButton:
def CreateButton(self, image, root):
# --- image setup ---
self.ImageName = image
self.ButtonRoot = root
self.normal = PhotoImage(file="Images/" + self.ImageName + "/normal.png")
self.hover = PhotoImage(file="Images/" + self.ImageName + "/hover.png")
self.clicked = PhotoImage(file="Images/" + self.ImageName + "/clicked.png")
# --- button setup ---
self.Button = Button(self.ButtonRoot, image=self.normal)
self.Button.bind("<Leave>", self.normal_event)
self.Button.bind("<Enter>", self.hover_event)
self.Button.bind("<Button-1>", self.click_event)
return self.Button
def normal_event(self, event):
self.Button.config(image=self.normal)
def hover_event(self, event):
self.Button.config(image=self.hover)
def click_event(self, event):
self.Button.config(image=self.clicked)
Root = Tk()
Image = ImageButton()
FirstButton = Image.CreateButton("scann_device", Root)
FirstButton.grid(row=0, column=0)
SecondButton = Image.CreateButton("get_device_info", Root)
SecondButton.grid(row=1, column=0)
Root.mainloop()
So if you create a button with that class, you can do anything you want. It only binds it to the events I mentioned above. If you create 1 button, it works and you see it on your window, but if you create 2 buttons and grid/pack them, it wont work. It looks like this. The second button also gets the hitbox of the first button.
You only initiate one instance of Image. Therefore, in your second call to CreateButton(), all class objects (self.normal, self.ImageName, self.Button) are overwritten with the new values.
You are probably better off inheriting a class from the tkinter Button and adding your own functions. See this example:
from tkinter import *
class ImageButton(Button):
def __init__(self, image, root, *args, **kwargs):
super().__init__(root, *args, **kwargs)
# --- image setup ---
self.image_name = image
self.normal = PhotoImage(file="Images/" + self.image_name + "/normal.png")
self.hover = PhotoImage(file="Images/" + self.image_name + "/hover.png")
self.clicked = PhotoImage(file="Images/" + self.image_name + "/clicked.png")
# --- button setup ---
self.config(image=self.normal)
self.bind("<Leave>", self.normal_event)
self.bind("<Enter>", self.hover_event)
self.bind("<Button-1>", self.click_event)
def normal_event(self, event):
self.config(image=self.normal)
def hover_event(self, event):
self.config(image=self.hover)
def click_event(self, event):
self.config(image=self.clicked)
root = Tk()
FirstButton = ImageButton("scann_device", root)
FirstButton.grid(row=0, column=0)
SecondButton = ImageButton("get_device_info", root)
SecondButton.grid(row=1, column=0)
root.mainloop()
The objective of my exercise is to create a game in which a falling ball needs to be caught by a bar at the bottom of the screen. Below code does not make the ball to fall automatically. I referred to below posts, but could not find a solution:
Tkinter bind to arc, Automatically Moving Shape? Python 3.5 Tkinter
import tkinter as tk
class Game(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.can = tk.Canvas(self, width=400, height=400)
self.can.pack(fill="both", expand=True)
self.ball = self.can.create_oval(40, 40, 60, 60, fill="red", tag="ball")
self.player = self.can.create_rectangle(300,345,350,360, fill="red")
self.bind("<Key>", self.move_player)
self.can.tag_bind("ball",self.move_b)
self.mainloop()
def move_b(self,event=None):
self.can.move(self.ball, 1, 0)
print(self.ball)
# move again after 25ms (0.025s)
self.can.after(25, self.move_b)
def move_player(self, event):
key = event.keysym
if key == "Left":
self.can.move(self.player, -20, 0)
elif key == "Right":
self.can.move(self.player, 20, 0)
if __name__ == '__main__':
Game()
2nd positional argument to tag_bind is an event, whereas in your code it's passed as the actual callback, self.move_b. First, add an event:
self.can.tag_bind("ball", "<ButtonRelease-1>", self.move_b)
If you don't want it to have event, simply pass None:
self.can.tag_bind("ball", None, self.move_b)
or don't use tag_bind at all, and simply call:
self.move_b()
Whenever you want the animation to start.