How to make a mouse click do something at a specific point? - python

how can I make the mouse do something once it's clicking at a certain point in Zelle's graphics? What I am trying to do is make my stopwatch start when I click the "startbutton" image. However, I am obviously doing something wrong because my program either crashes or doesn't do anything.
from graphics import *
import time
#from tkinter import *
win = GraphWin('Stopwatch', 600, 600)
win.yUp()
#Assigning images
stopWatchImage = Image(Point (300, 300), "stopwatch.png")
startImage = Image(Point (210, 170), "startbutton.png")
stopImage = Image(Point (390, 170), "stopbutton.png")
lapImage = Image(Point (300, 110), "lapbutton.png")
stopWatchImage.draw(win)
startImage.draw(win)
stopImage.draw(win)
lapImage.draw(win)
sec = 0
minute = 0
hour = 0
def isBetween(x, end1, end2):
'''Return True if x is between the ends or equal to either.
The ends do not need to be in increasing order.'''
return end1 <= x <= end2 or end2 <= x <= end1
def isInside(point, startImage):
'''Return True if the point is inside the Rectangle rect.'''
pt1 = startImage.getP1()
pt2 = startImage.getP2()
return isBetween(point.getX(), pt1.getX(), pt2.getX()) and \
isBetween(point.getY(), pt1.getY(), pt2.getY())
def getChoice(win): #NEW
'''Given a list choicePairs of tuples with each tuple in the form
(rectangle, choice), return the choice that goes with the rectangle
in win where the mouse gets clicked, or return default if the click
is in none of the rectangles.'''
point = win.getMouse()
if isInside(point, startImage):
time.sleep(1)
sec += 1
timeText.setText(sec)
timeText.setText('')
while sec >= 0 and sec < 61:
#Displaying Time
timeText = Text(Point (300,260), str(hour) + ":" + str(minute) + ":" + str(sec))
timeText.setSize(30)
timeText.draw(win)
time.sleep(1)
sec += 1
timeText.setText(sec)
timeText.setText('')
#Incrementing minutes,hours
if sec == 60:
sec = 0
minute += 1
if minute == 60:
sec = 0
minute = 0
hour += 1
return default
def layout()
getChoice(win)
layout()
I can't seem to get it to work.
Edit: added the rest of my code for clarification.

You can use setMouseHandler to assign function which will called when you click in window.
In example if you click in left part of window then it draws rectangle, if you click in right part of window then it draws circle.
You can open file graphics.py and see all code. It is the fastest method to can see what functions you can use.
from graphics import *
# --- functions ---
def on_click(point):
# inform function to use global variable
global win
if point.x > win.width//2:
c = Circle(point, 10)
c.draw(win)
else:
a = Point(point.x - 10, point.y - 10)
b = Point(point.x + 10, point.y + 10)
r = Rectangle(a, b)
r.draw(win)
def main():
# inform function to use global variable
global win
win = GraphWin("My Circle", 500, 500)
win.setMouseHandler(on_click)
win.getKey()
win.close()
# --- start ---
# create global variable
win = None
main()
BTW: graphics uses Tkinter which has widgets Button, Label, Text, etc. It can use canvas.create_window() to add widget to canvas.
Tkinter has also function after(miliseconds, function_name) which lets you execute function periodically - ie. to update time.
Example
from graphics import *
import datetime
# --- classes ---
class _Widget():
def __init__(self, x, y, w, h, **options):
self.x = x
self.y = y
self.w = w
self.h = h
self.options = options
def draw(self, canvas, **options):
return None
def set(self, **options):
self.widget.config(options)
def get(self, **options):
self.widget.cget(options)
class Button(_Widget):
def draw(self, canvas, **options):
x, y = canvas.toScreen(self.x, self.y) # ???
self.widget = tk.Button(canvas, self.options)
return canvas.create_window((x, y), options, width=self.w, height=self.h, window=self.widget, anchor='nw')
class Label(_Widget):
def draw(self, canvas, **options):
x, y = canvas.toScreen(self.x, self.y) # ???
self.widget = tk.Label(canvas, self.options)
return canvas.create_window((x, y), options, width=self.w, height=self.h, window=self.widget, anchor='nw')
# --- functions ---
def on_start():
#global b1, b2
global running
b1.set(state='disabled')
b2.set(state='normal')
running = True
# run first time
update_time()
print("START")
def on_stop():
#global b1, b2
global running
b1.set(state='normal')
b2.set(state='disabled')
l.set(text="Controls:")
running = False
print("STOP")
def update_time():
#global l
#global running
if running:
l.set(text=datetime.datetime.now().strftime("%H:%M:%S"))
# run again after 1000ms (1s)
win.after(1000, update_time)
# --- main ---
def main():
global win, l, b1, b2
win = GraphWin("My Buttons", 500, 500)
l = Label(0, 0, 100, 50, text="Controls:")
l.draw(win)
b1 = Button(100, 0, 100, 50, text="START", command=on_start)
b1.draw(win)
b2 = Button(200, 0, 100, 50, text="STOP", command=on_stop, state='disabled')
b2.draw(win)
win.getKey()
win.close()
# --- global variable to access in functions ---
win = None
l = None
b1 = None
b2 = None
running = False
# --- start ---
main()
Tkinter: Canvas, Button, other
EDIT: working example
from graphics import *
def isBetween(x, end1, end2):
return end1 <= x <= end2 or end2 <= x <= end1
def isInside(point, startImage):
x = startImage.getAnchor().getX()
y = startImage.getAnchor().getY()
w = startImage.getWidth()/2
h = startImage.getHeight()/2
pt1_x = x - w
pt1_y = y - h
pt2_x = x + w
pt2_y = y + h
return isBetween(point.getX(), pt1_x, pt2_x) and \
isBetween(point.getY(), pt1_y, pt2_y)
def getChoice(event):
global hour, minute, sec
global running
point = Point(round(event.x), round(event.y))
if isInside(point, startImage):
sec = 0
minute = 0
hour = 0
running = True
update_time()
if isInside(point, stopImage):
running = False
def update_time():
global hour, minute, sec
#global timeText
#global win
sec += 1
if sec == 60:
sec = 0
minute += 1
if minute == 60:
minute = 0
hour += 1
timeText.setText('{}:{}:{}'.format(hour, minute, sec))
if running:
win.after(1000, update_time)
else:
timeText.setText('')
def layout():
global win
global stopWatchImage
global startImage
global stopImage
global lapImage
global timeText
win = GraphWin('Stopwatch', 600, 600)
#win.yUp()
#Assigning images
stopWatchImage = Image(Point(300, 300), "stopwatch.png")
startImage = Image(Point(210, 170), "startbutton.png")
stopImage = Image(Point(390, 170), "stopbutton.png")
lapImage = Image(Point(300, 110), "lapbutton.png")
#Drawing images
stopWatchImage.draw(win)
startImage.draw(win)
stopImage.draw(win)
lapImage.draw(win)
timeText = Text(Point(300,260), '')
timeText.setSize(30)
timeText.draw(win)
win.setMouseHandler(getChoice)
win.getKey()
# --- global variable ---
win = None
stopWatchImage = None
startImage = None
stopImage = None
lapImage = None
timeText = None
running = False
# --- start ---
layout()

