# PyMsgBox - A simple, cross-platform, pure Python module for JavaScript-like message boxes.
# Al Sweigart al@inventwithpython.com
# Modified BSD License
# Derived from Stephen Raymond Ferg's EasyGui http://easygui.sourceforge.net/
"""
The four functions in PyMsgBox :
- alert ( text = ' ' , title = ' ' , button = ' OK ' )
Displays a simple message box with text and a single OK button . Returns the text of the button clicked on .
- confirm ( text = ' ' , title = ' ' , buttons = [ ' OK ' , ' Cancel ' ] )
Displays a message box with OK and Cancel buttons . Number and text of buttons can be customized . Returns the text of the button clicked on .
- prompt ( text = ' ' , title = ' ' , default = ' ' )
Displays a message box with text input , and OK & Cancel buttons . Returns the text entered , or None if Cancel was clicked .
- password ( text = ' ' , title = ' ' , default = ' ' , mask = ' * ' )
Displays a message box with text input , and OK & Cancel buttons . Typed characters appear as * . Returns the text entered , or None if Cancel was clicked .
"""
"""
TODO Roadmap :
- Be able to specify a custom icon in the message box .
- Be able to place the message box at an arbitrary position ( including on multi screen layouts )
- Add mouse clicks to unit testing .
- progress ( ) function to display a progress bar
- Maybe other types of dialog : open , save , file / folder picker , etc .
"""
__version__ = ' 1.0.6 '
import sys
RUNNING_PYTHON_2 = sys . version_info [ 0 ] == 2
# Because PyAutoGUI requires PyMsgBox but might be installed on systems
# without tkinter, we don't want a lack of tkinter to cause installation
# to fail. So exceptions won't be raised until the PyMsgBox functions
# are actually called.
TKINTER_IMPORT_SUCCEEDED = True
try :
if RUNNING_PYTHON_2 :
import Tkinter as tk
else :
import tkinter as tk
rootWindowPosition = ' +300+200 '
if tk . TkVersion < 8.0 :
raise RuntimeError ( ' You are running Tk version: ' + str ( tk . TkVersion ) + ' You must be using Tk version 8.0 or greater to use PyMsgBox. ' )
except ImportError :
TKINTER_IMPORT_SUCCEEDED = False
PROPORTIONAL_FONT_FAMILY = ( ' MS ' , ' Sans ' , ' Serif ' )
MONOSPACE_FONT_FAMILY = ( ' Courier ' )
PROPORTIONAL_FONT_SIZE = 10
MONOSPACE_FONT_SIZE = 9 #a little smaller, because it it more legible at a smaller size
TEXT_ENTRY_FONT_SIZE = 12 # a little larger makes it easier to see
STANDARD_SELECTION_EVENTS = [ ' Return ' , ' Button-1 ' , ' space ' ]
# constants for strings: (for internationalization, change these)
OK_TEXT = ' OK '
CANCEL_TEXT = ' Cancel '
TIMEOUT_TEXT = ' Timeout '
# Initialize some global variables that will be reset later
__choiceboxMultipleSelect = None
__widgetTexts = None
__replyButtonText = None
__choiceboxResults = None
__firstWidget = None
__enterboxText = None
__enterboxDefaultText = ' '
__multenterboxText = ' '
choiceboxChoices = None
choiceboxWidget = None
entryWidget = None
boxRoot = None
buttonsFrame = None
def alert ( text = ' ' , title = ' ' , button = OK_TEXT , root = None , timeout = None ) :
""" Displays a simple message box with text and a single OK button. Returns the text of the button clicked on. """
assert TKINTER_IMPORT_SUCCEEDED , ' Tkinter is required for pymsgbox '
return _buttonbox ( msg = text , title = title , choices = [ str ( button ) ] , root = root , timeout = timeout )
def confirm ( text = ' ' , title = ' ' , buttons = [ OK_TEXT , CANCEL_TEXT ] , root = None , timeout = None ) :
""" Displays a message box with OK and Cancel buttons. Number and text of buttons can be customized. Returns the text of the button clicked on. """
assert TKINTER_IMPORT_SUCCEEDED , ' Tkinter is required for pymsgbox '
return _buttonbox ( msg = text , title = title , choices = [ str ( b ) for b in buttons ] , root = root , timeout = timeout )
def prompt ( text = ' ' , title = ' ' , default = ' ' , root = None , timeout = None ) :
""" Displays a message box with text input, and OK & Cancel buttons. Returns the text entered, or None if Cancel was clicked. """
assert TKINTER_IMPORT_SUCCEEDED , ' Tkinter is required for pymsgbox '
return __fillablebox ( text , title , default = default , mask = None , root = root , timeout = timeout )
def password ( text = ' ' , title = ' ' , default = ' ' , mask = ' * ' , root = None , timeout = None ) :
""" Displays a message box with text input, and OK & Cancel buttons. Typed characters appear as *. Returns the text entered, or None if Cancel was clicked. """
assert TKINTER_IMPORT_SUCCEEDED , ' Tkinter is required for pymsgbox '
return __fillablebox ( text , title , default , mask = mask , root = root , timeout = timeout )
import pymsgbox . native as native # This needs to be after the above functions so that the unimplmeneted native functions can default back to the above functions.
native # dummy line just to make lint stop complaining about the previous line
def timeoutBoxRoot ( ) :
global boxRoot , __replyButtonText , __enterboxText
boxRoot . destroy ( )
__replyButtonText = TIMEOUT_TEXT
__enterboxText = TIMEOUT_TEXT
def _buttonbox ( msg , title , choices , root = None , timeout = None ) :
"""
Display a msg , a title , and a set of buttons .
The buttons are defined by the members of the choices list .
Return the text of the button that the user selected .
@arg msg : the msg to be displayed .
@arg title : the window title
@arg choices : a list or tuple of the choices to be displayed
"""
global boxRoot , __replyButtonText , __widgetTexts , buttonsFrame
# Initialize __replyButtonText to the first choice.
# This is what will be used if the window is closed by the close button.
__replyButtonText = choices [ 0 ]
if root :
root . withdraw ( )
boxRoot = tk . Toplevel ( master = root )
boxRoot . withdraw ( )
else :
boxRoot = tk . Tk ( )
boxRoot . withdraw ( )
boxRoot . title ( title )
boxRoot . iconname ( ' Dialog ' )
boxRoot . geometry ( rootWindowPosition )
boxRoot . minsize ( 400 , 100 )
# ------------- define the messageFrame ---------------------------------
messageFrame = tk . Frame ( master = boxRoot )
messageFrame . pack ( side = tk . TOP , fill = tk . BOTH )
# ------------- define the buttonsFrame ---------------------------------
buttonsFrame = tk . Frame ( master = boxRoot )
buttonsFrame . pack ( side = tk . TOP , fill = tk . BOTH )
# -------------------- place the widgets in the frames -----------------------
messageWidget = tk . Message ( messageFrame , text = msg , width = 400 )
messageWidget . configure ( font = ( PROPORTIONAL_FONT_FAMILY , PROPORTIONAL_FONT_SIZE ) )
messageWidget . pack ( side = tk . TOP , expand = tk . YES , fill = tk . X , padx = ' 3m ' , pady = ' 3m ' )
__put_buttons_in_buttonframe ( choices )
# -------------- the action begins -----------
# put the focus on the first button
__firstWidget . focus_force ( )
boxRoot . deiconify ( )
if timeout is not None :
boxRoot . after ( timeout , timeoutBoxRoot )
boxRoot . mainloop ( )
try :
boxRoot . destroy ( )
except tk . TclError :
if __replyButtonText != TIMEOUT_TEXT :
__replyButtonText = None
if root : root . deiconify ( )
return __replyButtonText
def __put_buttons_in_buttonframe ( choices ) :
""" Put the buttons in the buttons frame """
global __widgetTexts , __firstWidget , buttonsFrame
__firstWidget = None
__widgetTexts = { }
i = 0
for buttonText in choices :
tempButton = tk . Button ( buttonsFrame , takefocus = 1 , text = buttonText )
_bindArrows ( tempButton )
tempButton . pack ( expand = tk . YES , side = tk . LEFT , padx = ' 1m ' , pady = ' 1m ' , ipadx = ' 2m ' , ipady = ' 1m ' )
# remember the text associated with this widget
__widgetTexts [ tempButton ] = buttonText
# remember the first widget, so we can put the focus there
if i == 0 :
__firstWidget = tempButton
i = 1
# for the commandButton, bind activation events to the activation event handler
commandButton = tempButton
handler = __buttonEvent
for selectionEvent in STANDARD_SELECTION_EVENTS :
commandButton . bind ( ' < %s > ' % selectionEvent , handler )
if CANCEL_TEXT in choices :
commandButton . bind ( ' <Escape> ' , __cancelButtonEvent )
def _bindArrows ( widget , skipArrowKeys = False ) :
widget . bind ( ' <Down> ' , _tabRight )
widget . bind ( ' <Up> ' , _tabLeft )
if not skipArrowKeys :
widget . bind ( ' <Right> ' , _tabRight )
widget . bind ( ' <Left> ' , _tabLeft )
def _tabRight ( event ) :
boxRoot . event_generate ( ' <Tab> ' )
def _tabLeft ( event ) :
boxRoot . event_generate ( ' <Shift-Tab> ' )
def __buttonEvent ( event ) :
"""
Handle an event that is generated by a person clicking a button .
"""
global boxRoot , __widgetTexts , __replyButtonText
__replyButtonText = __widgetTexts [ event . widget ]
boxRoot . quit ( ) # quit the main loop
def __cancelButtonEvent ( event ) :
""" Handle pressing Esc by clicking the Cancel button. """
global boxRoot , __widgetTexts , __replyButtonText
__replyButtonText = CANCEL_TEXT
boxRoot . quit ( )
def __fillablebox ( msg , title = ' ' , default = ' ' , mask = None , root = None , timeout = None ) :
"""
Show a box in which a user can enter some text .
You may optionally specify some default text , which will appear in the
enterbox when it is displayed .
Returns the text that the user entered , or None if he cancels the operation .
"""
global boxRoot , __enterboxText , __enterboxDefaultText
global cancelButton , entryWidget , okButton
if title == None :
title == ' '
if default == None :
default = ' '
__enterboxDefaultText = default
__enterboxText = __enterboxDefaultText
if root :
root . withdraw ( )
boxRoot = tk . Toplevel ( master = root )
boxRoot . withdraw ( )
else :
boxRoot = tk . Tk ( )
boxRoot . withdraw ( )
boxRoot . title ( title )
boxRoot . iconname ( ' Dialog ' )
boxRoot . geometry ( rootWindowPosition )
boxRoot . bind ( ' <Escape> ' , __enterboxCancel )
# ------------- define the messageFrame ---------------------------------
messageFrame = tk . Frame ( master = boxRoot )
messageFrame . pack ( side = tk . TOP , fill = tk . BOTH )
# ------------- define the buttonsFrame ---------------------------------
buttonsFrame = tk . Frame ( master = boxRoot )
buttonsFrame . pack ( side = tk . TOP , fill = tk . BOTH )
# ------------- define the entryFrame ---------------------------------
entryFrame = tk . Frame ( master = boxRoot )
entryFrame . pack ( side = tk . TOP , fill = tk . BOTH )
# ------------- define the buttonsFrame ---------------------------------
buttonsFrame = tk . Frame ( master = boxRoot )
buttonsFrame . pack ( side = tk . TOP , fill = tk . BOTH )
#-------------------- the msg widget ----------------------------
messageWidget = tk . Message ( messageFrame , width = ' 4.5i ' , text = msg )
messageWidget . configure ( font = ( PROPORTIONAL_FONT_FAMILY , PROPORTIONAL_FONT_SIZE ) )
messageWidget . pack ( side = tk . RIGHT , expand = 1 , fill = tk . BOTH , padx = ' 3m ' , pady = ' 3m ' )
# --------- entryWidget ----------------------------------------------
entryWidget = tk . Entry ( entryFrame , width = 40 )
_bindArrows ( entryWidget , skipArrowKeys = True )
entryWidget . configure ( font = ( PROPORTIONAL_FONT_FAMILY , TEXT_ENTRY_FONT_SIZE ) )
if mask :
entryWidget . configure ( show = mask )
entryWidget . pack ( side = tk . LEFT , padx = ' 3m ' )
entryWidget . bind ( ' <Return> ' , __enterboxGetText )
entryWidget . bind ( ' <Escape> ' , __enterboxCancel )
# put text into the entryWidget and have it pre-highlighted
if __enterboxDefaultText != ' ' :
entryWidget . insert ( 0 , __enterboxDefaultText )
entryWidget . select_range ( 0 , tk . END )
# ------------------ ok button -------------------------------
okButton = tk . Button ( buttonsFrame , takefocus = 1 , text = OK_TEXT )
_bindArrows ( okButton )
okButton . pack ( expand = 1 , side = tk . LEFT , padx = ' 3m ' , pady = ' 3m ' , ipadx = ' 2m ' , ipady = ' 1m ' )
# for the commandButton, bind activation events to the activation event handler
commandButton = okButton
handler = __enterboxGetText
for selectionEvent in STANDARD_SELECTION_EVENTS :
commandButton . bind ( ' < %s > ' % selectionEvent , handler )
# ------------------ cancel button -------------------------------
cancelButton = tk . Button ( buttonsFrame , takefocus = 1 , text = CANCEL_TEXT )
_bindArrows ( cancelButton )
cancelButton . pack ( expand = 1 , side = tk . RIGHT , padx = ' 3m ' , pady = ' 3m ' , ipadx = ' 2m ' , ipady = ' 1m ' )
# for the commandButton, bind activation events to the activation event handler
commandButton = cancelButton
handler = __enterboxCancel
for selectionEvent in STANDARD_SELECTION_EVENTS :
commandButton . bind ( ' < %s > ' % selectionEvent , handler )
# ------------------- time for action! -----------------
entryWidget . focus_force ( ) # put the focus on the entryWidget
boxRoot . deiconify ( )
if timeout is not None :
boxRoot . after ( timeout , timeoutBoxRoot )
boxRoot . mainloop ( ) # run it!
# -------- after the run has completed ----------------------------------
if root : root . deiconify ( )
try :
boxRoot . destroy ( ) # button_click didn't destroy boxRoot, so we do it now
except tk . TclError :
if __enterboxText != TIMEOUT_TEXT :
return None
return __enterboxText
def __enterboxGetText ( event ) :
global __enterboxText
__enterboxText = entryWidget . get ( )
boxRoot . quit ( )
def __enterboxRestore ( event ) :
global entryWidget
entryWidget . delete ( 0 , len ( entryWidget . get ( ) ) )
entryWidget . insert ( 0 , __enterboxDefaultText )
def __enterboxCancel ( event ) :
global __enterboxText
__enterboxText = None
boxRoot . quit ( )