Why does gtk.main_quit() not work? - python-2.7

I'm new here. :)
I am having trouble emigrating from GTK+ in C to PyGTK, specifically I do not understand why (in this specific context of a pause/play button) gtk.main_quit() does nothing even though it is called.
In my working example, after you press "Play", press "Pause" and then try to use the "Quit" button. On my end the "Quit" button, even though it calls gtk.main_quit() (and prints stuff after it to prove it does), will not have any effect on the program. Any ideas on what I am doing wrong?
Here it is:
import pygtk
pygtk.require('2.0')
import gtk, gobject, cairo
import time
global paused
paused = False
running = False
def some_stuff(widget, data=None):
global paused
global running
x = 0
if running:
return
running = True
paused = False
while True:
x += 1
print x
while gtk.events_pending():
gtk.main_iteration(False)
while paused:
while gtk.events_pending():
gtk.main_iteration(False)
running = False
paused = False
def pause_stuff(widget, data=None):
global paused
paused = not paused
def force_quit(widget, data=None):
print 'before'
gtk.main_quit() # doesn't work, gtk main loop keeps running
#exit() # obviously works to kill everything
print 'after'
def main():
window = gtk.Window()
window.connect("delete-event", force_quit)
window.show()
vbox = gtk.VBox(True, 3)
vbox.show()
window.add(vbox)
button = gtk.Button("Play")
button.connect("clicked", some_stuff, None)
vbox.add(button)
button.show()
button = gtk.Button("Pause")
button.connect("clicked", pause_stuff, None)
vbox.add(button)
button.show()
button = gtk.Button("Quit")
button.connect("clicked", force_quit, None)
vbox.add(button)
button.show()
window.present()
gtk.main()
return 0
if __name__ =="__main__":
main()

gtk.main_quit() instructs the GTK main loop to terminate. It has no effect in your code because your some_stuff() function implements its own version of the main loop, unaffected by calls to gtk.main_quit(). Since the outer while never exits, once the Play button is clicked, you never return into the real GTK main loop.
A correct GTK program does not reimplement the main loop like some_stuff() does, precisely to avoid such problems, and also to avoid busy-looping while nothing is going on. The while gtk.events_pending(): gtk.main_iteration() idiom should be used very sparingly, only when absolutely necessary (updating a progress bar comes to mind).

Adding this as answer based on the comment to the other answer (since this is a bit long for a comment). Let's assume that one game iteration is fast and you can do one in gtk main thread without hurting the UI.
Make a function that runs one single iteration of your game (to begin with you can just print something). The function should be an "idle handler callback" (see below). It should return True, except if the game is paused or stopped: in that case it should return False (meaning the function should not be called again).
In Play() add an idle handler for the iteration function
In Pause(), update the paused variable, and if unpausing also add an idle handler like in Play()
This way the iteration function keeps getting called after Play is called (but the UI is also updated). after Pause the iteration function is still called once, but it will return False and will not be called again.

Related

Pygame - "error: display Surface quit"

I've made a menu screen where clicking on a button leads to a different screen in the same window.
def main():
import pygame, random, time
pygame.init()
size=[800, 600]
screen=pygame.display.set_mode(size)
pygame.display.set_caption("Game")
done=False
clock=pygame.time.Clock()
while done==False:
for event in pygame.event.get():
pos = pygame.mouse.get_pos()
if event.type == pygame.QUIT:
done=True
break
if button_VIEW.collidepoint(pos):
if event.type == pygame.MOUSEBUTTONDOWN:
print("VIEW.")
view()
break
screen.fill(black)
...
def view():
done=False
clock=pygame.time.Clock()
while done==False:
for event in pygame.event.get():
pos = pygame.mouse.get_pos()
if event.type == pygame.QUIT:
done=True
break
...
If possible, I'd like to know how I can avoid the error:
screen.fill(black)
error: display Surface quit
>>>
After looking at other questions on here, I tried adding breaks to the exits of any loops, but still the error occurs.
I understand the issue is that the program is trying to execute screen.fill(black) after the window has been closed, but I have no further ideas on how to prevent the error.
I appreciate any help. Sorry if it seems simple.
Several possibilities:
end the process (with e.g. sys.exit()) in the view function. Not ideal.
return a value from the view function to indicate that the application shoud end (e.g. return done), and check for that return value in the main function (if done: return). Better.
make done global and check for its value in the main function. I really would not like this solution.
my favourite: avoid multiple event loops altogether, so the problem solves itself (so you could just e.g. exit the main function with return).

