I have this bit of code here:
from tkinter import *
class player():
def __init__(self, radius, xcoordinate = 0, ycoordinate = 0):
self.xcoordinate = xcoordinate
self.ycoordinate = ycoordinate
self.radius = radius
def moveRight(self, event):
self.xcoordinate += 25
self.draw()
print("Right key pressed")
print("x: " + str(self.xcoordinate))
def moveLeft(self, event):
self.ycoordinate += 25
self.draw()
print("Left key pressed")
print("y: " + str(self.ycoordinate))
def draw(self):
world = client()
world.title("World")
world.bind('<Right>', self.moveRight)
world.bind('<Left>', self.moveLeft)
canvas = Canvas(world, width=200, height=200, borderwidth=0,highlightthickness=0, bg="black")
canvas.grid()
canvas.draw_player(self.xcoordinate, self.ycoordinate, self.radius, fill="blue", width=4)
world.mainloop()
class client(Tk):
def __init__(self):
super().__init__()
def draw_player(self, x, y, r, **kwargs):
return self.create_oval(x-r, y-r, x+r, y+r, **kwargs)
Canvas.draw_player = draw_player
p1 = player(50)
p1.draw()
The problem is that whenever I press the right or left arrow keys, it calls the draw() method. The draw() method constructs a new client object and etc.
So you end up opening multiple windows each with a circle with different x and y coordinates. How do I make this such that when I call draw() it only edits the x and y coordinates and redraws the circle on the same window?
Please no suggestions to use pygame, my IDE gets errors when I try to import the module.
The code can be simplified by passing the move parameters to a single function.
from tkinter import *
from functools import partial
class player():
def __init__(self, master, radius, xcoordinate=100, ycoordinate=100):
self.master=master
self.xcoordinate = xcoordinate
self.ycoordinate = ycoordinate
self.radius = radius
self.master.title("World")
self.master.bind('<Right>', partial(self.move_oval, 25, 0))
self.master.bind('<Left>', partial(self.move_oval, -25, 0))
self.master.bind('<Up>', partial(self.move_oval, 0, -25))
self.master.bind('<Down>', partial(self.move_oval, 0, 25))
self.draw() ## called once
def move_oval(self, x, y, event):
self.canvas.move(self.oval_id, x, y)
print("key pressed", x, y)
def draw(self):
self.canvas = Canvas(self.master, width=200, height=200,
borderwidth=0,highlightthickness=0, bg="black")
self.canvas.grid()
self.oval_id=self.canvas.create_oval(self.xcoordinate-self.radius,
self.ycoordinate-self.radius,
self.xcoordinate+self.radius,
self.ycoordinate+self.radius,
fill="red")
master=Tk()
p1 = player(master, 50)
master.mainloop()
I assume that
redraws the circle on the same window
means moving the circle, and does not mean drawing a second circle in the same space. Use the move() function for a canvas object to do this http://effbot.org/tkinterbook/canvas.htm . Note that you have to save a reference to the object to be moved. Also your moveLeft() function doesn't.
class Player():
def __init__(self, master, radius, xcoordinate=100, ycoordinate=100):
self.master=master
self.xcoordinate = xcoordinate
self.ycoordinate = ycoordinate
self.radius = radius
self.master.title("World")
self.master.bind('<Right>', self.moveRight)
self.master.bind('<Left>', self.moveLeft)
self.draw() ## called once
def moveRight(self, event):
self.canvas.move(self.oval_id, 25, 0)
print("Right key pressed")
print("x: " + str(self.xcoordinate))
def moveLeft(self, event):
self.canvas.move(self.oval_id, 0, 25)
print("Left key pressed")
print("y: " + str(self.ycoordinate))
def draw(self):
self.canvas = Canvas(self.master, width=200, height=200,
borderwidth=0,highlightthickness=0, bg="black")
self.canvas.grid()
self.oval_id=self.canvas.create_oval(self.xcoordinate-self.radius,
self.ycoordinate-self.radius,
self.xcoordinate+self.radius,
self.ycoordinate+self.radius,
fill="red")
master=Tk()
p1 = Player(master, 50)
master.mainloop()
Related
I'm strugling with canvas.move in a simulation of a celestial system. How can I simultaneously move multiple objects in a defined Space instance? I guess I have to do something with the identity of the body objects. But I cannot find it out. Perhaps I should use repeating draw en delete methods in stead of canvas.move? See a simplified version of the coden below. Does some body has a suggestion? Many thanks
import tkinter as tk
class Space(tk.Frame):
def __init__(self, master, size, bg=None):
super().__init__(master)
frame = tk.Frame(master, border = 5)
frame.pack()
self.width, self.height = size[0], size[1]
self.canvas = tk.Canvas(frame,
width = self.width,
height = self.height,
borderwidth= 0,
highlightthickness= 0,
bg=bg)
self.canvas.pack()
def place_body(self, body):
x1, y1 = body.loc[0], body.loc[1]
x2, y2 = x1+body.size, y1+body.size
self.canvas.create_oval(x1,y1,x2,y2, fill=body.color)
def distance_step(self):
pass
def move_body(self, body):
# in stead of distance_step:
dx, dy = body.speed[0], body.speed[1]
self.canvas.move(body, dx, dy)
self.canvas.after(1, lambda: self.move_body(body))
class CelestialBody:
def __init__(self, name, size, mass, loc, speed, color="white"):
self.name = name
self.size = size
self.mass = mass
self.loc = loc
self.speed = speed
self.color = color
def __repr__(self):
return f"{self.name}"
class App:
def __init__(self):
x, y = 1000, 800
size = (x,y)
space = Space(root,size, bg = 'black')
sun1_size = 30
sun1_mass = 10
sun1_loc = (700, 450)
sun1_speed = (-200,0)
sun2_size = 30
sun2_mass = 10
sun2_loc = (300, 350)
sun2_speed = (200,0)
sun1 = CelestialBody("sun1", sun1_size, sun1_mass, sun1_loc, sun1_speed, color = "yellow")
sun2 = CelestialBody("sun2", sun2_size, sun2_mass, sun2_loc, sun2_speed, color ="yellow")
space.place_body(sun1)
space.place_body(sun2)
space.move_body(sun1)
space.move_body(sun2)
print(sun1, sun2)
root.mainloop()
root = tk.Tk()
root.title('UNIVERSE')
app = App()```
You need to keep track of the tag returned from canvas.create_oval(). See .tk_tag below. I had to slow down your speeds because the object immediately left the screen. Also note: instead of dx, dy = body.speed[0], body.speed[1], you can just do dx, dy = body.speed.
import tkinter as tk
class Space(tk.Frame):
def __init__(self, master, size, bg=None):
super().__init__(master)
frame = tk.Frame(master, border=5)
frame.pack()
self.width, self.height = size
self.canvas = tk.Canvas(frame,
width=self.width,
height=self.height,
borderwidth=0,
highlightthickness=0,
bg=bg)
self.canvas.pack()
def place_body(self, body):
x1, y1 = body.loc
x2, y2 = x1 + body.size, y1 + body.size
body.tk_tag = self.canvas.create_oval(x1, y1, x2, y2, fill=body.color)
def distance_step(self):
pass
def move_body(self, body):
# in stead of distance_step:
dx, dy = body.speed
dx, dy = dx/100, dy/100
self.canvas.move(body.tk_tag, dx, dy)
self.canvas.after(1, lambda: self.move_body(body))
class CelestialBody:
def __init__(self, name, size, mass, loc, speed, color="white"):
self.name = name
self.size = size
self.mass = mass
self.loc = loc
self.speed = speed
self.color = color
self.tk_tag = None
def __repr__(self):
return f"{self.name}"
class App:
def __init__(self):
x, y = 1000, 800
size = (x, y)
space = Space(root, size, bg='black')
sun1_size = 30
sun1_mass = 10
sun1_loc = (700, 450)
sun1_speed = (-200, 0)
sun2_size = 30
sun2_mass = 10
sun2_loc = (300, 350)
sun2_speed = (200, 0)
sun1 = CelestialBody("sun1", sun1_size, sun1_mass, sun1_loc, sun1_speed, color="yellow")
sun2 = CelestialBody("sun2", sun2_size, sun2_mass, sun2_loc, sun2_speed, color="yellow")
space.place_body(sun1)
space.place_body(sun2)
space.move_body(sun1)
space.move_body(sun2)
print(sun1, sun2)
root.mainloop()
root = tk.Tk()
root.title('UNIVERSE')
app = App()
I want to have many colored dots constantly move across a background. (Description of code below): A PolkaDot widget is constructed with a random color, size, position, and duration. The QPropertyAnimation moves the widget across the screen from left to right, restarting at a new height when the animation ends. 100 PolkaDot widgets are constructed in the Background widget, which is enough to make it appear like tons of new dots are constantly rushing in from the left side of the screen.
However, the 100 property animations seem to consume a lot of CPU power, causing it to slow down and look un-smooth. Is there another way to achieve a similar result? Try running the code below.
import sys, random
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
colors = [QColor('#f00'), QColor('#00f'), QColor('#0f0'), QColor('#ff0'),
QColor('#fff'), QColor('#ff6000'), QColor('#6b00ff'), QColor('#f0f')]
class PolkaDot(QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setFixedSize(50, 50)
self.color = random.choice(colors)
self.r = random.randrange(5, 22)
self.move(random.randrange(w), random.randrange(h))
self.anim = QPropertyAnimation(self, b'pos')
self.anim.finished.connect(self.run)
self.anim.setDuration(random.randrange(3000, 9000))
self.anim.setStartValue(QPoint(self.x() - (w + 60), self.y()))
self.anim.setEndValue(QPoint(w + 60, self.y()))
self.anim.start()
def paintEvent(self, event):
qp = QPainter(self)
qp.setRenderHint(QPainter.Antialiasing)
qp.setBrush(self.color)
qp.setPen(QPen(self.color.darker(130), self.r / 5))
qp.drawEllipse(QPoint(25, 25), self.r, self.r)
def run(self):
y = random.randrange(h)
self.anim.setDuration(random.randrange(3000, 9000))
self.anim.setStartValue(QPoint(-60, y))
self.anim.setEndValue(QPoint(w + 60, y + random.randrange(-50, 50)))
self.anim.start()
class Background(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(window)
polka_dots = [PolkaDot(self) for i in range(100)]
self.setStyleSheet('background-color: #000')
if __name__ == '__main__':
app = QApplication(sys.argv)
window = QDesktopWidget().availableGeometry()
w, h = window.width(), window.height()
gui = Background()
gui.show()
sys.exit(app.exec_())
I sort of found a solution. For some reason, creating the dots in the background widget's paint event and calling repaint() on QTimer.timeout() to update the XY coordinates is much more efficient than using QPropertyAnimation.
tick = 24
class Dot(object):
def __init__(self):
self.x = random.randrange(-w - 60, 0)
self.randomize()
def randomize(self):
self.color = random.choice(colors)
self.r = random.randrange(5, 22)
self.y = random.randrange(h)
self.x_speed = random.randrange(3000, 9000)
self.y_speed = random.randrange(-50, 50)
def move(self):
self.x += w * tick / self.x_speed
self.y += self.y_speed * tick / self.x_speed
if self.x > w:
self.x = -60
self.randomize()
class Background(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(window)
self.setStyleSheet('background-color: #000')
self.dots = [Dot() for i in range(150)]
self.timer = QTimer()
self.timer.setInterval(tick)
self.timer.timeout.connect(self.animate)
self.timer.start()
def paintEvent(self, event):
qp = QPainter(self)
qp.setRenderHint(QPainter.Antialiasing)
for dot in self.dots:
qp.setBrush(dot.color)
qp.setPen(QPen(dot.color.darker(130), dot.r / 5))
qp.drawEllipse(QPoint(dot.x, dot.y), dot.r, dot.r)
def animate(self):
for d in self.dots:
d.move()
self.repaint()
I'm working on a simulation in which some cubes of the same class are moving randomly. My aim is to give them another moving pattern, when they fulfill some characteristics (for example their object number).
My Problem:
If they fulfill the characteristics, how can I "switch off" the first moving pattern and activate the next?
Here a strongly simplified example of the simulation, and how it doesn't work:
from tkinter import *
from random import *
class Cubes:
def __init__(self, master, canvas, number, x1, y1, color):
self.master = master
self.canvas = canvas
self.number = number
self.x1 = x1
self.y1 = y1
self.x2 = x1 + 15
self.y2 = y1 + 15
self.color = color
self.rectangle = canvas.create_rectangle(x1, y1, self.x2, self.y2, fill=color)
def movement(self):
self.x = randint(-10, 10)
self.y = randint(-10, 10)
canvas.move(self.rectangle, self.x, self.y)
if self.number == 2:
def movementII(self):
canvas.move(self.rectangle, 0, 0)
self.canvas.after(100, self.movementII)
self.canvas.after(100, self.movement)
if __name__ == "__main__":
master = Tk()
canvas = Canvas(master, width=900, height=600)
canvas.pack()
master.title("Simulation")
cube = Cubes(master, canvas, 2, randint(50, 800), randint(25, 500), "black")
cube.movement()
mainloop()
how can I "switch off" the first moving pattern and activate the next?
When you call after, it returns a unique identifier. You can save that identifier and then later pass it to after_cancel to cancel the job if it hasn't already run.
I'm not entirely clear what you want, but if you want to turn off the old movement when you switch to the new, it would look something like this:
class Cubes:
def __init__(self, master, canvas, number, x1, y1, color):
...
self.after_id = None
...
def cancel(self):
if self.after_id is not None:
self.after_cancel(self.after_id)
self.after_id = None
def movement(self):
self.x = randint(-10, 10)
self.y = randint(-10, 10)
canvas.move(self.rectangle, self.x, self.y)
if self.number == 2:
def movementII(self):
canvas.move(self.rectangle, 0, 0)
self.cancel()
self.after_id = self.canvas.after(100, self.movementII)
self.after_id = self.canvas.after(100, self.movement)
A better way might be to have a single method that you call with after, and it simply calls the appropriate method.
For example, something like this:
def move(self):
if self.number == 1:
self.movement_1()
elif self.number == 2:
self.movement_2()
self.canvas.after(100, self.move)
def movement_1(self):
self.x = randint(-10, 10)
self.y = randint(-10, 10)
canvas.move(self.rectangle, self.x, self.y)
def movement_2(self):
canvas.move(self.rectangle, 0, 0)
Then, to switch the movement method, just change self.number and it will automatically be called at the appropriate time.
So I've made a "game" that has 2 balls a green ball and a red one you can move the red ball around but when it collides with the green ball I want it to display a success message by printing in the console! Heres my code.
__author__ = 'Zac'
from Tkinter import *
from random import randint
class Application:
def circle(self, r, x, y):
return (x-r, y-r, x+r, y+r)
def square(self, s, x, y):
return (x, y, s, s)
def __init__(self, canvas, r, x, y, **kwargs):
self.canvas = canvas
self.r = r
self.x = x
self.y = y
self.ball = canvas.create_oval(self.circle(r, x, y), **kwargs)
root = Tk()
canvas = Canvas(root, width = 1000, height = 1000)
canvas.pack()
ball1 = Application(canvas, 20, 50, 50, fill='red')
ball2 = Application(canvas, 30, 200, 250, fill='green')
def forward(event):
canvas.delete(ball1.ball)
ball1.y -= 5
ball1.ball = canvas.create_oval(ball1.circle(ball1.r, ball1.x, ball1.y), fill='red')
def backward(event):
canvas.delete(ball1.ball)
ball1.y += 5
ball1.ball = canvas.create_oval(ball1.circle(ball1.r, ball1.x, ball1.y), fill='red')
def left(event):
canvas.delete(ball1.ball)
ball1.x -= 5
ball1.ball = canvas.create_oval(ball1.circle(ball1.r, ball1.x, ball1.y), fill='red')
def right(event):
canvas.delete(ball1.ball)
ball1.x += 5
ball1.ball = canvas.create_oval(ball1.circle(ball1.r, ball1.x, ball1.y), fill='red')
root.bind('<w>', forward)
root.bind('<s>', backward)
root.bind('<a>', left)
root.bind('<d>', right)
root.mainloop()
Ok so heres how I did it!
I added this function
def collide():
x_diff = abs(ball1.x - ball2.x)
y_diff = abs(ball1.y - ball2.y)
if x_diff <= 49:
if y_diff <= 49:
print "COLLIDED"
And everytime you run the function to move the collision function is called and if there touching it prints "Collided"
Thx to #CurlyJoe for the code for checking the distance between them!
I tried to use the Tkinter library for my small project in python. I create a 500 by 500 square with 10000 small square in it.
And I want each small square turns black when user click on it. Can someone please tell me why, I would really appreciate it. Here is the graphics code:
from Tkinter import *
from button import *
class AppFrame(Frame):
def __init__(self):
self.root = Tk()
self.root.geometry = ("1000x1000")
self.f = Frame(self.root, relief = 'sunken', width = 600, height = 600)
self.w = Canvas(self.f,width = 505, height =505)
##get the x, y value whenever the user make a mouse click
self.w.bind("<Button-1>", self.xy)
self.bolist = []
for k in range(1,101):
for i in range(1, 101):
button = Buttons(self.w, i * 5, k * 5, i * 5 + 5, k * 5 + 5)
self.bolist.append(button)
self.f.grid(column =0, columnspan = 4)
self.w.grid(column = 0)
self.root.mainloop()
def xy (self, event):
self.x, self.y = event.x, event.y
print (self.x, self.y)
##check each button if it's clicked
for hb in self.bolist:
if hb.clicked(self.x, self.y):
print ("hurry")
hb.activate()
And
##button.py
from Tkinter import *
class Buttons:
def __init__(self,canvas,bx,by,tx,ty):
self.canvas = canvas
self.rec = canvas.create_rectangle((bx,by,tx,ty),fill = "lightgray",
activefill= 'black', outline = 'lightgray')
self.xmin = bx
self.xmax = tx
self.ymin = by
self.ymax = ty
##print (bx, by, tx, ty)
def clicked(self, px, py):
return (self.active and self.xmin <= px <= self.xmax and
self.ymin <= py <= self.ymax)
def activate(self):
self.canvas.itemconfigure(slef.rec, fill = 'black')
self.active = True
The problem is that you don't initialize the active attribute, so it doesn't exist until the cell becomes active. To fix that, add self.active = False inside the __init__ method of Buttons.
You also have a typo in this line (notice you use slef rather than self):
self.canvas.itemconfigure(slef.rec, fill = 'black')
Instead of a global binding on the canvas, it would be more efficient to set a binding on each individual rectangle. You can then use the binding to pass the instance of the Buttons class to the callback. This way you don't have to iterate over several thousand widgets looking for the one that was clicked on.
To do this, use the tag_bind method of the canvas. You can make it so that your main program passes in a reference to a function to call when the rectangle is clicked, then the binding can call that method and pass it a reference to itself.
For example:
class Buttons:
def __init__(self,canvas,bx,by,tx,ty, callback):
...
self.rec = canvas.create_rectangle(...)
self.canvas.tag_bind(self.rec, "<1>",
lambda event: callback(self))
...
class AppFrame(Frame):
def __init__(...):
...
button = Buttons(..., self.callback)
...
def callback(self, b):
b.activate()
Here, I looked at your code, debugged it, and made some adjustments. It works now.
Just keep both the scripts in one folder and run your AppFrame script (the second one in this answer)
##button.py
from Tkinter import *
class Buttons:
def __init__(self,canvas,bx,by,tx,ty):
self.canvas = canvas
self.rec = canvas.create_rectangle((bx,by,tx,ty),fill = "lightgray", activefill= 'black', outline = 'lightgray')
self.xmin = bx
self.xmax = tx
self.ymin = by
self.ymax = ty
##print (bx, by, tx, ty)
def clicked(self, px, py):
return (self.xmin <= px <= self.xmax and
self.ymin <= py <= self.ymax)
def activate(self):
self.canvas.itemconfigure(self.rec, fill = 'black')
AND
from Tkinter import *
from button import *
class AppFrame(Frame):
def __init__(self):
self.root = Tk()
self.root.geometry = ("1000x1000")
self.f = Frame(self.root, relief = 'sunken', width = 600, height = 600)
self.w = Canvas(self.f,width = 505, height =505)
##get the x, y value whenever the user make a mouse click
self.w.bind("<Button-1>", self.xy)
self.bolist = []
for k in range(1,101):
for i in range(1, 101):
button = Buttons(self.w, i * 5, k * 5, i * 5 + 5, k * 5 + 5)
self.bolist.append(button)
self.f.grid(column =0, columnspan = 4)
self.w.grid(column = 0)
self.root.mainloop()
def xy (self, event):
self.x, self.y = event.x, event.y
print (self.x, self.y)
##check each button if it's clicked
for hb in self.bolist:
if hb.clicked(self.x, self.y):
print ("hurry")
hb.activate()
newApp = AppFrame()