Related

I'm making a snake game using python. I cant seem to make the snake moves in any direction. What should i fix and add with my codes?

I have this code below where it will add a +1 or -1 unit whenever I press any of the arrow button. My question is how can I use that new value in velocity and add it to my snake to the snake moves? I believe my code is wrong in the def snake() function but I don't seem to find the solution.
additional: what else should I fix to my code here to make it cleaner and concise? thank you
import random
from tkinter import *
SCREEN_WIDTH = 500
SCREEN_HEIGHT = 500
SCORE = 0
BODY_SIZE = 10
SNAKE_COLOUR ="blue"
SNAKE_FOOD = "purple"
def food():
x = random.randint(0,30-1)*BODY_SIZE
y = random.randint(0,30-1)*BODY_SIZE
canvas.create_rectangle (x,y,x+BODY_SIZE,y+BODY_SIZE,fill="GOLD")
def snake():
SNAKE_VELOCITY_X = 0
SNAKE_VELOCITY_Y = 0
snakeX = 0
snakeY = 0
snakeX = snakeX + SNAKE_VELOCITY_X
snakeY = snakeY + SNAKE_VELOCITY_Y
canvas.create_rectangle (snakeX,snakeY,snakeX+BODY_SIZE,snakeY+BODY_SIZE,fill="blue")
def update():
food()
snake()
master =Tk()
master.geometry("%sx%s"%(SCREEN_WIDTH,SCREEN_HEIGHT))
labelName= Label(master, text="A python game!", font= ('Courier 10 underline'))
labelScore = Label(master, text="%s"%(SCORE), font=('Courier 10'))
labelName.pack()
labelScore.pack()
canvas = Canvas(master, bg="black",width=300,height=300)
canvas.pack()
update()
def right_direction(event):
SNAKE_VELOCITY_X = 1
SNAKE_VELOCITY_Y = 0
print(SNAKE_VELOCITY_X)
def left_direction(event):
SNAKE_VELOCITY_X = -1
SNAKE_VELOCITY_Y = 0
print(SNAKE_VELOCITY_X)
def up_direction(event):
SNAKE_VELOCITY_X = 0
SNAKE_VELOCITY_Y = 1
print(SNAKE_VELOCITY_Y)
def down_direction(event):
SNAKE_VELOCITY_X = 0
SNAKE_VELOCITY_Y = -1
print(SNAKE_VELOCITY_Y)
master.bind("<Right>", right_direction)
master.bind("<Left>", left_direction)
master.bind("<Up>", up_direction)
master.bind("<Down>", down_direction)
master.mainloop()
This is not an exhaustive answer, but should get you moving in the right direction. First, the concept: you create objects with the create_...() functions, and those functions return "handles" to the created objects. you can then use the handles to move, etc. the objects. To actually redraw the screen, you need to call the Tk.update() function.
Roughly, you need to change number of things:
the function canvas.create_rectangle(...) returns a handle to the rectangle. Save it, this is your "snake"
snakeHandle=canvas.create_rectangle(...)
then, when you execute one of your functions ..._direction(event), inside that function, tell the snake to move somewhere like this:
canvas.move(snakeHandle,x_distance, y_distance)
then kick tkinter so it updates the screen:
master.update()
i changed a couple off things and now it should move. but the only problem is that the the snake does not get removed after it moves. you can try canvas.delete().
i put a # after everything i changed.
import random
from tkinter import *
SCREEN_WIDTH = 500
SCREEN_HEIGHT = 500
SCORE = 0
BODY_SIZE = 10
SNAKE_COLOUR ="blue"
SNAKE_FOOD = "purple"
SNAKE_VELOCITY_X = 0 # making these variables avalable to the whole code
SNAKE_VELOCITY_Y = 0 #
snakeX = 0 #
snakeY = 0 #
def food():
x = random.randint(0,30-1)*BODY_SIZE
y = random.randint(0,30-1)*BODY_SIZE
canvas.create_rectangle (x,y,x+BODY_SIZE,y+BODY_SIZE,fill="GOLD")
def snake():
global snakeX, snakeY, SNAKE_VELOCITY_X, SNAKE_VELOCITY_Y # global makes it so that the change is not only for the function but for the entire code
snakeX = snakeX + SNAKE_VELOCITY_X * BODY_SIZE # makes it so that the player moves on player size instead of one pixel
snakeY = snakeY + SNAKE_VELOCITY_Y * BODY_SIZE #
canvas.create_rectangle (snakeX,snakeY,snakeX+BODY_SIZE,snakeY+BODY_SIZE,fill="blue")
def update():
#food() # otherwise spawns new food
snake()
master =Tk()
master.geometry("%sx%s"%(SCREEN_WIDTH,SCREEN_HEIGHT))
labelName= Label(master, text="A python game!", font= ('Courier 10 underline'))
labelScore = Label(master, text="%s"%(SCORE), font=('Courier 10'))
labelName.pack()
labelScore.pack()
canvas = Canvas(master, bg="black",width=300,height=300)
canvas.pack()
update()
def right_direction(event):
global SNAKE_VELOCITY_X, SNAKE_VELOCITY_Y #
SNAKE_VELOCITY_X = 1
SNAKE_VELOCITY_Y = 0
print(SNAKE_VELOCITY_X)
update() # calling the update function
def left_direction(event):
global SNAKE_VELOCITY_X, SNAKE_VELOCITY_Y #
SNAKE_VELOCITY_X = -1
SNAKE_VELOCITY_Y = 0
print(SNAKE_VELOCITY_X)
update() #
def up_direction(event):
global SNAKE_VELOCITY_X, SNAKE_VELOCITY_Y #
SNAKE_VELOCITY_X = 0
SNAKE_VELOCITY_Y = -1
print(SNAKE_VELOCITY_Y)
update() #
def down_direction(event):
global SNAKE_VELOCITY_X, SNAKE_VELOCITY_Y #
SNAKE_VELOCITY_X = 0
SNAKE_VELOCITY_Y = 1
print(SNAKE_VELOCITY_Y)
update() #
master.bind("<Right>", right_direction)
master.bind("<Left>", left_direction)
master.bind("<Up>", up_direction)
master.bind("<Down>", down_direction)
master.mainloop()
hope this helps
EDIT
i change your code to be in a class and cleaned some things up
import random
from tkinter import *
class game(Tk):
def __init__(self):
super().__init__()
self.createVariables()
self.createWindow()
self.bindControls()
self.snake()
self.food()
def createVariables(self):
self.SCREEN_WIDTH = 500
self.SCREEN_HEIGHT = 500
self.SCORE = 0
self.BODY_SIZE = 10
self.SNAKE_COLOUR ="blue"
self.SNAKE_FOOD = "purple"
self.SNAKE_VELOCITY_X = 0
self.SNAKE_VELOCITY_Y = 0
self.snakeX = 0
self.snakeY = 0
self._snake = None
def createWindow(self):
self.geometry("%sx%s"%(self.SCREEN_WIDTH,self.SCREEN_HEIGHT))
self.labelName= Label(self, text="A python game!", font= ('Courier 10 underline'))
self.labelScore = Label(self, text="%s"%(self.SCORE), font=('Courier 10'))
self.labelName.pack()
self.labelScore.pack()
self.canvas = Canvas(self, bg="black",width=300,height=300)
self.canvas.pack()
def bindControls(self):
self.bind("<Right>", self.right_direction)
self.bind("<Left>", self.left_direction)
self.bind("<Up>", self.up_direction)
self.bind("<Down>", self.down_direction)
def food(self):
x = random.randint(0,30-1)*self.BODY_SIZE
y = random.randint(0,30-1)*self.BODY_SIZE
self._food = self.canvas.create_rectangle (x,y,x+self.BODY_SIZE,y+self.BODY_SIZE,fill="GOLD")
def snake(self):
if self._snake != None:
self.canvas.delete(self._snake)
self.snakeX = self.snakeX + self.SNAKE_VELOCITY_X * self.BODY_SIZE
self.snakeY = self.snakeY + self.SNAKE_VELOCITY_Y * self.BODY_SIZE
self._snake = self.canvas.create_rectangle (self.snakeX,self.snakeY,self.snakeX+self.BODY_SIZE,self.snakeY+self.BODY_SIZE,fill="blue")
def update(self):
self.snake()
def right_direction(self, event):
self.SNAKE_VELOCITY_X = 1
self.SNAKE_VELOCITY_Y = 0
print(self.SNAKE_VELOCITY_X)
self.update()
def left_direction(self, event):
self.SNAKE_VELOCITY_X = -1
self.SNAKE_VELOCITY_Y = 0
print(self.SNAKE_VELOCITY_X)
self.update()
def up_direction(self, event):
self.SNAKE_VELOCITY_X = 0
self.SNAKE_VELOCITY_Y = -1
print(self.SNAKE_VELOCITY_Y)
self.update()
def down_direction(self, event):
self.SNAKE_VELOCITY_X = 0
self.SNAKE_VELOCITY_Y = 1
print(self.SNAKE_VELOCITY_Y)
self.update()
if __name__ == "__main__":
run = game()
run.mainloop()
hope it helps