Please fix my function ive tried everything but i cant get my while loop to end when the function is over

When i run this and type start it doesnt stop and return back to the console
#this code defines the menu:
def main():
print('this will be the main menu')
return(Menu == False)
#this was temporary code to prevent the menu from launching on start up this function will soon be used to start other functions from the 'console' onces i add a dictionary of functions
Menu = False
def Mcall(x):
Menu = True
while Menu == True:
Menu = main()
#this code is a console or menu that should start functions or end the program
while(True):
CP = raw_input("enter a comand:\n")
if CP == 'start':
Mcall('start')
Figgued it out!
I was trying to end the while loop while inside a function with Menu = False when it needed to be under the function on the loop's indentation. Why it doesent work this way im not sure but I bet it has something to do with global variables not being changed within a function.

How to do mouse hovering for group of buttons in python

I am just trying a mouse hovering function for a group of 5 buttons, mouse hovering color
setting is running fine with one button sample but it is not giving proper result
when I tried for multiple buttons, it is affecting only last binded button, can anyone
help me quick on what is wrong with my below code, I tried with bind and bind_all methods!
both are failing to give desired output.
import os
from Tkinter import *
import Tkinter as tk
global btnPlaceY
btnPlaceY=10
class App():
def __init__ (self):
root['width']=400
root['height']=600
root['bg']='blue'
self.btnGroupCreate()
def btnDisplaymessage1(self):
print "Function testing1"
def btnDisplaymessage2(self):
print "Function testing2"
def btnGroupCreate(self):
btnNameNameGroup="btnSample"
self.btnWidget={}
lstBtnTitle=['A','B','C','D','E']
lstBtnCommands=[ lambda:self.btnDisplaymessage1(),
lambda:self.btnDisplaymessage2() ]
for B in range(5):
btnName=btnNameNameGroup+lstBtnTitle[B]
global btnPlaceY
btnPlaceY=btnPlaceY+70
# Button commands
if B==0:
btnMenuCommand=lstBtnCommands[0]
if B==1:
btnMenuCommand=lstBtnCommands[1]
if B==2:
btnMenuCommand=lstBtnCommands[0]
if B==3:
btnMenuCommand=lstBtnCommands[1]
if B==4:
btnMenuCommand=lstBtnCommands[1]
if B==5:
btnMenuCommand=lstBtnCommands[0]
self.btnWidget[btnName]= tk.Button(root, width=4, height=2,text=lstBtnTitle[B],bg="gray",relief=FLAT,command=btnMenuCommand) #activebackground="red"
widget = self.btnWidget[btnName]
widget.bind_all("<Enter>", lambda event: event.widget.configure(bg="red"))
widget.bind_all("<Leave>", lambda event: event.widget.configure(bg="gray"))
self.btnWidget[btnName].pack()
self.btnWidget[btnName].place(x=40, y = btnPlaceY)
def make_callback(self,btnName):
print "make_callback"
root['bg']='green'
widget = self.btnWidget[btnName]
def callback(event):
print "callback"
widget.configure(bg="red")
root['bg']='green'
return callback
root=tk.Tk()
app = App()
root.mainloop()
The problem is that in Python the closures such as your lambda function, close over the names in the surrounding block, not the values. As such, the value of btnName as observed in the lambda depends on when it is executed - as the events can occur only after the loop is exited, the btnName is the last one seen.
There are many ways to avoid this problem, one being the Kevin's default value trick. However, the default value approach is prone to errors - now the function accepts 2 arguments instead of 1 and can be called erroneously in some context.
Thus a better approach is to use a callback maker:
def make_callback(btnName):
def callback(event):
self.btnWidget[btnName].configure(bg="red"))
return callback
self.btnWidget[btnName].bind_all("<Enter>", make_callback(btnName))
However, one notices that the btnWidget is repeated, so it is possible to do this into
widget = self.btnWidget[btnName]
def make_callback(btnName):
def callback(event):
widget.configure(bg="red")
return callback
widget.bind_all("<Enter>", make_callback(widget))
Which could be written with lambdas as:
widget = self.btnWidget[btnName]
widget.bind_all("<Enter>",
(lambda widget: lambda event: widget.configure(bg="red"))(widget))
However, in this case none of this trickery is necessary as the only thing needed in callback is the widget on which the event occurs. And that is readily available in the TkInter events in the attribute widget, so really all you need to do is
widget = self.btnWidget[btnName]
widget.bind_all("<Enter>", lambda event: event.widget.configure(bg="red"))
widget.bind_all("<Leave>", lambda event: event.widget.configure(bg="yellow"))
self.btnWidget[btnName].bind_all("<Enter>", lambda event: self.btnWidget[btnName].configure(bg="red"))
self.btnWidget[btnName].bind_all("<Leave>", lambda event: self.btnWidget[btnName].configure(bg="yellow"))
Inside the lambda expressions on each of these lines, btnName will have the last value that it held during the loop, rather than the value it had when you bound the event. It will be "btnSampleE", even for buttons A through D. Additionally, you should be using bind instead of bind_all for widget-specific events.
The typical way to force "early binding" behavior, so that btnName keeps its current value, is to supply a default argument to the lambda, containing the value you need.
self.btnWidget[btnName].bind("<Enter>", lambda event, x = btnName: self.btnWidget[x].configure(bg="red"))
self.btnWidget[btnName].bind("<Leave>", lambda event, x = btnName: self.btnWidget[x].configure(bg="yellow"))
You can use whatever variable name you want for this argument; you could even call it btnName, as the local value will overshadow the nonlocal one.
self.btnWidget[btnName].bind("<Enter>", lambda event, btnName=btnName: self.btnWidget[btnName].configure(bg="red"))
self.btnWidget[btnName].bind("<Leave>", lambda event, btnName=btnName: self.btnWidget[btnName].configure(bg="yellow"))

