wxpython frame doesn't re-draw it's contents in while loop - python

I am creating a countdown to Christmas using wxpython as a GUI. I tried testing the script with the secondsLeft so that the seconds would be printed on the canvas. The program draws the seconds but they don't change as they're supposed to.
This is my code:
"""A simple contdown for Christmas using wxpython as a GUI"""
import wx
import datetime
#Setting our current time.
currentTime = datetime.datetime.now()
now = list(str(currentTime))
now = now[:19]
now = ''.join(now)
#Computing time left.
while now != '2015-12-25 1:00:00':
if currentTime.month == 11:
daysLeft = (30 - currentTime.day) + 24
else:
daysLeft = 25 - currentTime.day
if currentTime.hour >= 12:
hoursLeft = 25 - currentTime.hour
else:
hoursLeft = (12 - currentTime.hour) + 13
minutesLeft = currentTime.minute
secondsLeft = currentTime.second
currentTime = datetime.datetime.now()
now = list(str(currentTime))
now = now[:19]
now = ''.join(now)
class AFrame(wx.Frame):
def __init__ (self, parent=None, id=-1, title=None):
wx.Frame.__init__(self, parent, id, title, size=(400, 400))
self.statbmp = wx.StaticBitmap(self)
self.draw_image()
self.Refresh()
def draw_image(self):
# select the width and height of the blank bitmap
# must fit frame
w, h = 400, 400
# create the blank bitmap as background
draw_bmp = wx.EmptyBitmap(w, h)
#create canvas.
canvas = wx.MemoryDC(draw_bmp)
#fill the canvas with white
canvas.SetBrush(wx.Brush('white'))
canvas.Clear()
#get text dimentions.
tw, th = canvas.GetTextExtent(str(secondsLeft))
#draw the text.
canvas.DrawText(str(secondsLeft), (w - tw) / 2, (h - th) / 2 )
self.statbmp.SetBitmap(draw_bmp)
app = wx.App(0)
AFrame(title="Coundown to Christmas").Show()
app.MainLoop() #Starts frame.

Use wx.Timer to run function every 1000ms (1s).
Use datetime (and deltatime) to get left seconds.
#!/usr/bin/env python
import wx
import datetime
class AFrame(wx.Frame):
def __init__ (self):
wx.Frame.__init__(self, parent=None, id=-1, title="Coundown to Christmas", size=(400, 400))
# 2015.12.25 1:00:00
self.future_time = datetime.datetime(2015, 12, 25, 1, 0, 0)
# create timer
self.timer = wx.Timer(self)
# assign draw_image to timer
self.Bind(wx.EVT_TIMER, self.draw_image, self.timer)
# start timer
self.timer.Start(1000)
self.statbmp = wx.StaticBitmap(self)
self.draw_image()
self.Refresh()
self.Show()
def draw_image(self, event=None): # event required by timer
# get deltatime
secondsLeft = self.future_time - datetime.datetime.now()
# get seconds, round to integer,
secondsLeft = int(secondsLeft.total_seconds())
if secondsLeft <= 0:
secondsLeft = 0
if self.timer.IsRunning():
self.timer.Stop()
# convert to text
secondsLeft = str(secondsLeft)
# select the width and height of the blank bitmap
# must fit frame
w, h = 400, 400
# create the blank bitmap as background
draw_bmp = wx.EmptyBitmap(w, h)
#create canvas.
canvas = wx.MemoryDC(draw_bmp)
#fill the canvas with white
canvas.SetBrush(wx.Brush('white'))
canvas.Clear()
#get text dimentions.
tw, th = canvas.GetTextExtent(secondsLeft)
#draw the text.
canvas.DrawText(secondsLeft, (w - tw) / 2, (h - th) / 2 )
self.statbmp.SetBitmap(draw_bmp)
app = wx.App()
AFrame()
app.MainLoop()