Label Position, Resize Background And Hide Title Bar

I'm trying to better understand Pyglet and I'm using a script posted by Torxed in my previous question. I started to modify it until I reached this point:
import pyglet, datetime, os
from pyglet.gl import *
from collections import OrderedDict
from time import time
from pathlib import Path
key = pyglet.window.key
class main(pyglet.window.Window):
#Variable Display
Fullscreen = False
CountDisplay = 0
setDisplay0 = (1024, 576)
setDisplay1 = (800, 600)
setDisplay2 = (1024, 768)
setDisplay3 = (1280, 720)
setDisplay4 = (1280, 1024)
setDisplay5 = (1366, 768)
setDisplay6 = (1920, 1080)
#Variable Info
infoHidden = False
title = "Pokémon Life And Death: Esploratori Del Proprio Destino"
build = "Versione: 0.0.0"
keyPrint = "Nessuno"
MousekeyPrint = "Nessuno"
infoFullscreen = "Disattivo"
infoDisplay = "1024, 576"
def __init__ (self, width=1024, height=576, fullscreen=False, *args, **kwargs):
super(main, self).__init__(width, height, fullscreen, *args, **kwargs)
platform = pyglet.window.get_platform()
display = platform.get_default_display()
screen = display.get_default_screen()
self.infoScreen = (screen.width, screen.height)
self.xDisplay = int(screen.width / 2 - self.width / 2)
self.yDisplay = int(screen.height / 2 - self.height / 2)
self.set_location(self.xDisplay, self.yDisplay)
self.sprites = OrderedDict()
self.spritesInfo = OrderedDict()
#Information Hidden
#Title
self.spritesInfo["title_label"] = pyglet.text.Label(self.title, x=self.infoPos("x", 0), y=self.infoPos("y", 0))
#Build Version
self.spritesInfo["build_label"] = pyglet.text.Label(self.build, x=self.infoPos("x", 0), y=self.infoPos("y", 20))
#Fullscreen
self.spritesInfo["fullscreen_label"] = pyglet.text.Label("Fullscreen: " + self.infoFullscreen, x=self.infoPos("x", 0), y=self.infoPos("y", 40))
#Display
self.spritesInfo["screen_label"] = pyglet.text.Label("Display: " + self.infoDisplay, x=self.infoPos("x", 0), y=self.infoPos("y", 60))
#FPS
self.spritesInfo["fps_label"] = pyglet.text.Label("FPS: 0", x=self.infoPos("x", 0), y=self.infoPos("y", 80))
self.last_update = time()
self.fps_count = 0
#Mouse Position
self.mouse_x = 0
self.mouse_y = 0
self.spritesInfo["mouse_label"] = pyglet.text.Label(("Mouse Position (X,Y): " + str(self.mouse_x) + "," + str(self.mouse_y)), x=self.infoPos("x", 0), y=self.infoPos("y", 100))
#Player Position
self.player_x = 0
self.player_y = 0
self.spritesInfo["player_label"] = pyglet.text.Label(("Player Position (X,Y): " + str(self.player_x) + "," + str(self.player_y)), x=self.infoPos("x", 0), y=self.infoPos("y", 120))
#Key Press
self.keys = OrderedDict()
self.spritesInfo["key_label"] = pyglet.text.Label(("Key Press: " + self.keyPrint), x=self.infoPos("x", 0), y=self.infoPos("y", 140))
#Mouse Press
self.spritesInfo["MouseKey_label"] = pyglet.text.Label(("Mouse Key Press: " + self.MousekeyPrint), x=self.infoPos("x", 0), y=self.infoPos("y", 160))
self.alive = 1
def infoPos(self, object, ny):
posInfo = self.get_size()
if object is "x":
elab = 10
elif object is "y":
elab = posInfo[1] - 20 - ny
else:
elab = 0
return elab
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_mouse_motion(self, x, y, dx, dy):
self.mouse_x = x
self.mouse_y = y
def on_mouse_release(self, x, y, button, modifiers):
self.MousekeyPrint = "Nessuno"
def on_mouse_press(self, x, y, button, modifiers):
self.MousekeyPrint = str(button)
def on_mouse_drag(self, x, y, dx, dy, button, modifiers):
self.drag = True
print('Dragging mouse at {}x{}'.format(x, y))
def on_key_release(self, symbol, modifiers):
self.keyPrint = "Nessuno"
try:
del self.keys[symbol]
except:
pass
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE:
self.alive = 0
if symbol == key.F2:
datanow = datetime.datetime.now()
if not Path("Screenshot").is_dir():
os.makedirs("Screenshot")
pyglet.image.get_buffer_manager().get_color_buffer().save("Screenshot/"+str(datanow.day)+"-"+str(datanow.month)+"-"+str(datanow.year)+"_"+str(datanow.hour)+"."+str(datanow.minute)+"."+str(datanow.second)+".png")
if symbol == key.F3:
if self.infoHidden:
self.infoHidden = False
else:
self.infoHidden = True
if symbol == key.F10:
self.CountDisplay += 1
if self.CountDisplay == 1:
size = self.setDisplay1
elif self.CountDisplay == 2:
size = self.setDisplay2
elif self.CountDisplay == 3:
size = self.setDisplay3
elif self.CountDisplay == 4:
size = self.setDisplay4
elif self.CountDisplay == 5:
size = self.setDisplay5
elif self.CountDisplay == 6:
size = self.setDisplay6
else:
self.CountDisplay = 0
size = self.setDisplay0
self.set_size(size[0], size[1])
self.infoDisplay = str(size[0]) + "," + str(size[1])
pos = (int(self.infoScreen[0] / 2 - size[0] / 2), int(self.infoScreen[1] / 2 - size[1] / 2))
self.set_location(pos[0], pos[1])
if symbol == key.F11:
if self.Fullscreen:
self.Fullscreen = False
self.set_fullscreen(False)
self.infoFullscreen = "Disattivo"
else:
self.Fullscreen = True
self.set_fullscreen(True)
self.infoFullscreen = "Attivo"
self.keyPrint = str(symbol)
self.keys[symbol] = True
def pre_render(self):
pass
def render(self):
self.clear()
#FPS
self.fps_count += 1
if time() - self.last_update > 1:
self.spritesInfo["fps_label"].text = "FPS: " + str(self.fps_count)
self.fps_count = 0
self.last_update = time()
#Mouse Position
self.spritesInfo["mouse_label"].text = "Mouse Position (X,Y): " + str(self.mouse_x) + "," + str(self.mouse_y)
#Player Position
self.spritesInfo["player_label"].text = "Player Position (X,Y): " + str(self.player_x) + "," + str(self.player_y)
#Key Press
self.spritesInfo["key_label"].text = "Key Press: " + self.keyPrint
#Mouse Press
self.spritesInfo["MouseKey_label"].text = "Mouse Key Press: " + self.MousekeyPrint
#Fullscreen
self.spritesInfo["fullscreen_label"].text = "Fullscreen: " + self.infoFullscreen
#Display
self.spritesInfo["screen_label"].text = "Display: " + self.infoDisplay
#self.bg.draw()
self.pre_render()
for sprite in self.sprites:
self.sprites[sprite].draw()
if self.infoHidden:
for spriteInfo in self.spritesInfo:
self.spritesInfo[spriteInfo].draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
event = self.dispatch_events()
if __name__ == '__main__':
x = main()
x.run()
But now I find myself in front of a point where I can not proceed in Pyglet alone.
I can not understand how I can change the coordinates of the "label" every time the window size changes.
I would like to be able to use an image as a background and adapt it to the size of the window (basic the image is 1920x1080, ie the maximum window size). The problem is that I did not find much on this subject. I state that what I'm working on is 2D, not 3D. I had found a possible solution in another question, always answered by Torxed on the resizing of an image, but in some tests before this, after having adapted it, it did not work. So I do not know where to bang my head sincerely. In Pygame it was easy to use "pygame.transform.scale", but in Pyglet I would not know.
Finally, I tried both on the Pyglet wiki and on the web, but I did not find anything. How can I delete or hide the window title bar? In Pygame, you could with the flags. Is it possible to do this also with Pyglet?
EDIT:
I forgot to ask another question. When I press a key on the keyboard or mouse, the information displayed with F3 is printed in the corresponding key number. Is there a way to replace the number with the name of the button? For example, 97 is replaced with "A"? Obviously, I mean if there's a way without having to list all the keys and put the "if" statement for everyone.
EDIT2:
It seems like the script posted, the def on_resize (self, width, height) part does not like it very much. Let me explain, if for itself the function works correctly. The problem is that if I insert it, the labels, once pressed F3, do not appear. I tried to test it using print in several places and it seems that the only instruction that is not executed is self.spritesInfo [spriteInfo] .draw ()