multiple tasks on tkinter canvas?

I am trying to update canvas background at the same time running a binding event.
(from code)In do_popup popup menu will be implemented and conti will continuously change the canvas background color. how can i use popup option while canvas is updating continuously.
Sample code:
from Tkinter import *
root = Tk()
def do_popup(event,w2):
print w2 # inplace of print some popupmenu will be implemented
def conti():
idt=1
while idt==1:
w.config(bg="red") # in place of red it will be a random color
w.update_idletasks()
w= Canvas(root, width=600, height=600)
w.grid(row=0, column=0)
line1 = w.create_line(200,200,300,300, width=10, tags="line1", fill="black")
w.tag_bind(line1, "<Button-3>", lambda e, w2="test1" :do_popup(e,w2))
f = Frame(root)
f.grid(row=0, column=1, sticky=N)
f1=Button(f, text='visual', command=lambda :conti())
f1.grid(row=0, column=1,columnspan=1, sticky=W+N+S+E)
mainloop()
will multiprocessing work?
I am using windows 7 32 bit with python 2.7.3
Thanks in advance
When your script enters the mainloop then the events are executed.
To make reoccurring updates I like to do this:
def conti():
try:
w.config(bg="red") # in place of red it will be a random color
finally:
# start conti after 10 milliseconds,
root.after(10, conti)
# could also be 0ms to handle events
root.after(0, conti)
You can see root.mainloop as
while not (root.quit was called):
root.update()
This way wou can do:
root.quit()
and conti automatically stops.
There is no concurrency as with threads in mainloops.
But you can put a mainloop() somewhere when you create an own dialog box and conti will go on.
If you use the modules tkMessageBox(Python2) or tkinter.messagebox(Python3) then you conti should run while the dialog is open.
Does this answer your questions?
PS: do root.protocol("WM_DELETE_WINDOW", root.quit) to make the mainloop end when you close the window.

Tkinter bind executes immediately when script is run

I've laid out a frame, part of which is:
ticketID = IntVar()
ticketID.set(ticket)
ticketfield = Label(titleframe, text = 'Ticket : ')
ticketfield.grid(row=0, column=0)
ticketfieldentry = Entry(titleframe, textvariable=ticketID)
ticketfieldentry.grid(row=0, column=1)
ticketfieldentry.bind("<Double-Button-1>", searchforticket(ticketfieldentry.get()))
And a placeholder:
def searchforticket(ticket):
searchforstring = "This would search for ticket %s" % ticket
tkMessageBox.showinfo('Search by ticket', message = searchforstring)
Ticket is passed to the script at run time, or is assumed None. What I thought the above would do is create an Entry box that would display the ticket number, or could have one entered directly. After being entered, double clicking would bring up the searchforticket function.
However, when I run the script, searchforticket is run immediately, with whatever is being assigned to ticket and then after I click past the tkMessageBox is when the window containing the ticketfieldentry and everything else renders.
I am not seeing at all what could be causing this, but I assume it is something dead obvious to someone else.
searchforticket(ticketfieldentry.get() cause immediate call. Change as follow:
ticketfieldentry.bind("<Double-Button-1>", lambda e: searchforticket(ticketfieldentry.get()))