I want to draw a rectangle on the image I used this code :
import Tkinter as tk
class draw_rect(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.x = self.y = 0
self.canvas = tk.Canvas(self, width=400, height=400,cursor="cross")
self.canvas.pack(side="top", fill="both", expand=True)
self.canvas.bind("<ButtonPress-1>", self.on_button_press)
self.canvas.bind("<ButtonRelease-1>", self.on_button_release)
def on_button_press(self, event):
self.x = event.x
self.y = event.y
def on_button_release(self, event):
x0,y0 = (self.x, self.y)
x1,y1 = (event.x, event.y)
self.canvas.create_rectangle(x0,y0,x1,y1, fill="RED")
if __name__ == "__main__":
app = draw_rect()
app.mainloop()
the issue is it draws more than one rectangle, I want if I draw a new rectangle the previous one deleted.
one more thing I want to save the rectangle coordinate (x0,x1,y0,y1) that I can use it later.
Thank you.
You can use canvas.delete("all") to clear, and the coordinates are already saved in a variable.
def on_button_press(self, event):
self.x = event.x
self.y = event.y
self.canvas.delete("all")
def on_button_release(self, event):
x0,y0 = (self.x, self.y)
x1,y1 = (event.x, event.y)
self.canvas.create_rectangle(x0,y0,x1,y1, fill="RED")
print(x0,y0,x1,y1) # You can write to file, or store as lists or do w/e with these
Related
I have a solid-coloured ball moving in a tkinter canvas, using the move(), and update() method to refresh the canvas.
I want to keep track of the ball trajectory onscreen. i.e. - the ball moves to its new position but a line - series of points showing its trajectory - remains onscreen.
How do I do this? (two canvases, one of them transparent??)
Just as suggested, you can paint line that will start from the last position of the ball and end in the current new ball position.
I've made simple app doing so in tkinter, that you can modify to your needs.
It's using only one canvas and you can move ball just by clicking on the canvas. Right mouse click resets the canvas and ball position.
from tkinter import Tk, Canvas, Frame, BOTH
class MovingBall(Frame):
ball_r = 25
x, y = 0, 0 # ball last coords
def __init__(self):
super().__init__()
self.ball = None
self.initUI()
def initUI(self):
self.master.title("Moving Ball")
self.pack(fill=BOTH, expand=1)
self.canvas = Canvas(self)
self.ball = self.paint_ball(self.x, self.y)
self.canvas.pack(fill=BOTH, expand=1)
# Left click on canvas moves the ball
self.canvas.bind("<Button-1>", self.move)
# Right click on canvas reset ball position
self.canvas.bind("<Button-3>", self.reset)
def paint_ball(self, x, y):
return self.canvas.create_oval(x - self.ball_r, y - self.ball_r, x + self.ball_r, y + self.ball_r, fill="red",
outline="silver", width=1)
def paint_path(self, x, y):
return self.canvas.create_line(self.x, self.y, x, y, fill="silver", width=1)
def move(self, event):
# Remove last painted ball
self.canvas.delete(self.ball)
# Add new line to the path
self.paint_path(event.x, event.y)
# Paint new ball at new position
self.ball = self.paint_ball(event.x, event.y)
self.canvas.pack(fill=BOTH, expand=1)
# Store current ball coords
self.x, self.y = event.x, event.y
def reset(self, event):
"""Reset whole scene and put ball to 0,0"""
self.x, self.y = 0, 0
self.canvas.delete("all")
self.ball = self.paint_ball(self.x, self.y)
self.canvas.pack(fill=BOTH, expand=1)
def main():
root = Tk()
MovingBall()
root.geometry("1024x768")
root.mainloop()
if __name__ == '__main__':
main()
Here is my code but for some reason whenever I run it the ball only goes in one direction.
from tkinter import * import random import time
class Ball:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
self.canvas.move(self.id, 245, 100)
starts = [-3, -2, -1, 1, 2, 3]
random.shuffle(starts)
self.x = starts[0]
self.y = -3
self.canvas_height = self.canvas.winfo_height()
self.canvas_width = self.canvas.winfo_width()
def draw(self):
self.canvas.move(self.id, self.x, self.y)
pos = self.canvas.coords(self.id)
if pos[1] <= 0:
self.y = 1
if pos[3] >= self.canvas_height:
self.y = -1
if pos[0] <= 0:
self.x = 3
if pos[2] >= self.canvas_width:
self.x = -3
tk = Tk()
tk.title("Game")
tk.resizable(0, 0)
tk.wm_attributes("-topmost", 1)
canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0)
canvas.pack()
ball = Ball(canvas, 'red') while 1:
ball.draw()
tk.update_idletasks()
tk.update()
time.sleep(0.01)
Can someone please help me?
Shuffle is working properly but there is some problem with your logic due to which ball always go to the left side and in the upward direction. You need to revise that. Also you must use if and elif in draw method for both x and y to stop your ball going out of the frame instead of using if for all.
Feel free to ask question incase of any confusion.
In case you ever run into issue like this, try debugging or at least printing some crucial values to double-check if these in fact are correct (same as you assumed).
And so if you would print self.canvas_height and self.canvas_width values you would notice that these are incorrect (they always return 1).
Solution to your problem is to change these lines in your init function:
self.canvas_height = self.canvas.winfo_height()
self.canvas_width = self.canvas.winfo_width()
to these lines:
self.canvas_height = self.canvas.winfo_reqheight()
self.canvas_width = self.canvas.winfo_reqwidth()
So, yeah I'm a beginner to Tkinter Canvas Games and it's being too difficult for me to code than I thought.
Okk, let's come to the chase, below is my code:
import time
from tkinter import *
from numpy import random
class Paddle:
def __init__(self, Main_Frame):
Main_Frame.title("Paddle")
Main_Frame.geometry("300x300+630+150")
self.Random_X = random.randint(270)
self.Random_Y = random.randint(120)
self.x = 3
self.y = 3
self.Y_Position = 288
self.Can = Canvas(Main_Frame, height = 300, width = 300)
self.Can.pack()
self.paddle = self.Can.create_rectangle(0, 288, 90, 300, fill = "Aqua", outline = "Aqua")
self.ball = self.Can.create_oval(self.Random_X, self.Random_Y, self.Random_X + 20, self.Random_Y + 20, outline = "Red", fill = "Red")
self.Can.bind("<Motion>", self.Move_Paddle)
self.Move_Ball()
def Move_Ball(self):
Ball_x1, Ball_y1, Ball_x2, Ball_y2 = self.Can.coords(self.ball)
if Ball_x2 > 300:
self.x = -self.x
if Ball_y2 > 300:
self.y = -self.y
if Ball_x1 < 0:
self.x = -self.x
if Ball_y2 < 10:
self.y = -self.y
self.Can.moveto(self.ball, self.x, self.y)
Window.after(1, self.Move_Ball)
def Move_Paddle(self, event):
self.X_Position = event.x
self.Can.moveto(self.paddle, self.X_Position, self.Y_Position)
Window = Tk()
Class = Paddle(Window)
Window.mainloop()
So, my problem here is that even after calling the Move_Ball() function, it had no effect on the ball, (okk there is no error occurring though) when I tried to call the function under Move_Paddle() function (before adding the Window.after(1, self.Move_Ball) line), it worked well when I moved the mouse around the Canvas, well, I want it to automatically activate when the Paddle class is called.
I was trying to check the problem in the code by placing the function call in Move_Paddel() (with the Window.after(1, self.Move_Ball) line as in the code), it increased the trouble and insanely increased the speed of the ball.
If anyone could help me with these couple of problems, it would be highly appreciated.
Thanks in advance!
Use move() instead of moveto() method. move() method moves each of the items given by tagOrId in the canvas coordinate space by adding X amount to the x-coordinate of each point associated with the item and y amount to the y-coordinate of each point associated with the item.
Then you may increase the delay in the after() method.
Here is the working code:
import time
from tkinter import *
from numpy import random
class Paddle:
def __init__(self, Main_Frame):
Main_Frame.title("Paddle")
Main_Frame.geometry("300x300+630+150")
self.Random_X = random.randint(270)
self.Random_Y = random.randint(120)
self.x = 3
self.y = 3
self.Y_Position = 288
self.Can = Canvas(Main_Frame, height = 300, width = 300)
self.Can.pack()
self.paddle = self.Can.create_rectangle(0, 288, 90, 300, fill = "Aqua", outline = "Aqua")
self.ball = self.Can.create_oval(self.Random_X, self.Random_Y, self.Random_X + 20, self.Random_Y + 20, outline = "Red", fill = "Red")
self.Can.bind("<Motion>", self.Move_Paddle)
self.Move_Ball()
def Move_Ball(self):
Ball_x1, Ball_y1, Ball_x2, Ball_y2 = self.Can.coords(self.ball)
if Ball_x2 > 300:
self.x = -self.x
if Ball_y2 > 300:
self.y = -self.y
if Ball_x1 < 0:
self.x = -self.x
if Ball_y2 < 10:
self.y = -self.y
self.Can.move(self.ball, self.x, self.y)
Window.after(50 ,self.Move_Ball)
def Move_Paddle(self, event):
self.X_Position = event.x
self.Can.moveto(self.paddle, self.X_Position, self.Y_Position)
Window = Tk()
Class = Paddle(Window)
Window.mainloop()
I rewrote your code and now I think it works as you wished. Red ball slowly goes down:
import time
from tkinter import *
from numpy import random
class Paddle:
def __init__(self, main_frame):
self.root = main_frame
random.seed(int(time.time()))
self.x = random.randint(270)
self.y = random.randint(120)
self.Y_Position = 288
self.Can = Canvas(self.root, height=300, width=300)
self.Can.pack()
self.paddle = self.Can.create_rectangle(0, 288, 90, 300, fill="Aqua", outline="Aqua")
self.ball = self.Can.create_oval(self.x, self.y, self.x + 20, self.y + 20, outline="Red", fill="Red")
self.Can.bind("<Motion>", self.move_paddle)
self.move_ball()
def move_ball(self):
Ball_x1, Ball_y1, Ball_x2, Ball_y2 = self.Can.coords(self.ball)
# if Ball_x2 > 300:
# self.x = -self.x
# if Ball_y2 > 300:
# self.y = -self.y
# if Ball_x2 < 0:
# self.x = 300
if Ball_y2 > 300:
self.y = 0
self.y += 0.1
self.Can.moveto(self.ball, self.x, self.y)
self.root.after(1, self.move_ball)
def move_paddle(self, event):
self.X_Position = event.x
self.Can.moveto(self.paddle, self.X_Position, self.Y_Position)
root = Tk()
root.title("Paddle")
root.geometry("300x300+630+150")
Class = Paddle(root)
root.mainloop()
And please read about PEP8
You should use move() instead of moveto() inside Move_Ball() and after(1, ...) is too frequent so the ball will be moved very fast. Try after(50, ...) instead:
def Move_Ball(self):
...
self.Can.move(self.ball, self.x, self.y)
Window.after(50, self.Move_Ball)
I am trying to code a simple image editor like paint.
I implemented drawing lines rectangles and ellipses.
what i want is to see an animation (a foreshadowing ?) of how the rectangle will look like, just like in paint when you draw a shape you can see what it actually looks like without really drawing on the canvas.
here is a shortened version of the code
class Canvas(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.initLogic()
def initUI(self):
self.image = QImage(self.size(), QImage.Format_RGB32)
self.image.fill(Qt.white)
def initLogic(self):
self.brushSize = 1
self.brushStyle = Qt.SolidLine
self.brushColor = QColor(0, 0, 0)
self.shapeMode = None
self.drawing = False
self.mousePointer = None
def mousePressEvent(self, event):
self.drawing = True
self.mousePointer = event.pos()
def mouseMoveEvent(self, event):
#if no pen mode set draw lines from event to event
if self.drawing:
painter = QPainter(self.image)
painter.setPen(QPen(self.brushColor,
self.brushSize,
self.brushStyle))
shape = None #i try later to assign the method Qpainter.draw<someShape> to this variable
# hoping it works like in tkinter.
if self.shapeMode == None:#free shape
painter.drawLine(self.mousePointer, event.pos())
self.mousePointer = event.pos()
else:
#previous x and previous y
ox, oy = self.mousePointer.x(), self.mousePointer.y()
#current x and current y
dx, dy = event.pos().x(), event.pos().y()
width, height = dx - ox, dy - oy
#self.shapeMode is a string corresponding to a QPainter method
#we get the corresponding method using getattr builtin function
drawMethod = getattr(painter, self.shapeMode)# = painter.someFunc this works fine
shape = drawMethod(ox, oy, width, height) #assigning the method call to a variable
self.update()
if shape != None:
painter.eraseRect(shape)
"""
if self.drawing and self.shapeMode == None:
painter = QPainter(self.image)
painter.setPen(QPen(self.brushColor,
self.brushSize,
self.brushStyle))
painter.drawLine(self.mousePointer, event.pos())
self.mousePointer = event.pos()
self.update()"""
#otherwise if pen mode set draw shape at event then delete until release
def mouseReleaseEvent(self, event):
if self.shapeMode != None:
painter = QPainter(self.image)
painter.setPen(QPen(self.brushColor,
self.brushSize,
self.brushStyle))
#previous x and previous y
ox, oy = self.mousePointer.x(), self.mousePointer.y()
#current x and current y
dx, dy = event.pos().x(), event.pos().y()
width, height = dx - ox, dy - oy
#self.shapeMode is a string corresponding to a QPainter methid
#we get the corresponding method using getattr builtin function
drawMethod = getattr(painter, self.shapeMode)# = painter.someFunc
shape = drawMethod(ox, oy, width, height)
self.update()
self.mousePointer = event.pos()
self.drawing = False
#TODO end registering the action
def paintEvent(self, event):
widgetPainter = QPainter(self)
widgetPainter.drawImage(self.rect(), self.image, self.rect())
the canvas keeps drawing rectangles as long as i hold the mouse, what i want is the rectangle to resize and only definitively be drawn after mouse release.
You can achieve this by taking advantage of which paint device to pass to QPainter. During mouseMoveEvent keep a reference to the points and sizes calculated so in paintEvent you can draw onto the main widget. This way anything painted will only last until the next update. Then in mouseReleaseEvent you can paint on the QImage to permanently draw the rectangle.
class Canvas(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.initLogic()
def initUI(self):
self.image = QImage(self.size(), QImage.Format_RGB32)
self.image.fill(Qt.white)
def initLogic(self):
self.brushSize = 1
self.brushStyle = Qt.SolidLine
self.brushColor = QColor(0, 0, 0)
self.shapeMode = 'drawRect'
self.temp_rect = QRect()
self.drawing = False
self.mousePointer = None
def mousePressEvent(self, event):
self.drawing = True
self.mousePointer = event.pos()
def mouseMoveEvent(self, event):
#if no pen mode set draw lines from event to event
if self.drawing:
painter = QPainter(self.image)
painter.setPen(QPen(self.brushColor,
self.brushSize,
self.brushStyle))
if self.shapeMode == None:#free shape
painter.drawLine(self.mousePointer, event.pos())
self.mousePointer = event.pos()
else:
#previous x and previous y
ox, oy = self.mousePointer.x(), self.mousePointer.y()
#current x and current y
dx, dy = event.pos().x(), event.pos().y()
width, height = dx - ox, dy - oy
self.temp_rect = QRect(ox, oy, width, height)
self.update()
def mouseReleaseEvent(self, event):
if self.shapeMode != None:
painter = QPainter(self.image)
painter.setPen(QPen(self.brushColor,
self.brushSize,
self.brushStyle))
#self.shapeMode is a string corresponding to a QPainter methid
#we get the corresponding method using getattr builtin function
drawMethod = getattr(painter, self.shapeMode)# = painter.someFunc
drawMethod(self.temp_rect)
self.update()
self.mousePointer = event.pos()
self.drawing = False
#TODO end registering the action
def paintEvent(self, event):
widgetPainter = QPainter(self)
widgetPainter.drawImage(self.rect(), self.image, self.rect())
if self.drawing:
drawMethod = getattr(widgetPainter, self.shapeMode)
drawMethod(self.temp_rect)
Outcome:
I've updated the initial script with a modified version of Bryan-Oakley's answer. It now has 2 canvas, 1 with the draggable rectangle, and 1 with the plot. I would like the rectangle to be dragged along the x-axis on the plot if that is possible?
import tkinter as tk # python 3
# import Tkinter as tk # python 2
import numpy as np
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
# Implement the default Matplotlib key bindings.
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
class Example(tk.Frame):
"""Illustrate how to drag items on a Tkinter canvas"""
def __init__(self, parent):
tk.Frame.__init__(self, parent)
fig = Figure(figsize=(5, 4), dpi=100)
t = np.arange(0, 3, .01)
fig.add_subplot(111).plot(t, 2 * np.sin(2 * np.pi * t))
#create a canvas
self.canvas = tk.Canvas(width=200, height=300)
self.canvas.pack(fill="both", expand=True)
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
canvas.draw()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
# this data is used to keep track of an
# item being dragged
self._drag_data = {"x": 0, "y": 0, "item": None}
# create a movable object
self.create_token(100, 150, "black")
# add bindings for clicking, dragging and releasing over
# any object with the "token" tag
self.canvas.tag_bind("token", "<ButtonPress-1>", self.drag_start)
self.canvas.tag_bind("token", "<ButtonRelease-1>", self.drag_stop)
self.canvas.tag_bind("token", "<B1-Motion>", self.drag)
def create_token(self, x, y, color):
"""Create a token at the given coordinate in the given color"""
self.canvas.create_rectangle(
x - 5,
y - 100,
x + 5,
y + 100,
outline=color,
fill=color,
tags=("token",),
)
def drag_start(self, event):
"""Begining drag of an object"""
# record the item and its location
self._drag_data["item"] = self.canvas.find_closest(event.x, event.y)[0]
self._drag_data["x"] = event.x
self._drag_data["y"] = event.y
def drag_stop(self, event):
"""End drag of an object"""
# reset the drag information
self._drag_data["item"] = None
self._drag_data["x"] = 0
self._drag_data["y"] = 0
def drag(self, event):
"""Handle dragging of an object"""
# compute how much the mouse has moved
delta_x = event.x - self._drag_data["x"]
delta_y = 0
# move the object the appropriate amount
self.canvas.move(self._drag_data["item"], delta_x, delta_y)
# record the new position
self._drag_data["x"] = event.x
self._drag_data["y"] = event.y
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
Any help is greatly appreciated.
The issue with your code is that you create two canvases, one for the matplotlib figure and one for the draggable rectangle while you want both on the same.
To solve this, I merged the current code of the question with the one before the edit, so the whole matplotlib figure is now embedded in the Tkinter window. The key modification I made to the DraggableLine class is that it now takes the canvas as an argument.
import tkinter as tk # python 3
# import Tkinter as tk # python 2
import numpy as np
import matplotlib.lines as lines
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.figure import Figure
class DraggableLine:
def __init__(self, ax, canvas, XorY):
self.ax = ax
self.c = canvas
self.XorY = XorY
x = [XorY, XorY]
y = [-2, 2]
self.line = lines.Line2D(x, y, color='red', picker=5)
self.ax.add_line(self.line)
self.c.draw_idle()
self.sid = self.c.mpl_connect('pick_event', self.clickonline)
def clickonline(self, event):
if event.artist == self.line:
self.follower = self.c.mpl_connect("motion_notify_event", self.followmouse)
self.releaser = self.c.mpl_connect("button_press_event", self.releaseonclick)
def followmouse(self, event):
self.line.set_xdata([event.xdata, event.xdata])
self.c.draw_idle()
def releaseonclick(self, event):
self.XorY = self.line.get_xdata()[0]
self.c.mpl_disconnect(self.releaser)
self.c.mpl_disconnect(self.follower)
class Example(tk.Frame):
"""Illustrate how to drag items on a Tkinter canvas"""
def __init__(self, parent):
tk.Frame.__init__(self, parent)
fig = Figure(figsize=(5, 4), dpi=100)
t = np.arange(0, 3, .01)
ax = fig.add_subplot(111)
ax.plot(t, 2 * np.sin(2 * np.pi * t))
# create the canvas
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
canvas.draw()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.line = DraggableLine(ax, canvas, 0.1)
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
I'm not sure how to do it with tkinter or pyQt but I know how to make something like this with PyGame which is another GUI solution for python. I hope this example helps you:
import pygame
SCREEN_WIDTH = 430
SCREEN_HEIGHT = 410
WHITE = (255, 255, 255)
RED = (255, 0, 0)
FPS = 30
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
rectangle = pygame.rect.Rect(176, 134, 17, 170)
rectangle_draging = False
clock = pygame.time.Clock()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
if rectangle.collidepoint(event.pos):
rectangle_draging = True
mouse_x, mouse_y = event.pos
offset_x = rectangle.x - mouse_x
offset_y = rectangle.y - mouse_y
elif event.type == pygame.MOUSEBUTTONUP:
if event.button == 1:
rectangle_draging = False
print("Line is at: (", rectangle.x, ";", rectangle.y,")")
elif event.type == pygame.MOUSEMOTION:
if rectangle_draging:
mouse_x, mouse_y = event.pos
rectangle.x = mouse_x + offset_x
rectangle.y = mouse_y + offset_y
screen.fill(WHITE)
pygame.draw.rect(screen, RED, rectangle)
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
And when you move the line around, the console prints the location that it is in.