Python Game: Control Square. If square reaches within 10 pixels of random spawned square it adds 1 point. Doesn't work. Why?

I made a game where you control a square and a random other square spawns randomly on the map. If the first square gets within 10 pixels of the other it adds a point but the program doesn't work. Can anyone tell me why?
Here is my code:
from tkinter import *
from random import uniform, randrange
tk = Tk()
canvas = Canvas(tk, width=400, height=400,bg='black')
canvas.pack()
pointcount = -1
LENGTH = 15
WIDTH = 15
LENGTH2 = randrange(1,390)
WIDTH2 = randrange(1,390)
LENGTH3 = LENGTH2 + 15
WIDTH3 = WIDTH2 + 15
X = randrange(1,400)
Y = randrange(1,400)
Snake = canvas.create_rectangle(0,0,WIDTH,LENGTH,fill="green")
Food = canvas.create_rectangle(WIDTH2,LENGTH2,WIDTH3,LENGTH3,fill="yellow")
pos = canvas.coords(Snake)
pos2 = canvas.coords(Food)
def movement_right(event):
canvas.move(Snake,15,0)
def movement_left(event):
canvas.move(Snake,-15,0)
def movement_down(event):
canvas.move(Snake,0,15)
def movement_up(event):
canvas.move(Snake,0,-15)
for i in range(1,20):
if pos[0] - pos2[2] == 10:
pointcount = pointcount + 1
print("Total points : ", pointcount)
tk.update()
tk.bind('<Left>', movement_left)
tk.bind('<Right>', movement_right)
tk.bind('<Down>', movement_down)
tk.bind('<Up>', movement_up)
tk.mainloop()
You have to check it inside functions because other code is executed only once.
You can create on function to test positions and execute in all movement_ functions.
I use "cells" to set positions
import tkinter as tk
import random
# --- constants --- (UPPER_CASE names)
SCREEN_WIDTH = 400
SCREEN_HEIGHT = 400
CELL_WIDTH = 20
CELL_HEIGHT = 20
COLS = SCREEN_WIDTH/CELL_WIDTH
ROWS = SCREEN_HEIGHT/CELL_HEIGHT
# --- functions ---
def test():
global pointcount
snake_pos = canvas.coords(snake)
food_pos = canvas.coords(food)
# calculate distance
diff_x = abs(snake_pos[0] - food_pos[0])
diff_y = abs(snake_pos[1] - food_pos[1])
print(diff_x, diff_y)
# if snake eat food
if diff_x == 0 and diff_y == 0:
pointcount += 1
print("Total points : ", pointcount)
# move food to new place
food_x1 = CELL_WIDTH * random.randrange(0, COLS)
food_y1 = CELL_HEIGHT * random.randrange(0, ROWS)
food_x2 = food_x1 + CELL_WIDTH
food_y2 = food_y1 + CELL_HEIGHT
canvas.coords(food, (food_x1, food_y1, food_x2, food_y2))
def movement_right(event):
canvas.move(snake, CELL_WIDTH, 0)
test()
def movement_left(event):
canvas.move(snake, -CELL_WIDTH, 0)
test()
def movement_down(event):
canvas.move(snake, 0, CELL_HEIGHT)
test()
def movement_up(event):
canvas.move(snake, 0, -CELL_HEIGHT)
test()
# --- other --- (lower_case names)
pointcount = 0
# --- main ---
root = tk.Tk()
canvas = tk.Canvas(root, width=SCREEN_WIDTH, height=SCREEN_HEIGHT, bg='black')
canvas.pack()
root.bind('<Left>', movement_left)
root.bind('<Right>', movement_right)
root.bind('<Down>', movement_down)
root.bind('<Up>', movement_up)
snake = canvas.create_rectangle(0, 0, CELL_WIDTH, CELL_HEIGHT, fill="green")
# create food in first random place
food_x1 = CELL_WIDTH * random.randrange(0, COLS)
food_y1 = CELL_HEIGHT * random.randrange(0, ROWS)
food_x2 = food_x1 + CELL_WIDTH
food_y2 = food_y1 + CELL_HEIGHT
food = canvas.create_rectangle(food_x1, food_y1, food_x2, food_y2, fill="yellow")
root.mainloop()

