Is there a way to destroy the tkinter messagebox without clicking OK? - python

I know you can use something like,
self.root.after(1000, self.update_clock)
But could I some how replace that second function with a function that's similar to messagebox.showinfo.destroy()? I'm basically trying to put these message boxes on a timer so that the user will see them but won't have to do anything themselves.
response = tkinter.messagebox.showinfo("Warning!", "New artist object has been created: "
+ "\n" + "$oid: " + str(self.artistObjectId))
if response == "ok":
self.currentState += 1
self.states[self.currentState](importedTracks[self.currentTrack])

Maybe a message box is not what you require in this context. If you would just like to show a message then have it automatically disappear you could use a new TopLevel or frame and then destroy the frame after a timeout. In terms of user interaction and experience, message boxes are designed to wait for user input?
This is a good example of using a new TopLevel
closing tkmessagebox after some time in python
I found this page that describes what can be done to customise message boxes, though what I could find is somewhat limited.
http://effbot.org/tkinterbook/tkinter-standard-dialogs.htm

The small function below will do the job. By setting the type you can choose for: info, warning or error message box, the default is 'Info'. You can set also the timeout, the default is 2.5 seconds.
def showMessage(message, type='info', timeout=2500):
import tkinter as tk
from tkinter import messagebox as msgb
root = tk.Tk()
root.withdraw()
try:
root.after(timeout, root.destroy)
if type == 'info':
msgb.showinfo('Info', message, master=root)
elif type == 'warning':
msgb.showwarning('Warning', message, master=root)
elif type == 'error':
msgb.showerror('Error', message, master=root)
except:
pass
Call the function as follow:
For message type 'Info' and timeout of 2.5 seconds:
showMessage('Your message')
Or by your own settings for type message 'Error' and timeout 4 seconds:
showMessage('Your message', type='error', timeout=4000)

Related

Efficient multi window tkinter

So I am writing this code with multiple states where each state is represented with its own Tkinter window. the question is how to do this efficiently knowing that each window is defined as a class on a separate file. I'm doing this right now but I'm not sure this okay as the interface seems to be lagging compared to when I launch the class directly (not from the method call)
def StartExperimentButtonCallback(self):
ErrorMessage=''
#build error message
if(len(ErrorMessage)>0):
ErrorMessage += '\n'
messagebox.showerror("Error", ErrorMessage)
else:
self.parent.destroy()
factory = eego_sdk.factory()
v = factory.getVersion()
print('version: {}.{}.{}.{}'.format(v.major, v.minor, v.micro, v.build))
print('delaying to allow slow devices to attach...')
time.sleep(1)
amplifier=factory.getAmplifiers()
amplifier=amplifier[0]
root = tk.Tk()
# This is the section of code which creates the main window
root.geometry('1280x740')
root.configure(background='#FFFFFF')
root.title('Impedances')
timer = ImpedanceGUI(root, amplifier, self.SubjectID.get(), [float(self.RightSphere.get()),float(self.LeftSphere.get()),float(self.RightCylinder.get()), float(self.LeftCylinder.get())])
del self
root.mainloop()

Python display a notification with a visible timeout

