I'm new to pyglet, but have needed to learn it quickly in order to complete a school project. Basically, I have my first slide with the images menu and preview on it, and a second slide that has the end_screen image on it. The showing of the first slide works just fine, though the program wont let me transition into the second slide when I press a key. No Error message, It just doesnt do anything when I try to draw the second slide.
import pyglet
from pyglet.window import key
import time
slide = 1
preview_image = pyglet.image.load('untitled (3).jpg')
menu_image = pyglet.image.load('untitled (4).jpg')
end_screen_image = pyglet.image.load('untitled (5).jpg')
preview = pyglet.sprite.Sprite(preview_image, x=0, y=0)
menu = pyglet.sprite.Sprite(menu_image, x=540, y=0)
end_screen = pyglet.sprite.Sprite(end_screen_image, x=270, y=0)
window = pyglet.window.Window(1180, 630)
def update():
global menu
global window
global preview
global end_screen
global slide
if slide == 1:
window.clear()
preview.draw()
menu.draw()
elif slide == 2:
window.clear()
end_screen.draw()
#window.event
def on_key_press(symbol, modifiers):
global slide
slide = 2
update()
#window.event
def on_draw():
window.clear()
preview.draw()
menu.draw()
pyglet.app.run()
For some reason it just wont work. I've tried different kinds of update functioons but none of them worked either. Thanks in advance!
Main issue here is the concept of update and how you'd normally expect code to execute.
I'm guessing you're used to code being called as you write it, meaning that you think "If I press a button, I want update() to be called instead". And that's true, partially.
The problem you're experiencing is that Pyglet is trying to render things as fast as possible, and it does so by calling on_draw() as often as needed. So when you execute update() - a fraction of a second later on_draw() will be called again, and again.. and again. And the first line you have in on_draw is window.clear() - which clears the window and any changes made in update(). Hopefully that makes sense.
Instead, you probably want your "which thing am i showing"-logic in the on_draw every render. Altho this will be pretty slow, hopefully this convey the logic of how the automatic rendering process works and why your logic from school assignments doesn't necessarily translate well into projects where there's "background tasks" running.
import pyglet
from pyglet.window import key
import time
preview = pyglet.sprite.Sprite(pyglet.image.load('untitled (3).jpg'), x=0, y=0)
menu = pyglet.sprite.Sprite(pyglet.image.load('untitled (4).jpg'), x=540, y=0)
end_screen = pyglet.sprite.Sprite(pyglet.image.load('untitled (5).jpg'), x=270, y=0)
window = pyglet.window.Window(1180, 630)
#window.event
def on_key_press(symbol, modifiers):
global slide
slide = 2
#window.event
def on_draw():
window.clear()
if slide == 1:
window.clear()
preview.draw()
menu.draw()
elif slide == 2:
window.clear()
end_screen.draw()
pyglet.app.run()
Oh and btw, no need for global on variables in functions like your update() function. Unless you assign something to variables (menu = ...) they're going to be global by default (For instance, see in on_key_press where you update slide with slide = 2, in those cases you need to globalize first). Just a friendly reminder how global scope works in Python and it's functions - giving you some more wiggle room to write less code and achieve the same thing. So remember, = means you need to make them global, otherwise don't bother :)
I also made the code a bit "smaller", since you're not using preview_image i went ahead and passed the image-loading-result straight into Sprite() for preview for instance, saving 3 lines of code making it arguably slightly less code and thus more readable. It's a matter of taste tho, can revert it if you feel like it.
I have a program that will show an image on screen when a hotkey is pressed on my keyboard. Depending on which key was pressed a different image will be shown.
After 3 seconds of no input my root Tk gets withdraw()n so there is nothing on screen.
Now, whenever I want to show an image I call this function:
def drawOverlay(self, index):
self.canvas.itemconfig(self.overlayWidget, image=self.overlayImages[index])
self.deiconify()
The problem is that I see the old image for a few milliseconds before it is replaced.
I tried to find out why there is the delay eventhough I deiconify() only after i switched the image and came across this answer which suggested to call deiconify() using the root's after(). I tried that and when it still didn't work I played around with the call and found out that calling
self.after(1000, self.deiconify())
causes the old image to appear for exactly 1 second after which it is replaced with the new one.
This leads me to believe that the Tk or the Canvas is unable to update while it is withdraw()n. Is there any way around this? I'd rather have a short delay before the image is displayed than the old image flashing on the screen for a few frames but I have no idea how to accomplish this.
Edit: So I'm an idiot for actually calling deiconify instead of just passing it to after because of the parantheses.
That does fix the old image appearing for the duration but does not fix the flashing.
Edit2: I managed to reproduce this problem with the following code.
Pressing any key on the keyboard will make a green rectagle appear. Waiting for it to vanish and then pressing another key will make a red rectangle appear.
Only sometimes can you see the flashes happening so try for a couple times. I did not manage to reproduce when -transparentcolor isnt set but I can't tell if this is due to the option being the problem or due to the reduced rendering times making the problem almost imperceptable. It is also a bit easier to see then overrideredirect is set.
Edit3: I have worked the code down further by using actual images. Even without the keypress or additional event callbacks this code produces a black flash before displaing the image. -transparentcolor and overrideredirect(True) seem to be vital to reproducing this. Manually calling update() before reduces the frequency of this ocurring but still causes flashing consistently on larger images. This points to rendering time being one of the factors.
overlay.png
overlayRaw.png
from tkinter import Tk, Canvas
from PIL import ImageTk, Image
TRANSCOLOR = "blue"
IMAGE_SMALL = "overlay.png"
IMAGE_LARGE = "overlayRaw.png"
class Overlay(Tk):
def __init__(self):
Tk.__init__(self)
self.rawImage = Image.open(IMAGE_SMALL)
self.image = ImageTk.PhotoImage(self.rawImage)
self.canvas = Canvas(self, width = self.rawImage.size[0], height = self.rawImage.size[1])
self.canvas.pack()
self.wm_attributes('-transparentcolor', TRANSCOLOR) # If disabled stops the flashes from ocurring even on large images.
self.overrideredirect(True) # If disabled the Windows animation for opening windows plays. Stops the flashing from ocurring
self.withdraw()
self.overlayWidget = self.canvas.create_image(0, 0, image = self.image, anchor = "nw")
self.deiconify() # Flashes Clearly Everytime
## self.update_idletasks()
## self.deiconify() # Only Flashes Sometimes. Always flashes on large images
## self.update()
## self.deiconify() # Only Flashes Sometimes. Always flashes on large images
## self.after(0, self.deiconify) # Flashes Clearly everytime
## self.after(200, self.deiconify) # Only Flashes Sometimes. Always flashes on large images
## self.update()
## self.after(200, self.deiconify) # Flashes Clearly Everytime
o = Overlay()
o.mainloop()
I don't see much that could cause a flicker. My recommendation would be to add an explicit call to self.update before the call to deiconify happens. That should instruct tkinter to redraw everything on the canvas. However, that won't help if tkinter on your platform defers drawing until the window is mapped.
Try replacing this:
self.after(0,self.deiconify)
self.after(2000, self.hideOverlay)
with this:
self.update()
self.deiconify()
Till now, I used to end my Tkinter programs with: tk.mainloop(), or nothing would show up! See example:
from Tkinter import *
import random
import time
tk = Tk()
tk.title = "Game"
tk.resizable(0,0)
tk.wm_attributes("-topmost", 1)
canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0)
canvas.pack()
class Ball:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
self.canvas.move(self.id, 245, 100)
def draw(self):
pass
ball = Ball(canvas, "red")
tk.mainloop()
However, when tried the next step in this program (making the ball move by time), the book am reading from, says to do the following. So I changed the draw function to:
def draw(self):
self.canvas.move(self.id, 0, -1)
and add the following code to my program:
while 1:
ball.draw()
tk.update_idletasks()
tk.update()
time.sleep(0.01)
But I noticed that adding this block of code, made the use of tk.mainloop() useless, since everything would show up even without it!!!
At this moment I should mention that my book never talks about tk.mainloop() (maybe because it uses Python 3) but I learned about it searching the web since my programs didn't work by copying book's code!
So I tried doing the following that would not work!!!
while 1:
ball.draw()
tk.mainloop()
time.sleep(0.01)
What's going on? What does tk.mainloop()? What does tk.update_idletasks() and tk.update() do and how that differs from tk.mainloop()? Should I use the above loop?tk.mainloop()? or both in my programs?
tk.mainloop() blocks. It means that execution of your Python commands halts there. You can see that by writing:
while 1:
ball.draw()
tk.mainloop()
print("hello") #NEW CODE
time.sleep(0.01)
You will never see the output from the print statement. Because there is no loop, the ball doesn't move.
On the other hand, the methods update_idletasks() and update() here:
while True:
ball.draw()
tk.update_idletasks()
tk.update()
...do not block; after those methods finish, execution will continue, so the while loop will execute over and over, which makes the ball move.
An infinite loop containing the method calls update_idletasks() and update() can act as a substitute for calling tk.mainloop(). Note that the whole while loop can be said to block just like tk.mainloop() because nothing after the while loop will execute.
However, tk.mainloop() is not a substitute for just the lines:
tk.update_idletasks()
tk.update()
Rather, tk.mainloop() is a substitute for the whole while loop:
while True:
tk.update_idletasks()
tk.update()
Response to comment:
Here is what the tcl docs say:
Update idletasks
This subcommand of update flushes all currently-scheduled idle events
from Tcl's event queue. Idle events are used to postpone processing
until “there is nothing else to do”, with the typical use case for
them being Tk's redrawing and geometry recalculations. By postponing
these until Tk is idle, expensive redraw operations are not done until
everything from a cluster of events (e.g., button release, change of
current window, etc.) are processed at the script level. This makes Tk
seem much faster, but if you're in the middle of doing some long
running processing, it can also mean that no idle events are processed
for a long time. By calling update idletasks, redraws due to internal
changes of state are processed immediately. (Redraws due to system
events, e.g., being deiconified by the user, need a full update to be
processed.)
APN As described in Update considered harmful, use of update to handle
redraws not handled by update idletasks has many issues. Joe English
in a comp.lang.tcl posting describes an alternative:
So update_idletasks() causes some subset of events to be processed that update() causes to be processed.
From the update docs:
update ?idletasks?
The update command is used to bring the application “up to date” by
entering the Tcl event loop repeatedly until all pending events
(including idle callbacks) have been processed.
If the idletasks keyword is specified as an argument to the command,
then no new events or errors are processed; only idle callbacks are
invoked. This causes operations that are normally deferred, such as
display updates and window layout calculations, to be performed
immediately.
KBK (12 February 2000) -- My personal opinion is that the [update]
command is not one of the best practices, and a programmer is well
advised to avoid it. I have seldom if ever seen a use of [update] that
could not be more effectively programmed by another means, generally
appropriate use of event callbacks. By the way, this caution applies
to all the Tcl commands (vwait and tkwait are the other common
culprits) that enter the event loop recursively, with the exception of
using a single [vwait] at global level to launch the event loop inside
a shell that doesn't launch it automatically.
The commonest purposes for which I've seen [update] recommended are:
Keeping the GUI alive while some long-running calculation is
executing. See Countdown program for an alternative. 2) Waiting for a window to be configured before doing things like
geometry management on it. The alternative is to bind on events such
as that notify the process of a window's geometry. See
Centering a window for an alternative.
What's wrong with update? There are several answers. First, it tends
to complicate the code of the surrounding GUI. If you work the
exercises in the Countdown program, you'll get a feel for how much
easier it can be when each event is processed on its own callback.
Second, it's a source of insidious bugs. The general problem is that
executing [update] has nearly unconstrained side effects; on return
from [update], a script can easily discover that the rug has been
pulled out from under it. There's further discussion of this
phenomenon over at Update considered harmful.
.....
Is there any chance I can make my program work without the while loop?
Yes, but things get a little tricky. You might think something like the following would work:
class Ball:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
self.canvas.move(self.id, 245, 100)
def draw(self):
while True:
self.canvas.move(self.id, 0, -1)
ball = Ball(canvas, "red")
ball.draw()
tk.mainloop()
The problem is that ball.draw() will cause execution to enter an infinite loop in the draw() method, so tk.mainloop() will never execute, and your widgets will never display. In gui programming, infinite loops have to be avoided at all costs in order to keep the widgets responsive to user input, e.g. mouse clicks.
So, the question is: how do you execute something over and over again without actually creating an infinite loop? Tkinter has an answer for that problem: a widget's after() method:
from Tkinter import *
import random
import time
tk = Tk()
tk.title = "Game"
tk.resizable(0,0)
tk.wm_attributes("-topmost", 1)
canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0)
canvas.pack()
class Ball:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
self.canvas.move(self.id, 245, 100)
def draw(self):
self.canvas.move(self.id, 0, -1)
self.canvas.after(1, self.draw) #(time_delay, method_to_execute)
ball = Ball(canvas, "red")
ball.draw() #Changed per Bryan Oakley's comment
tk.mainloop()
The after() method doesn't block (it actually creates another thread of execution), so execution continues on in your python program after after() is called, which means tk.mainloop() executes next, so your widgets get configured and displayed. The after() method also allows your widgets to remain responsive to other user input. Try running the following program, and then click your mouse on different spots on the canvas:
from Tkinter import *
import random
import time
root = Tk()
root.title = "Game"
root.resizable(0,0)
root.wm_attributes("-topmost", 1)
canvas = Canvas(root, width=500, height=400, bd=0, highlightthickness=0)
canvas.pack()
class Ball:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
self.canvas.move(self.id, 245, 100)
self.canvas.bind("<Button-1>", self.canvas_onclick)
self.text_id = self.canvas.create_text(300, 200, anchor='se')
self.canvas.itemconfig(self.text_id, text='hello')
def canvas_onclick(self, event):
self.canvas.itemconfig(
self.text_id,
text="You clicked at ({}, {})".format(event.x, event.y)
)
def draw(self):
self.canvas.move(self.id, 0, -1)
self.canvas.after(50, self.draw)
ball = Ball(canvas, "red")
ball.draw() #Changed per Bryan Oakley's comment.
root.mainloop()
while 1:
root.update()
... is (very!) roughly similar to:
root.mainloop()
The difference is, mainloop is the correct way to code and the infinite loop is subtly incorrect. I suspect, though, that the vast majority of the time, either will work. It's just that mainloop is a much cleaner solution. After all, calling mainloop is essentially this under the covers:
while the_window_has_not_been_destroyed():
wait_until_the_event_queue_is_not_empty()
event = event_queue.pop()
event.handle()
... which, as you can see, isn't much different than your own while loop. So, why create your own infinite loop when tkinter already has one you can use?
Put in the simplest terms possible: always call mainloop as the last logical line of code in your program. That's how Tkinter was designed to be used.
I'm using an MVC / MVA design pattern, with multiple types of "views". One type is a "GuiView", which is a Tk window. I pass a view reference to my window object which does things like link buttons back to view functions (which the adapter / controller class also calls).
In order to do that, the view object constructor needed to be completed prior to creating the window object. After creating and displaying the window, I wanted to do some initial tasks with the view automatically. At first I tried doing them post mainloop(), but that didn't work because mainloop() blocked!
As such, I created the window object and used tk.update() to draw it. Then, I kicked off my initial tasks, and finally started the mainloop.
import Tkinter as tk
class Window(tk.Frame):
def __init__(self, master=None, view=None ):
tk.Frame.__init__( self, master )
self.view_ = view
""" Setup window linking it to the view... """
class GuiView( MyViewSuperClass ):
def open( self ):
self.tkRoot_ = tk.Tk()
self.window_ = Window( master=None, view=self )
self.window_.pack()
self.refresh()
self.onOpen()
self.tkRoot_.mainloop()
def onOpen( self ):
""" Do some initial tasks... """
def refresh( self ):
self.tkRoot_.update()
For some reason, I can't get pyglet to draw sprites. Here's my code:
import pyglet
game = pyglet.window.Window(640, 480, "I'm a window")
batch = pyglet.graphics.Batch()
pyglet.resource.path = ["."]
pyglet.resource.reindex()
image = pyglet.resource.image("hextile.png")
pyglet.sprite.Sprite(image, x=200, y=300, batch=batch)
pyglet.text.Label('DING', font_name='Arial', font_size=24, x=100, y=100, batch=batch)
#game.event
def on_draw():
game.clear()
batch.draw()
#image.blit(0, 0)
pyglet.app.run()
Now, when I draw the batch, the text label is shown correctly. I see "DING" on the window. However, the image "hextile.png" is not shown. I tried drawing the sprite independently, but that didn't work either. Blitting the image (as shown in the commented line), however, seems to work just fine, but obviously that's not quite the functionality I'm after here. I can't figure this one out. What am I missing?
Assuming you and your friends have ATI graphics cards:
Sprite.draw() uses the v2i format and VertexDomain.draw() internally. For some reason this combination doesn't work on Windows Vista/7 Catalyst drivers 11.9 and above, and consequently Sprite drawing fails as well. See also: pyglet vertex list not rendered (AMD driver?)
There is a pyglet issue you might want to follow: http://code.google.com/p/pyglet/issues/detail?id=544
Your options for the moment seem to be either to patch pyglet.sprite.Sprite as mentioned in the third comment on that issue or downgrade your video driver.
Update: No need to patch Sprite or downgrade your video driver. This problem seems to be fixed with Catalyst 12.4 (video driver 8.961.0.0).
The sprite is getting garbage collected because you don't hold a reference to it. Do this:
sprite = pyglet.sprite.Sprite(image, x=200, y=300, batch=batch)
For what it's worth, I prefer using a subclass of Window, like this: (this code works for me too)
import pyglet
class Window(pyglet.window.Window):
def __init__(self, *args, **kwargs):
super(Window, self).__init__(*args, **kwargs)
self.batch = pyglet.graphics.Batch()
image = pyglet.resource.image('hextile.png')
self.sprite = pyglet.sprite.Sprite(image, batch=self.batch)
def on_draw(self):
self.clear()
self.batch.draw()
def main():
window = Window(width=640, height=480, caption='Pyglet')
pyglet.app.run()
if __name__ == '__main__':
main()
I'm trying to use gtk.window.get_size(), but it always just returns the default width and height. The documentation says
The get_size() method returns a tuple containing the current width and
height of the window. If the window is not on-screen, it returns the
size PyGTK will suggest to the window manager for the initial window
size. The size obtained by the get_size() method is the last size
received in a configure event, that is, PyGTK uses its locally-stored
size, rather than querying the X server for the size. As a result, if
you call the resize() method then immediately call the get_size()
method, the size won't have taken effect yet. After the window manager
processes the resize request, PyGTK receives notification that the
size has changed via a configure event, and the size of the window
gets updated.
I've tried resizing the window manually and waiting a minute or so, but I still get the default width and height.
I'm trying to use this to save the window size on quit so that I can restore it on start. Is there a better way to do this?
Here's the code snipit I have for my main quit.
def on_main_window_destroy(self, widget, data=None):
if self.view.current_view.layout == 'list':
self.view.current_view.set_column_order()
width = self.main_window.get_size()[0]
height = self.main_window.get_size()[1]
#test statement
print (width, height)
self.prefs.set_size_prefs(width, height)
self.prefs.set_view_prefs(self.view.current_view.media, self.view.current_view.layout)
gtk.main_quit()
I think I understand what's happening now. This is inside the destroy signal, so by the time the code gets called, the window is already gone. Is there a more canonical way of handling window resizing? I was hoping to avoid handling resize events everytime the user resized the window.
This seems to fix your problem:
import gtk
def print_size(widget, data=None):
print window.get_size()
def delete_event(widget, data=None):
print window.get_size()
return False
def destroy(widget, data=None):
gtk.main_quit()
window = gtk.Window()
window.connect('delete_event', delete_event)
window.connect('destroy', destroy)
button = gtk.Button(label='Print size')
button.connect('clicked', print_size)
window.add(button)
window.show_all()
gtk.main()
I think the key is calling get_size on the delete_event signal rather than the destroy signal. If you do it on the destroy signal, it's like you describe, it just returns the default size.
Try running:
while gtk.events_pending():
gtk.main_iteration_do(False)
right before calling window.get_size()