How to stop other processes or finish processes in Python / Tkinter program

I have been drawing some graphics with a Python / Tkinter program. The program has a main menu with menu items for drawing different figures. It works quite well but I came up against a problem. If the program is part way through drawing one figure and the user clicks to draw a second figure then the program draws the second figure, but when it has finished drawing the second figure it goes back and finishes drawing the first figure. What I want it to do is stop drawing the first figure and not go back to drawing the first figure even when the second figure has finished drawing. I created an simpler example program to demonstrate the scenario. To see the problem in this program click "Draw -> Red" and then click "Draw -> Blue" before the red has finished drawing. How do I get the program to abort any previous drawing? Here is the example program:
from tkinter import *
import random
import math
def line(canvas, w, h, p, i):
x0 = random.randrange(0, w)
y0 = random.randrange(0, h)
x1 = random.randrange(0, w)
y1 = random.randrange(0, h)
canvas.create_line(x0, y0, x1, y1, fill=p.col(i))
class Color:
def __init__(self, r, g, b):
self.red = r
self.gre = g
self.blu = b
def hexVal(self, v):
return (hex(v)[2:]).zfill(2)
def str(self):
return "#" + self.hexVal(self.red) + self.hexVal(self.gre) + self.hexVal(self.blu)
class Palette:
def __init__(self, n0, y):
self.colors = []
self.n = n0
self.m = 0
if y == "red":
self.red()
elif y == "blue":
self.blue()
def add(self, c):
self.colors.append(c)
self.m += 1
def red(self):
self.add(Color(127, 0, 0))
self.add(Color(255, 127, 0))
def blue(self):
self.add(Color(0, 0, 127))
self.add(Color(0, 127, 255))
def col(self, i):
k = i % (self.n*self.m)
z = k // self.n
j = k % self.n
c0 = self.colors[z]
c1 = self.colors[(z + 1) % self.m]
t0 = (self.n - j)/self.n
t1 = j/self.n
r = int(math.floor(c0.red*t0 + c1.red*t1))
g = int(math.floor(c0.gre*t0 + c1.gre*t1))
b = int(math.floor(c0.blu*t0 + c1.blu*t1))
c = Color(r, g, b)
return c.str()
def upd(canvas):
try:
canvas.update()
return True
except TclError:
return False
def tryLine(canvas, w, h, p, i, d):
try:
line(canvas, w, h, p, i)
if i % d == 0:
upd(canvas)
return True
except TclError:
return False
class MenuFrame(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.WIDTH = 800
self.HEIGHT = 800
self.canvas = Canvas(self.parent, width=self.WIDTH, height=self.HEIGHT)
self.pack(side=BOTTOM)
self.canvas.pack(side=TOP, fill=BOTH, expand=1)
self.parent.title("Line Test")
menubar = Menu(self.parent)
self.parent.config(menu=menubar)
self.parent.protocol('WM_DELETE_WINDOW', self.onExit)
menu = Menu(menubar)
menu.add_command(label="Red", command=self.onRed)
menu.add_command(label="Blue", command=self.onBlue)
menu.add_command(label="Exit", command=self.onExit)
menubar.add_cascade(label="Draw", menu=menu)
self.pRed = Palette(256, "red")
self.pBlue = Palette(256, "blue")
def onRed(self):
# How to abort here any processes currently running?
self.canvas.delete("all")
for i in range(0, 7000):
tryLine(self.canvas, self.WIDTH, self.HEIGHT, self.pRed, i, 100)
upd(self.canvas)
def onBlue(self):
# How to abort here any processes currently running?
self.canvas.delete("all")
for i in range(0, 7000):
tryLine(self.canvas, self.WIDTH, self.HEIGHT, self.pBlue, i, 100)
upd(self.canvas)
def onExit(self):
self.canvas.delete("all")
self.parent.destroy()
def main():
root = Tk()
frame = MenuFrame(root)
root.mainloop()
if __name__ == '__main__':
main()
That's the simple example? ;)
You can add a variable that tracks the currently selected method, then check if that variable exists before completing the for loop. Here's an even simpler example:
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
Button(self, text='Task A', command=self._a).pack()
Button(self, text='Task B', command=self._b).pack()
self.current_task = None # this var will hold the current task (red, blue, etc)
def _a(self):
self.current_task = 'a' # set the current task
for i in range(1000):
if self.current_task == 'a': # only continue this loop if its the current task
print('a')
self.update()
def _b(self):
self.current_task = 'b'
for i in range(1000):
if self.current_task == 'b':
print('b')
self.update()
root = Tk()
Example(root).pack()
root.mainloop()
Let me know if that doesn't make sense or doesn't work out for you.

how to repaint only part of a QWidget in PyQt4?

I'm trying to create a program that displays a large grid of numbers (say, filling up a 6 by 4000 grid), where the user can move a cursor around via keyboard or mouse and enter in numbers into the grid. (This is for a guitar tablature program.) I'm new to python GUI programming, and thus far my idea is to have a very large QWidget window (say, 1000x80000 pixels) inside of a QScrollArea inside of the main window. The problem is that every mouse click or cursor movement causes the whole thing to repaint, causing a delay, when I just want to repaint whatever changes I just made to make things faster. In PyQt, is there a way to buffer already-painted graphics and change just the graphics that need changing?
edit: I've posted the code below, which I've run with python3.3 on Mac OS 10.7. The main point is that in the TabWindow init function, the grid size can be set by numXGrid and numYGrid (currently set to 200 and 6), and this grid is filled with random numbers by the generateRandomTablatureData() method. If the grid is filled with numbers, then there's a noticeable lag with every key press, which gets worse with larger grids. (There is also an initial delay due to generating the data, but my question is on the delay after each key press which I assume is due to having to repaint every number.)
There are two files. This is the main one, which I called FAIT.py:
import time
start_time = time.time()
import random
import sys
from PyQt4 import QtGui, QtCore
import Tracks
# generate tracks
tracks = [Tracks.Track(), Tracks.Track(), Tracks.Track()]
fontSize = 16
# margins
xMar = 50
yMar = 50
trackMar = 50 # margin between tracks
class MainWindow(QtGui.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.initUI()
end_time = time.time()
print("Initializing time was %g seconds" % (end_time - start_time))
def initUI(self):
# attach QScrollArea to MainWindow
l = QtGui.QVBoxLayout(self)
l.setContentsMargins(0,0,0,0)
l.setSpacing(0)
s=QtGui.QScrollArea()
l.addWidget(s)
# attach TabWindow to QScrollArea so we can paint on it
self.tabWindow=TabWindow(self)
self.tabWindow.setFocusPolicy(QtCore.Qt.StrongFocus)
self.setFocusPolicy(QtCore.Qt.NoFocus)
vbox=QtGui.QVBoxLayout(self.tabWindow)
s.setWidget(self.tabWindow)
self.positionWindow() # set size and position of main window
self.setWindowTitle('MainWindow')
self.show()
def positionWindow(self):
qr = self.frameGeometry()
cp = QtGui.QDesktopWidget().availableGeometry().center()
width = QtGui.QDesktopWidget().availableGeometry().width() - 100
height = QtGui.QDesktopWidget().availableGeometry().height() - 100
self.resize(width, height)
qr = self.frameGeometry()
cp = QtGui.QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
def keyPressEvent(self, e):
print('key pressed in MainWindow')
def mousePressEvent(self, e):
print('mouse click in MainWindow')
class TabWindow(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
# size of tablature grid
numXGrid = 200
numYGrid = 6
# initialize tablature information first
for i in range(0, len(tracks)):
tracks[i].numXGrid = numXGrid
self.arrangeTracks() # figure out offsets for each track
self.trackFocusNum = 0 # to begin with, focus is on track 0
self.windowSizeX = tracks[0].x0 + tracks[0].dx*(tracks[0].numXGrid+2)
self.windowSizeY = tracks[0].y0
for i in range(0, len(tracks)):
self.windowSizeY = self.windowSizeY + tracks[i].dy * tracks[i].numYGrid + trackMar
self.resize(self.windowSizeX,self.windowSizeY) # size of actual tablature area
# generate random tablature data for testing
self.generateRandomTablatureData()
def keyPressEvent(self, e):
print('key pressed in TabWindow')
i = self.trackFocusNum
if e.key() == QtCore.Qt.Key_Up:
tracks[i].moveCursorUp()
if e.key() == QtCore.Qt.Key_Down:
tracks[i].moveCursorDown()
if e.key() == QtCore.Qt.Key_Left:
tracks[i].moveCursorLeft()
if e.key() == QtCore.Qt.Key_Right:
tracks[i].moveCursorRight()
# check for number input
numberKeys = (QtCore.Qt.Key_0,
QtCore.Qt.Key_1,
QtCore.Qt.Key_2,
QtCore.Qt.Key_3,
QtCore.Qt.Key_4,
QtCore.Qt.Key_5,
QtCore.Qt.Key_6,
QtCore.Qt.Key_7,
QtCore.Qt.Key_8,
QtCore.Qt.Key_9)
if e.key() in numberKeys:
num = int(e.key())-48
# add data
tracks[i].data.addToTab(tracks[i].iCursor, tracks[i].jCursor, num)
# convert entered number to pitch and play note (do later)
# spacebar, backspace, or delete remove data
if e.key() in (QtCore.Qt.Key_Space, QtCore.Qt.Key_Backspace, QtCore.Qt.Key_Delete):
tracks[i].data.removeFromTab(tracks[i].iCursor, tracks[i].jCursor)
self.update()
def mousePressEvent(self, e):
print('mouse click in TabWindow')
xPos = e.x()
yPos = e.y()
# check tracks one by one
for i in range(0, len(tracks)):
if (tracks[i].isPositionInside(xPos, yPos)):
tracks[i].moveCursorToPosition(xPos, yPos)
self.trackFocusNum = i
break
else:
continue
self.update()
def paintEvent(self, e):
qp = QtGui.QPainter()
qp.begin(self)
qp.setPen(QtCore.Qt.black)
qp.setBrush(QtCore.Qt.white)
qp.drawRect(0, 0, self.windowSizeX, self.windowSizeY)
self.paintTracks(qp)
self.paintTunings(qp)
self.paintCursor(qp)
self.paintNumbers(qp)
qp.end()
def paintTracks(self, qp):
qp.setPen(QtCore.Qt.black)
qp.setBrush(QtCore.Qt.white)
for i in range(0, len(tracks)):
qp.drawPolyline(tracks[i].polyline)
def paintCursor(self, qp):
i = self.trackFocusNum
qp.setPen(QtCore.Qt.black)
qp.setBrush(QtCore.Qt.black)
qp.drawPolygon(tracks[i].getCursorQPolygon())
def paintNumbers(self, qp):
# iterate through tracks, and iterate through numbers on each track
for i in range(0, len(tracks)):
# make sure track has data to draw
if len(tracks[i].data.data) > 0:
for j in range(0, len(tracks[i].data.data)):
# do actual painting here
# first set color to be inverse cursor color if at cursor
if i == self.trackFocusNum and \
tracks[i].iCursor == tracks[i].data.data[j][0] and \
tracks[i].jCursor == tracks[i].data.data[j][1]:
qp.setPen(QtCore.Qt.white)
else:
qp.setPen(QtCore.Qt.black)
font = QtGui.QFont('Helvetica', fontSize)
qp.setFont(font)
text = str(tracks[i].data.data[j][2])
x1 = tracks[i].convertIndexToPositionX(tracks[i].data.data[j][0])
y1 = tracks[i].convertIndexToPositionY(tracks[i].data.data[j][1])
dx = tracks[i].dx
dy = tracks[i].dy
# height and width of number character(s)
metrics = QtGui.QFontMetrics(font)
tx = metrics.width(text)
ty = metrics.height()
# formula for alignment:
# xMar = (dx-tx)/2 plus offset
x11 = x1 + (dx-tx)/2
y11 = y1 + dy/2+ty/2
qp.drawText(x11, y11, text)
def paintTunings(self, qp):
qp.setPen(QtCore.Qt.black)
font = QtGui.QFont('Helvetica', fontSize)
qp.setFont(font)
for i in range(0, len(tracks)):
for j in range(0, tracks[i].numStrings):
text = tracks[i].convertPitchToLetter(tracks[i].stringTuning[j])
# height and width of characters
metrics = QtGui.QFontMetrics(font)
tx = metrics.width(text)
ty = metrics.height()
x11 = tracks[i].x0 - tx - 10
y11 = tracks[i].convertIndexToPositionY(j) + (tracks[i].dy+ty)/2
qp.drawText(x11, y11, text)
def arrangeTracks(self):
tracks[0].x0 = xMar
tracks[0].y0 = yMar
tracks[0].generateGridQPolyline()
for i in range(1, len(tracks)):
tracks[i].x0 = xMar
tracks[i].y0 = tracks[i-1].y0 + tracks[i-1].height + trackMar
tracks[i].generateGridQPolyline()
def generateRandomTablatureData(self):
t1 = time.time()
for i in range(0, len(tracks)):
# worst case scenario: fill every number
for jx in range(0, tracks[i].numXGrid):
for jy in range(0, tracks[i].numYGrid):
val = random.randint(0,9)
tracks[i].data.addToTab(jx, jy, val)
t2 = time.time()
print("Random number generating time was %g seconds" % (t2 - t1))
def main():
app = QtGui.QApplication(sys.argv)
ex = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
This is the other file, Tracks.py, which contains the Track class and supplementary methods:
# contains classes and methods relating to individual tracks
import math
from PyQt4 import QtGui, QtCore
# class for containing information about a track
class Track:
def __init__(self):
self.data = TabulatureData()
# position offset
self.x0 = 0
self.y0 = 0
self.dx = 20 # default rectangle width
self.dy = 40 # default rectangle height
# current cursor index coordinates
self.iCursor = 0
self.jCursor = 0
# default size of grid
self.numXGrid = 4000
self.numYGrid = 6
self.numStrings = self.numYGrid
# calculated maximum width and height in pixels
self.maxWidth = self.dx * self.numXGrid
self.maxHeight = self.dy * self.numYGrid
# generate points of grid and cursor
self.generateGridQPolyline()
# tuning
self.setTuning([40, 45, 50, 55, 59, 64])
# calculate bounds
self.height = self.numYGrid*self.dy
self.width = self.numXGrid*self.dx
def getCursorIndexX(self, xPos):
iPos = int(math.floor( (xPos-self.x0)/self.dx ))
return iPos
def getCursorIndexY(self, yPos):
jPos = int(math.floor( (yPos-self.y0)/self.dy ))
return jPos
def convertIndexToCoordinates(self, iPos, jPos):
return [self.ConvertIndexToPositionX(iPos),
self.ConvertIndexToPositionY(jPos)]
def convertIndexToPositionX(self, iPos):
return self.x0 + iPos*self.dx
def convertIndexToPositionY(self, jPos):
return self.y0 + jPos*self.dy
def getCursorQPolygon(self):
x1 = self.convertIndexToPositionX(self.iCursor)
y1 = self.convertIndexToPositionY(self.jCursor)
x2 = self.convertIndexToPositionX(self.iCursor+1)
y2 = self.convertIndexToPositionY(self.jCursor+1)
return QtGui.QPolygonF([QtCore.QPoint(x1, y1),
QtCore.QPoint(x1, y2),
QtCore.QPoint(x2, y2),
QtCore.QPoint(x2, y1)])
def generateGridQPolyline(self):
self.points = []
self.polyline = QtGui.QPolygonF()
for i in range(0, self.numXGrid):
for j in range(0, self.numYGrid):
x1 = self.convertIndexToPositionX(i)
y1 = self.convertIndexToPositionY(j)
x2 = self.convertIndexToPositionX(i+1)
y2 = self.convertIndexToPositionY(j+1)
self.points.append([(x1, y1), (x1, y2), (x2, y2), (x2, y1)])
self.polyline << QtCore.QPoint(x1,y1) << \
QtCore.QPoint(x1,y2) << \
QtCore.QPoint(x2,y2) << \
QtCore.QPoint(x2,y1) << \
QtCore.QPoint(x1,y1)
# smoothly connect different rows
jLast = self.numYGrid-1
x1 = self.convertIndexToPositionX(i)
y1 = self.convertIndexToPositionY(jLast)
x2 = self.convertIndexToPositionX(i+1)
y2 = self.convertIndexToPositionY(jLast+1)
self.polyline << QtCore.QPoint(x2,y1)
def isPositionInside(self, xPos, yPos):
if (xPos >= self.x0 and xPos <= self.x0 + self.width and
yPos >= self.y0 and yPos <= self.y0 + self.height):
return True
else:
return False
def moveCursorToPosition(self, xPos, yPos):
self.iCursor = self.getCursorIndexX(xPos)
self.jCursor = self.getCursorIndexY(yPos)
self.moveCursorToIndex(self.iCursor, self.jCursor)
def moveCursorToIndex(self, iPos, jPos):
# check if bounds are breached, and if cursor's already there,
# and if not, move cursor
if iPos == self.iCursor and jPos == self.jCursor:
return
if iPos >= 0 and iPos < self.numXGrid:
if jPos >= 0 and jPos < self.numYGrid:
self.iCursor = iPos
self.jCursor = jPos
return
def moveCursorUp(self):
self.moveCursorToIndex(self.iCursor, self.jCursor-1)
def moveCursorDown(self):
self.moveCursorToIndex(self.iCursor, self.jCursor+1)
def moveCursorLeft(self):
self.moveCursorToIndex(self.iCursor-1, self.jCursor)
def moveCursorRight(self):
self.moveCursorToIndex(self.iCursor+1, self.jCursor)
# return pitch in midi integer notation
def convertNumberToPitch(self, jPos, pitchNum):
return pitchNum + self.stringTuning[(self.numStrings-1) - jPos]
def convertPitchToLetter(self, pitchNum):
p = pitchNum % 12
letters = ('C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B')
return letters[p]
def setTuning(self, tuning):
self.stringTuning = tuning
class TabulatureData:
def __init__(self):
self.data = []
def addToTab(self, i, j, value):
# check if data is already there, and remove it first
if self.getValue(i, j) > -1:
self.removeFromTab(i, j)
self.data.append([i, j, value])
def getValue(self, i, j):
possibleTuples = [x for x in self.data if x[0] == i and x[1] == j]
if possibleTuples == []:
return -1
elif len(possibleTuples) > 1:
print('Warning: more than one number at a location!')
return possibleTuples[0][2] # return third number of tuple
def removeFromTab(self, i, j):
# first get value, if it exists
value = self.getValue(i,j)
if value == -1:
return
else:
# if it exists, then remove
self.data.remove([i, j, value])
1000*80000 is really huge.
So,maybe you should try QGLWidget or something like that?
Or according to Qt document, you should set which region you want to repaint.
some slow widgets need to optimize by painting only the requested region: QPaintEvent::region(). This speed optimization does not change the result, as painting is clipped to that region during event processing. QListView and QTableView do this, for example.

Categories