Please note: this is a self answered question for reference.
Using Python how does one display a notification:
a) with a timeout and
b) have the timeout visibly countdown.
Update: Just 2 weeks after posting this, I updated to Mint 19 (Ubuntu 18.04) the result, the timer function described below has vanished in the Standard Notification. I can only suppose that the move to GTK+3 has virtually hidden the timer. It is there but barely visible.
Choosing Notification style Nodoka or Coco in Control Centre --> Popup Notifications, does display the timer properly.
End Update
Using the standard notification modules notify2 and Notify in the gi.repository, one simply has to add an action, even though you have no intention of using it.
Note: there appears to be no documented reason for this, that I have found.
In addition to a close button being added to the notification, it also supplies a clockface that reduces based on the timeout supplied.
for notify2:
import notify2
class Notify():
def mess_callback():
pass
def __init__(self,parent,caption,msg,timeout=None,urgency=None):
if timeout != None: pass
else: timeout = 0 # message should not timeout
if urgency: pass
else: urgency = 0
img = '/home/rolf/MyApp.png'
caps = notify2.get_server_caps()
mess = notify2.Notification(caption,msg,img) # passing an image is optional
mess.set_timeout(timeout) #milliseconds
mess.set_urgency(urgency) #0-Low, 1-Normal, 2-Critical
# Without the following `add_action` option, No countdown to the time out is shown
if timeout != 0 and 'actions' in caps:
mess.add_action("close","Close",self.mess_callback,None) #Show the countdown to close
mess.show()
if __name__ == "__main__":
notify2.init("MyApp") #Register MyApp
Notify(None,"Error","This message is not timed and has to be manually cancelled")
Notify(None,"Error 2","This message will timeout after the default value",timeout=-1)
Notify(None,"Information","An Unimportant message",timeout=20000,urgency=0)
Notify(None,"Attention","An Important message",timeout=20000,urgency=1)
Notify(None,"Emergency","A Critical message",timeout=20000,urgency=2)
notify2.uninit() #Un-register
Using Notify in the gi.repository:
import gi
gi.require_version('Notify', '0.7')
from gi.repository import Notify
class Message():
def mess_callback():
pass
def __init__(self,parent,caption,msg,timeout=None,urgency=None):
if timeout != None: pass
else: timeout = 0 # message should not timeout
if urgency: pass
else: urgency = 0
img = '/home/rolf/MyApp.png'
caps = Notify.get_server_caps()
mess = Notify.Notification.new(caption, msg, img) # passing an image is optional
mess.set_timeout(timeout) #milliseconds
mess.set_urgency(urgency) #0-Low, 1-Normal, 2-Critical
# Without the following `add_action` option, No countdown to the time out is shown
if timeout != 0 and 'actions' in caps:
mess.add_action("close","Close",self.mess_callback,None) #Show the countdown to close
mess.show()
if __name__ == "__main__":
Notify.init("MyApp") #Register MyApp
Message(None,"Error","This message is not timed and has to be manually cancelled")
Message(None,"Error 2","This message will timeout after the default value",timeout=-1)
Message(None,"Information","An Unimportant message",timeout=20000,urgency=0)
Message(None,"Attention","An Important message",timeout=20000,urgency=1)
Message(None,"Emergency","A Critical message",timeout=20000,urgency=2)

I can't get my error message box to work in pyqt

In my init function, i've defined an error message box like this:
self.error_msg = QtGui.QMessageBox()
self.error_msg.setIcon(QtGui.QMessageBox.critical)
self.error_msg.setWindowTitle("Error")
self.error_msg.setDetailedText("")
on a different method, I try to call the message box like this, by setting the error text:
def detectRoot(self):
euid = os.geteuid()
if euid != 0:
print "need to be root to run this program"
self.logger.error("Not root, program exited")
self.error_msg.setText("You need to be root to run this program")
self.error_msg.exec_()
exit(1)
However, I keep getting message a pyqt/python error:
self.error_msg.setIcon(QtGui.QMessageBox.critical)
TypeError: QMessageBox.setIcon(QMessageBox.Icon): argument 1 has unexpected type 'builtin_function_or_method'
According to the documentation:
QMessageBox::NoIcon: The message box does not have any icon.
QMessageBox::Question: An icon indicating that the message is asking a question.
QMessageBox::Information: An icon indicating that the message is nothing out of the ordinary.
QMessageBox::Warning: An icon indicating that the message is a warning, but can be dealt with.
QMessageBox::Critical: An icon indicating that the message represents a critical problem.
Change QtGui.QMessageBox.critical to QtGui.QMessageBox.Critical

Why does an generic "try / except" block not apply to a Python TkInter callback?