Tart it up a bit furas!
and no this answer should not be accepted as it is ripping off Furas to whom any credit should go. Rolf
#!/usr/bin/env python
import wx
import datetime
class AFrame(wx.Frame):
def __init__ (self):
wx.Frame.__init__(self, parent=None, id=-1, title="Countdown to Christmas", size=(400, 400))
# 2015.12.25 1:00:00
self.future_time = datetime.datetime(2015, 12, 25, 1, 0, 0)
# create timer
self.timer = wx.Timer(self)
# assign draw_image to timer
self.Bind(wx.EVT_TIMER, self.draw_image, self.timer)
# start timer
self.timer.Start(1000)
self.statbmp = wx.StaticBitmap(self)
self.draw_image()
self.Refresh()
self.Show()
def draw_image(self, event=None): # event required by timer
# get deltatime
secondsLeft = self.future_time - datetime.datetime.now()
# get seconds, round to integer,
secondsLeft = int(secondsLeft.total_seconds())
if secondsLeft <= 0:
secondsLeft = 0
if self.timer.IsRunning():
self.timer.Stop()
# convert to text
d, s = divmod(secondsLeft,86400)
h, s = divmod(s,3600)
m, s = divmod(s, 60)
timestamp = 30*" "
if d> 0:
time_stamp = "%02d Days Hrs:%02d Mins:%02d Secs:%02d" % (d,h,m,s)
elif h > 0:
time_stamp = "Hrs:%02d Mins:%02d Secs:%02d" % (h,m,s)
else:
time_stamp = "Mins:%02d Secs:%02d" % (m,s)
# select the width and height of the blank bitmap
# must fit frame
w, h = 400, 400
# create the blank bitmap as background
draw_bmp = wx.EmptyBitmap(w, h)
#create canvas.
canvas = wx.MemoryDC(draw_bmp)
#fill the canvas with white
canvas.SetBrush(wx.Brush('white'))
canvas.Clear()
#get text dimentions.
tw, th = canvas.GetTextExtent(time_stamp)
#draw the text.
canvas.DrawText(time_stamp, (w - tw) / 2, (h - th) / 2 )
self.statbmp.SetBitmap(draw_bmp)
app = wx.App()
AFrame()
app.MainLoop()

Related

How to create a thread to take a picture after 3 seconds?

I've created a class to display my webcam video on a Tkinter screen and I would like to take 3 pictures(waiting 3 seconds after each picture taken) after a Tkinter button is pressed.
Here is my code(reduced), and my logic to take picture is done. Should I use Threads to solve this? I'm new to Python.
import tkinter, cv2, time, dlib, numpy as np, time, threading
from PIL import Image, ImageTk
class Tela:
def __init__(self, janela):
self.janela = janela
self.janela.title("Reconhecimento Facial")
self.janela.config(background="#FFFFFF")
self.image = None
self.cam = cv2.VideoCapture(0)
self.detector = dlib.get_frontal_face_detector()
self.delay = 15
self.update()
self.janela.mainloop()
def update(self): # display image on gui
ret, frame = self.cam.read()
if ret:
faces, confianca, idx = self.detector.run(frame)
for i, face in enumerate(faces):
e, t, d, b = (int(face.left()), int(face.top()), int(face.right()), int(face.bottom()))
cv2.rectangle(frame, (e, t), (d, b), (0, 255, 255), 2)
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
self.image = Image.fromarray(cv2image)
imgtk = ImageTk.PhotoImage(image=self.image)
self.painel.imgtk = imgtk
self.painel.config(image=imgtk)
self.janela.after(self.delay, self.update)
def take_picture(self):
cou = 1 # counter of pictures
start = time.clock() # starts the time
ret, frame = self.cam.read()
if ret:
faces, confianca, idx = self.detector.run(frame)
secs = (time.clock() - start) # count seconds
for i, face in enumerate(faces):
e, t, d, b = (int(face.left()), int(face.top()), int(face.right()), int(face.bottom()))
cv2.rectangle(frame, (e, t), (d, b), (0, 255, 255), 2)
if secs > 3:
imgfinal = cv2.resize(frame, (750, 600))
cv2.imwrite("fotos/pessoa." + str(id[0][0]) + "." + str(cou) + ".jpg", imgfinal)
print("Foto " + str(cou) + " tirada")
cou += 1
start = time.clock() # reset the counter of seconds
if cou > 3:
# here is where the thread should stop
# Creates the window
Tela(tkinter.Tk())
using time.sleep() will freeze your gui, with tkinter you can use after() which will call your method after x seconds, below is an example of how to call a function 4 times every 2 seconds, and you can use this idea in your application
import tkinter as tk
class App():
def __init__(self):
self.root = tk.Tk()
self.label = tk.Label(text="Anything")
self.label.pack()
self.counter = 0
self.take_picture(repeates=4, seconds=2) # our desired function
self.root.mainloop()
def take_picture(self, repeates=0, seconds=1):
if repeates:
self.counter = repeates
if self.counter == 0:
print('no more execution')
self.label.configure(text='Done, no more execution')
return
# doing stuff
text = f'function counting down # {self.counter}'
self.label.configure(text=text)
# schedule another call to this func using after()
self.root.after(seconds * 1000, self.take_picture, 0, seconds)
self.counter -= 1 # our tracker
app=App()
credits to this answer
You should use time.sleep(). It takes an integer as an parameter and waits that many seconds and then the code resumes running.

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 ()

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

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()

The item configure method didn't work in Tkinter

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()

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