I'm using tk for a project's GUI. I have some very strange behavior with it, but only as a built executable on windows. Essentially I have a function launches a new process and needs to update some GUI elements after it completes. This works fine on OS X and Windows running interpreted. It works fine as a OS X binary. But as a Windows binary, the code causes a second main window to appear for unknown reason.
The app is launched via:
root = tk.Tk()
root.withdraw()
app = impy()
root.mainloop()
where
class impy(tk.Toplevel):
Then sometime later a user clicks a button which causes this to run:
dialog = Progress_Dialog()
dialog.set_text('Implosion generation...')
dialog.update_idletasks()
# Use helper function:
parent_conn, child_conn = Pipe()
p = Process(target=ImplosionRunner.run, args=(child_conn,))
self.processes.append(p)
# start the process and send implosion:
p.start()
try:
parent_conn.send(self.imp)
except:
raise Exception('Implosion object passed to ImplosionRunner is not pickleable!')
obj = None
# Loop while the process is active:
def callback():
nonlocal dialog, p, parent_conn
if dialog.cancelled:
dialog.withdraw()
p.terminate()
return
# Try to receive from the Pipe:
if parent_conn.poll():
# Update the progress, or end otherwise:
obj = parent_conn.recv()
if isinstance(obj, Exception):
from tkinter.messagebox import showerror
showerror('Error!', 'A problem occurred generating the implosion (class '+self.imp.name()+')\n'+obj.__str__())
dialog.withdraw()
p.terminate()
return
elif isinstance(obj, float):
dialog.set(100*obj)
elif isinstance(obj, Implosion):
# Pass info back to the main app:
self.imp = obj
self.after(10, self.__postImplosion__)
dialog.withdraw()
p.terminate()
return
self.after(25, callback)
self.after(10, callback)
The callback loop eventually completes via the elif isinstance(obj, Implosion) clause. Then those functions are all called. Something that they do causes a second Toplevel window to appear which is essentially a clone of the main window. The UI operations are applied to the clone. The __postImplosion__ method is just:
for key in self.modControlChecks.keys():
self.modControlChecks[key].configure(state=tk.NORMAL)
# Run any modules that were already checked in refresh mode
for key in self.modControlChecks.keys():
self.modRedisplay[key] = (self.modControlVars[key].get() == 1)
for mod in allModules():
if self.modRedisplay[mod.name()]:
self.__runModule__(mod.name())
it just has to loop over some check boxes and enable them. I'm pretty baffled since this is only a problem with Windows binaries. Any thoughts?
Update: Some more troubleshooting: The extra main window appears immediately after p.start() is called. So this seems to be some weird behavior. Why can't I launch a process without an extra Tk window appearing?
OK, as usual, the solution is that I should have RTFM. According to the docs there is a magic function that must be called to fix weird problems with multiprocessing when frozen on Windows.
if __name__ == "__main__":
freeze_support()
root = tk.Tk()
root.withdraw()
app = impy()
root.mainloop()
Related
I have multiple files in this project, and, in this particular case, I call a function from another file.
from botmain import Execute
def EXECUTE_BOT(first_line, last_line, filename, padrao_comunicacao, concentrador, varredura, eventos, osci, param, ons,
empresa, grpparam, cnldgt, addparams):
time.sleep(5)
Execute(first_line,
last_line,
filename,
padrao_comunicacao,
concentrador,
varredura,
eventos,
osci,
param,
ons,
empresa,
grpparam,
cnldgt,
addparams)
if __name__ == '__main__':
root = Tk()
root.grab_set()
root.resizable = False
frm = ttk.Frame(root, padding=20)
frm.grid()
ttk.Button(frm, text="EXECUTAR", command=lambda: EXECUTE_BOT(wdg_firstLine.get(),
wdg_lastLine.get(),
wdg_sheetdirectory.cget("text"),
wdg_padrao_comunicacao.get(),
wdg_conc.get(),
radio_var.get(),
radio_eve.get(),
radio_osc.get(),
radio_param.get(),
radio_ons.get(),
wdg_empresa.get(),
wdg_grpParam.get(),
wdg_cnlDgt.get(),
converAddParam(addparam_list))).grid(column=5, row=22)
This code works just fine when I run it on Pycharm, but when I make it a .exe with pyinstaller, it stops working, and it opens another window every time I click on that button. I fixed this error by putting my code inside name == "main", but when I make it an .exe, it gets the same error than when I didn't have that.
Heeeeelp!!!
I had a similar issue, but when my multithread function was called it created a new root for each process. multiprocessing freeze_support() was the answer, read the docs here
if __name__ == '__main__':
multiprocessing.freeze_support() # to stop exe popups when threaded
I'm trying to make a python3 application for my Raspberry Pi 4B and I have the tkinter windows working fine, but need to add asynchronous handling to allow tkinter widgets to respond while processing asynchronous actions initiated by the window's widgets.
The test code is using asyncio and tkinter. However, without root.mainloop(), since asyncio loop.run_forever() is called at the end instead. The idea is that when the user clicks the main window's close box, RequestQuit() gets called to set the quitRequested flag and then when control returns to the event loop, root.after_idle(AfterIdle) would cause AfterIdle to be called, where the flag is checked and if true, the event loop is stopped, or that failing, the app is killed with exit(0).
The loop WM_DELETE_WINDOW protocol coroutine RequestQuit is somehow not getting called when the user clicks the main window close box, so the AfterIdle coroutine never gets the flag to quit and I have to kill the app by quitting XQuartz.
I'm using ssh via Terminal on MacOS X Big Sur 11.5.2, connected to a Raspberry Pi 4B with Python 3.7.3.
What have I missed here?
(I haven't included the widgets or their handlers or the asynchronous processing here, for brevity, since they aren't part of the problem at hand.)
from tkinter import *
from tkinter import messagebox
import aiotkinter
import asyncio
afterIdleProcessingIntervalMsec = 500 # Adjust for UI responsiveness here.
busyProcessing = False
quitRequested = False
def RequestQuit():
global quitRequested
global busyProcessing
if busyProcessing:
answer = messagebox.askquestion('Exit application', 'Do you really want to abort the ongoing processing?', icon='warning')
if answer == 'yes':
quitRequested = True
def AfterIdle():
global quitRequested
global loop
global root
if not quitRequested:
root.after(afterIdleProcessingIntervalMsec, AfterIdle)
else:
print("Destroying GUI at: ", time.time())
try:
loop.stop()
root.destroy()
except:
exit(0)
if __name__ == '__main__':
global root
global loop
asyncio.set_event_loop_policy(aiotkinter.TkinterEventLoopPolicy())
loop = asyncio.get_event_loop()
root = Tk()
root.protocol("WM_DELETE_WINDOW", RequestQuit)
root.after_idle(AfterIdle)
# Create and pack widgets here.
loop.run_forever()
The reason why your program doesn't work is that there is no Tk event loop, or its equivalent. Without it, Tk will not process events; no Tk callback functions will run. So your program doesn't respond to the WM_DELETE_WINDOW event, or any other.
Fortunately Tk can be used to perform the equivalent of an event loop as an asyncio.Task, and it's not even difficult. The basic concept is to write a function like this, where "w" is any tk widget:
async def new_tk_loop():
while some_boolean:
w.update()
await asyncio.sleep(sleep_interval_in_seconds)
This function should be created as an asyncio.Task when you are ready to start processing tk events, and should continue to run until you are ready to stop doing that.
Here is a class, TkPod, that I use as the basic foundation of any Tk + asyncio program. There is also a trivial little demo program, illustrating how to close the Tk loop from another Task. If you click the "X" before 5 seconds pass, the program will close immediately by exiting the mainloop function. After 5 seconds the program will close by cancelling the mainloop task.
I use a default sleep interval of 0.05 seconds, which seems to work pretty well.
When exiting such a program there are a few things to think about.
When you click on the "X" button on the main window, the object sets its app_closing variable to false. If you need to do some other clean-up, you can subclass Tk and over-ride the method close_app.
Exiting the mainloop doesn't call the destroy function. If you need to do that, you must do it separately. The class is a context manager, so you can make sure that destroy is called using a with block.
Like any asyncio Task, mainloop can be cancelled. If you do that, you need to catch that exception to avoid a traceback.
#! python3.8
import asyncio
import tkinter as tk
class TkPod(tk.Tk):
def __init__(self, sleep_interval=0.05):
self.sleep_interval = sleep_interval
self.app_closing = False
self.loop = asyncio.get_event_loop()
super().__init__()
self.protocol("WM_DELETE_WINDOW", self.close_app)
# Globally suppress the Tk menu tear-off feature
# In the following line, "*tearOff" works as documented
# while "*tearoff" does not.
self.option_add("*tearOff", 0)
def __enter__(self):
return self
def __exit__(self, *_x):
self.destroy()
def close_app(self):
self.app_closing = True
# I don't know what the argument n is for.
# I include it here because pylint complains otherwise.
async def mainloop(self, _n=0):
while not self.app_closing:
self.update()
await asyncio.sleep(self.sleep_interval)
async def main():
async def die_in5s(t):
await asyncio.sleep(5.0)
t.cancel()
print("It's over...")
with TkPod() as root:
label = tk.Label(root, text="Hello")
label.grid()
t = asyncio.create_task(root.mainloop())
asyncio.create_task(die_in5s(t))
try:
await t
except asyncio.CancelledError:
pass
if __name__ == "__main__":
asyncio.run(main())
Ctrl-C/SIGTERM/SIGINT seem to be ignored by tkinter. Normally it can be captured again with a callback. This doesn't seem to be working, so I thought I'd run tkinter in another thread since its mainloop() is an infinite loop and blocks. I actually also want to do this to read from stdin in a separate thread. Even after this, Ctrl-C is still not processed until I close the window. Here's my MWE:
#! /usr/bin/env python
import Tkinter as tk
import threading
import signal
import sys
class MyTkApp(threading.Thread):
def run(self):
self.root = tk.Tk()
self.root.mainloop()
app = MyTkApp()
app.start()
def signal_handler(signal, frame):
sys.stderr.write("Exiting...\n")
# think only one of these is needed, not sure
app.root.destroy()
app.root.quit()
signal.signal(signal.SIGINT, signal_handler)
Results:
Run the app
Ctrl-C in the terminal (nothing happens)
Close the window
"Exiting..." is printed and I get an error about the loop already having exited.
What's going on here and how can I make Ctrl-C from the terminal close the app?
Update: Adding a poll, as suggested, works in the main thread but does not help when started in another thread...
class MyTkApp(threading.Thread):
def poll(self):
sys.stderr.write("poll\n")
self.root.after(50, self.poll)
def run(self):
self.root = tk.Tk()
self.root.after(50, self.poll)
self.root.mainloop()
here's a working example that catches control c in the windows or from the command line. this was tested with 3.7.2 this seems simpler than the other solutions. I almost feel like I'm missing something.
import tkinter as TK
import signal
def hello():
print("Hello")
root = TK.Tk()
TK.Button(root, text="Hi", command=(hello)).pack( )
def handler(event):
root.destroy()
print('caught ^C')
def check():
root.after(500, check) # time in ms.
# the or is a hack just because I've shoving all this in a lambda. setup before calling main loop
signal.signal(signal.SIGINT, lambda x,y : print('terminal ^C') or handler(None))
# this let's the terminal ^C get sampled every so often
root.after(500, check) # time in ms.
root.bind_all('<Control-c>', handler)
root.mainloop()
Since your tkinter app is running in another thread, you do not need to set up the signal handler in the main thread and just use the following code block after the app.start() statement:
import time
while app.is_alive():
try:
time.sleep(0.5)
except KeyboardInterrupt:
app.root.destroy()
break
You can then use Ctrl-C to raise the KeyboardInterrupt exception to close the tkinter app and break the while loop. The while loop will also be terminated if you close your tkinter app.
Note that the above code is working only in Python 2 (as you use Tkinter in your code).
Proper CTRL-C & SIGINT Usage in Python
The problem is that you are exiting the main thread, so the signal handler is basically useless. You need to keep it running, in a while loop, or my personal preference, Events from threading module. You can also just catch the KeyboardInterrupt exception generated by the CTRL-C event, rather than dealing with signal handlers.
SIGINT in Tkinter
Using tkinter, you must have the tkinter app run in a separate thread, so that it doesn't interfere with the signal handler or KeyboardInterrupt exception. In the handler, to exit, you need to destroy then update tkinter root. Update allows the tkinter to update so that it closes, without waiting for mainloop. Otherwise, user has to click on the active window to activate mainloop.
# Python 3
from tkinter import *
from threading import Thread
import signal
class MyTkApp(Thread):
def run(self):
self.root = Tk()
self.root.mainloop()
def sigint_handler(sig, frame):
app.root.quit()
app.root.update()
app = MyTkApp()
# Set signal before starting
signal.signal(signal.SIGINT, sigint_handler)
app.start()
Note: SIGINTs can also be caught if you set handler in same thread as tkinter mainloop, but you need to make tkinter window active after the signal so that it's mainloop can run. There is no way around this unless you run in new thread.
More Information on Tkinter & Command Line Communication
For more on communicating between tkinter and the command line, see Using Tkinter Without Mainloop. Basically, you can use update method in your loop, and then communicate with other threads and processes, etc. I would personally NOT recommend this, as you are essentially doing the job of the python thread control system, which is probably opposite of what you want to do. (python has a process that runs all internal threads in one external thread, so you are not taking advantage of multitheading, unless using multiprocessing module)
# Python 2
from Tkinter import *
ROOT = Tk()
LABEL = Label(ROOT, text="Hello, world!")
LABEL.pack()
LOOP_ACTIVE = True
while LOOP_ACTIVE:
ROOT.update()
USER_INPUT = raw_input("Give me your command! Just type \"exit\" to close: ")
if USER_INPUT == "exit":
ROOT.quit()
LOOP_ACTIVE = False
else:
LABEL = Label(ROOT, text=USER_INPUT)
LABEL.pack()
I want to add a control terminal widget to my pure python+tkinter application similar to the python interpreter provided in Blender. It should be running within the same context (process) so the user can add features and control the application that is currently running from the control widget. Ideally I'd like it to also "hijack" stdout and stderr of the current application so it will report any problems or debugging information within the running application.
This is what I have come up with so far. The only problems are that it isn't responding to commands, and the thread doesn't stop when the user closes the window.
import Tkinter as tk
import sys
import code
from threading import *
class Console(tk.Frame):
def __init__(self,parent=None):
tk.Frame.__init__(self, parent)
self.parent = parent
sys.stdout = self
sys.stderr = self
self.createWidgets()
self.consoleThread = ConsoleThread()
self.after(100,self.consoleThread.start)
def write(self,string):
self.ttyText.insert('end', string)
self.ttyText.see('end')
def createWidgets(self):
self.ttyText = tk.Text(self.parent, wrap='word')
self.ttyText.grid(row=0,column=0,sticky=tk.N+tk.S+tk.E+tk.W)
class ConsoleThread(Thread):
def __init__(self):
Thread.__init__(self)
def run(self):
vars = globals().copy()
vars.update(locals())
shell = code.InteractiveConsole(vars)
shell.interact()
if __name__ == '__main__':
root = tk.Tk()
root.config(background="red")
main_window = Console(root)
main_window.mainloop()
try:
if root.winfo_exists():
root.destroy()
except:
pass
I have the answer in case anyone still cares! (I have also changed to python 3, hence the import tkinter rather than import Tkinter)
I have changed the approach slightly from the original by using a separate file to run the InteractiveConsole, and then making the main file open this other file (which I have called console.py and is in the same directory) in a subprocess, linking the stdout, stderr, and stdin of this subprocess to the tkinter Text widget programatically.
Here is the code in the for the console file (if this is run normally, it acts like a normal console):
# console.py
import code
if __name__ == '__main__':
vars = globals().copy()
vars.update(locals())
shell = code.InteractiveConsole(vars)
shell.interact()
And here is the code for the python interpreter, that runs the console inside the Text widget:
# main.py
import tkinter as tk
import subprocess
import queue
import os
from threading import Thread
class Console(tk.Frame):
def __init__(self,parent=None):
tk.Frame.__init__(self, parent)
self.parent = parent
self.createWidgets()
# get the path to the console.py file assuming it is in the same folder
consolePath = os.path.join(os.path.dirname(__file__),"console.py")
# open the console.py file (replace the path to python with the correct one for your system)
# e.g. it might be "C:\\Python35\\python"
self.p = subprocess.Popen(["python3",consolePath],
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE)
# make queues for keeping stdout and stderr whilst it is transferred between threads
self.outQueue = queue.Queue()
self.errQueue = queue.Queue()
# keep track of where any line that is submitted starts
self.line_start = 0
# make the enter key call the self.enter function
self.ttyText.bind("<Return>",self.enter)
# a daemon to keep track of the threads so they can stop running
self.alive = True
# start the functions that get stdout and stderr in separate threads
Thread(target=self.readFromProccessOut).start()
Thread(target=self.readFromProccessErr).start()
# start the write loop in the main thread
self.writeLoop()
def destroy(self):
"This is the function that is automatically called when the widget is destroyed."
self.alive=False
# write exit() to the console in order to stop it running
self.p.stdin.write("exit()\n".encode())
self.p.stdin.flush()
# call the destroy methods to properly destroy widgets
self.ttyText.destroy()
tk.Frame.destroy(self)
def enter(self,e):
"The <Return> key press handler"
string = self.ttyText.get(1.0, tk.END)[self.line_start:]
self.line_start+=len(string)
self.p.stdin.write(string.encode())
self.p.stdin.flush()
def readFromProccessOut(self):
"To be executed in a separate thread to make read non-blocking"
while self.alive:
data = self.p.stdout.raw.read(1024).decode()
self.outQueue.put(data)
def readFromProccessErr(self):
"To be executed in a separate thread to make read non-blocking"
while self.alive:
data = self.p.stderr.raw.read(1024).decode()
self.errQueue.put(data)
def writeLoop(self):
"Used to write data from stdout and stderr to the Text widget"
# if there is anything to write from stdout or stderr, then write it
if not self.errQueue.empty():
self.write(self.errQueue.get())
if not self.outQueue.empty():
self.write(self.outQueue.get())
# run this method again after 10ms
if self.alive:
self.after(10,self.writeLoop)
def write(self,string):
self.ttyText.insert(tk.END, string)
self.ttyText.see(tk.END)
self.line_start+=len(string)
def createWidgets(self):
self.ttyText = tk.Text(self, wrap=tk.WORD)
self.ttyText.pack(fill=tk.BOTH,expand=True)
if __name__ == '__main__':
root = tk.Tk()
root.config(background="red")
main_window = Console(root)
main_window.pack(fill=tk.BOTH,expand=True)
root.mainloop()
The reason that reading from stdout and stderr is in separate threads is because the read method is blocking, which causes the program to freeze until the console.py subprocess gives more output, unless these are in separate threads. The writeLoop method and the queues are needed to write to the Text widget since tkinter is not thread safe.
This certainly still has problems to be ironed out, such as the fact that any code on the Text widget is editable even once already submitted, but hopefully it answers your question.
EDIT: I've also neatened some of the tkinter such that the Console will behave more like a standard widget.
it isn't responding to commands
The reason it isn't responding to commands is because you haven't linked the Text widget (self.ttyText) into stdin. Currently when you type it adds text into the widget and nothing else. This linking can be done similarly to what you've already done with stdout and stderr.
When implementing this, you need to keep track of which part of the text in the widget is the text being entered by the user - this can be done using marks (as described here).
the thread doesn't stop when the user closes the window.
I don't think there is a "clean" way to solve this issue without a major code re-write, however a solution that seems to work well enough is it simply detect when the widget is destroyed and write the string "\n\nexit()" to the interpreter. This calls the exit function inside the interpreter, which causes the call to shell.interact to finish, which makes the thread finish.
So without further ado, here is the modified code:
import tkinter as tk
import sys
import code
from threading import Thread
import queue
class Console(tk.Frame):
def __init__(self, parent, _locals, exit_callback):
tk.Frame.__init__(self, parent)
self.parent = parent
self.exit_callback = exit_callback
self.destroyed = False
self.real_std_in_out = (sys.stdin, sys.stdout, sys.stderr)
sys.stdout = self
sys.stderr = self
sys.stdin = self
self.stdin_buffer = queue.Queue()
self.createWidgets()
self.consoleThread = Thread(target=lambda: self.run_interactive_console(_locals))
self.consoleThread.start()
def run_interactive_console(self, _locals):
try:
code.interact(local=_locals)
except SystemExit:
if not self.destroyed:
self.after(0, self.exit_callback)
def destroy(self):
self.stdin_buffer.put("\n\nexit()\n")
self.destroyed = True
sys.stdin, sys.stdout, sys.stderr = self.real_std_in_out
super().destroy()
def enter(self, event):
input_line = self.ttyText.get("input_start", "end")
self.ttyText.mark_set("input_start", "end-1c")
self.ttyText.mark_gravity("input_start", "left")
self.stdin_buffer.put(input_line)
def write(self, string):
self.ttyText.insert('end', string)
self.ttyText.mark_set("input_start", "end-1c")
self.ttyText.see('end')
def createWidgets(self):
self.ttyText = tk.Text(self.parent, wrap='word')
self.ttyText.grid(row=0, column=0, sticky=tk.N + tk.S + tk.E + tk.W)
self.ttyText.bind("<Return>", self.enter)
self.ttyText.mark_set("input_start", "end-1c")
self.ttyText.mark_gravity("input_start", "left")
def flush(self):
pass
def readline(self):
line = self.stdin_buffer.get()
return line
if __name__ == '__main__':
root = tk.Tk()
root.config(background="red")
main_window = Console(root, locals(), root.destroy)
main_window.mainloop()
This code has few changes other than those that solve the problems stated in the question.
The advantage of this code over my previous answer is that it works inside a single process, so can be created at any point in the application, giving the programmer more control.
I have also written a more complete version of this which also prevents the user from editing text which shouldn't be editable (e.g. the output of a print statement) and has some basic coloring: https://gist.github.com/olisolomons/e90d53191d162d48ac534bf7c02a50cd
I am trying to code an application that consists of various windows (e.g., generic message dialog, login dialog, main interface, etc.) and am having trouble getting the gtk.main_quit function to be called: either I get a complaint about the call being outside the main loop, or the function doesn't get called at all.
I am a newbie to both Python and GTK+, but my best guess as to how to get this to work is to have a "root" window, which is just a placeholder that is never seen, but controls the application's GTK+ loop. My code, so far, is as follows:
import pygtk
pygtk.require("2.0")
import gtk
class App(gtk.Window):
_exitStatus = 0
# Generic message box
def msg(self, title, text, type = gtk.MESSAGE_INFO, buttons = gtk.BUTTONS_OK):
# Must always have a button
if buttons == gtk.BUTTONS_NONE:
buttons = gtk.BUTTONS_OK
dialog = gtk.MessageDialog(None, 0, type, buttons, title)
dialog.set_title(title)
dialog.set_geometry_hints(min_width = 300)
dialog.set_resizable(False)
dialog.set_deletable(False)
dialog.set_position(gtk.WIN_POS_CENTER)
dialog.set_modal(True)
dialog.format_secondary_text(text)
response = dialog.run()
dialog.destroy()
return response
def nuke(self, widget, data):
gtk.main_quit()
exit(self._exitStatus)
def __init__(self):
super(App, self).__init__()
self.connect('destroy', self.nuke)
try:
raise Exception()
except:
self.msg('OMFG!', 'WTF just happened!?', gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE)
self._exitStatus = 1
self.destroy()
if self.msg('OK', 'Everything worked fine') == gtk.RESPONSE_OK:
self.destroy()
# Let's go!
App()
gtk.main()
The nuke function never gets called, despite the explicit calls to destroy.
DIFF On #DonQuestion's advice:
- self.destroy()
+ self.emit('destroy')
- App()
+ app = App()
This didn't solve the problem...
UPDATE Accepted #jku's answer, but also see my own answer for extra information...
First, there is a bit of a test problem with the code: You call Gtk.main_quit() from the App initialization: this happens before main loop is even running so signals probably won't work.
Second, you'll probably get a warning on destroy(): 'destroy' handler only takes two arguments (self plus one) but yours has three...
Also with regards to your comment about control flow: You don't need a Window to get signals as they're a GObject feature. And for your testing needs you could write a App.test_except() function and use glib.idle_add (self.test_except) in the object initialization -- this way test_except() is called when main loop is running.
I think #jku's answer identifies my key error, so I have marked it accepted, but while playing around, I found that the MessageDialog does not need to run within the GTK+ loop. I don't know if this is as designed, but it works! So, I broke my generic message dialog out into its own function and then kept the main app altogether in a class of its own, which respects the main loop as I was expecting:
import pygtk
pygtk.require("2.0")
import gtk
def msg(title, text, type = gtk.MESSAGE_INFO, buttons = gtk.BUTTONS_OK):
# Only allowed OK, Close, Cancel, Yes/No and OK/Cancel buttons
# Otherwise, default to just OK
if buttons not in [gtk.BUTTONS_OK, gtk.BUTTONS_CLOSE, gtk.BUTTONS_CANCEL, gtk.BUTTONS_YES_NO, gtk.BUTTONS_OK_CANCEL]:
buttons = gtk.BUTTONS_OK
dialog = gtk.MessageDialog(None, 0, type, buttons, title)
dialog.set_title(title)
dialog.set_geometry_hints(min_width = 300)
dialog.set_resizable(False)
dialog.set_deletable(False)
dialog.set_position(gtk.WIN_POS_CENTER)
dialog.set_modal(True)
dialog.format_secondary_text(text)
response = dialog.run()
dialog.destroy()
return response
class App:
def __init__(self):
# Build UI
# Connect signals
# Show whatever
def appQuit(self, widget):
gtk.main_quit()
def signalHandler(self, widget, data = None):
# Handle signal
# We can call msg here, when the main loop is running
# Load some resource
# We can call msg here, despite not having invoked the main loop
try:
# Load resource
except:
msg('OMFG!', 'WTF just happened!?', gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE)
exit(1)
# n.b., Calls to msg work even without the following code
App()
gtk.main()
exit(0)