I have determined that in a Python TkInter GUI program, it is best practice to enclose the entire thing in a try / except block in order to catch all exceptions and present them to the end user (as opposed to something going wrong silently or the program exiting for seemingly no reason).
However, this approach has some problems. Consider the following tiny program that attempts to divide by 0 when a button is clicked:
import tkinter
class Foo():
def __init__(self):
# Initialize a new GUI window
root = tkinter.Tk()
# The "Generic error" message is shown when the following is uncommented
#number = 1 / 0
# Define a button and draw it
button = tkinter.Button(root, text='Generate an error', command=self.generate_error)
button.pack()
# Loop forever
root.mainloop()
def generate_error(self):
# The "Generic error" message is not shown
number = 1 / 0
if __name__ == '__main__':
try:
# Run the Foo class
Foo()
except Exception as e:
print('Generic error:', str(e))
Why does the "Generic error" statement not apply to the button callback function?
The following StackOverflow post was helpful: Should I make silent exceptions louder in tkinter?
Basically, I need to use report_callback_exception. I've modified the code snippet accordingly:
import tkinter
import tkinter.messagebox
import traceback
class Foo():
def __init__(self):
# Initialize a new GUI window
tkinter.Tk.report_callback_exception = callback_error # TkInter callbacks run in different threads, so if we want to handle generic exceptions caused in a TkInter callback, we must define a specific custom exception handler for that
root = tkinter.Tk()
# The error() function is triggered when the following is uncommented
#number = 1 / 0
# Define a button and draw it
button = tkinter.Button(root, text='Generate an error', command=self.generate_error)
button.pack()
# Loop forever
root.mainloop()
def generate_error(self):
# The "callback_error()" function is triggered when the button is clicked
number = 1 / 0
def error(message, exception):
# Build the error message
if exception is not None:
message += '\n\n'
message += traceback.format_exc()
# Also log the error to a file
# TODO
# Show the error to the user
tkinter.messagebox.showerror('Error', message)
# Exit the program immediately
exit()
def callback_error(self, *args):
# Build the error message
message = 'Generic error:\n\n'
message += traceback.format_exc()
# Also log the error to a file
# TODO
# Show the error to the user
tkinter.messagebox.showerror('Error', message)
# Exit the program immediately
exit()
if __name__ == '__main__':
try:
# Run the Foo class
Foo()
except Exception as e:
error('Generic error:', e)

time.sleep() Equivalent on Tkinter

I have to stop a loop and resume after many seconds. I tried to use after(), but the loop don't freeze. And when I use time.sleep() (works out tkinter), tkinter freeze. Have another way or a function equivalent time.sleep() without freeze.
code:
for message in listOfMessages:
time.sleep(message.time)
#manipulate message
#change text widget
This will freeze my tkinter app.
I tried to use after() but message.time is a float: 0.5054564 to 1.5234244. It's random. And after supports only int. I can't convert to int, because low numbers make differents. And if i convert 0.52342342 in int: 523... i lost the others 42342
and this dont work too:
def changes(#values):
#manipulate message
#change text widget
for message in listOfMessages:
app.after(message.time, lambda: changes(#values))
Have another function equivalent time.sleep thats not freeze tkinter and is different that after()? If not, have another way to do this? Thanks for attention.
To create an analog of:
for message in listOfMessages:
time.sleep(message.time)
change(message)
in tkinter:
def update_widget(app, messages):
message = next(messages, None)
if message is None: # end of the loop
return
delay = int(message.time * 1000) # milliseconds
app.after(delay, change, message) # execute body
app.after(delay, update_widget, app, messages) # next iteration
update_widget(app, iter(listOfMessages))
If you want to wait until change(message) finishes before continuing the loop:
def iterate(message, app, messages):
change(message)
update_widget(app, messages)
def update_widget(app, messages):
message = next(messages, None)
if message is None: # end of the loop
return
delay = int(message.time * 1000) # milliseconds
app.after(delay, iterate, message, app, messages)
update_widget(app, iter(listOfMessages))

Categories