Source code for easygui.boxes.multi_fillable_box

"""

.. moduleauthor:: easygui developers and Stephen Raymond Ferg
.. default-domain:: py
.. highlight:: python

Version |release|
"""

try:
    from . import global_state
except:
    import global_state

try:
    import tkinter as tk  # python 3
except:
    import Tkinter as tk  # python 2

# -----------------------------------------------------------------------
# multpasswordbox
# -----------------------------------------------------------------------


[docs]def multpasswordbox(msg="Fill in values for the fields.", title=" ", fields=tuple(), values=tuple(), callback=None, run=True): r""" Same interface as multenterbox. But in multpassword box, the last of the fields is assumed to be a password, and is masked with asterisks. :param str msg: the msg to be displayed. :param str title: the window title :param list fields: a list of fieldnames. :param list values: a list of field values :return: String **Example** Here is some example code, that shows how values returned from multpasswordbox can be checked for validity before they are accepted:: msg = "Enter logon information" title = "Demo of multpasswordbox" fieldNames = ["Server ID", "User ID", "Password"] fieldValues = [] # we start with blanks for the values fieldValues = multpasswordbox(msg,title, fieldNames) # make sure that none of the fields was left blank while 1: if fieldValues is None: break errmsg = "" for i in range(len(fieldNames)): if fieldValues[i].strip() == "": errmsg = errmsg + ('"%s" is a required field.\n\n' % fieldNames[i]) if errmsg == "": break # no problems found fieldValues = multpasswordbox(errmsg, title, fieldNames, fieldValues) print("Reply was: %s" % str(fieldValues)) """ if run: mb = MultiBox(msg, title, fields, values, mask_last=True, callback=callback) reply = mb.run() return reply else: mb = MultiBox(msg, title, fields, values, mask_last=True, callback=callback) return mb
# ------------------------------------------------------------------- # multenterbox # ------------------------------------------------------------------- # TODO RL: Should defaults be list constructors. # i think after multiple calls, the value is retained. # TODO RL: Rename/alias to multienterbox? # default should be None and then in the logic create an empty liglobal_state.
[docs]def multenterbox(msg="Fill in values for the fields.", title=" ", fields=[], values=[], callback=None, run=True): r""" Show screen with multiple data entry fields. If there are fewer values than names, the list of values is padded with empty strings until the number of values is the same as the number of names. If there are more values than names, the list of values is truncated so that there are as many values as names. Returns a list of the values of the fields, or None if the user cancels the operation. Here is some example code, that shows how values returned from multenterbox can be checked for validity before they are accepted:: msg = "Enter your personal information" title = "Credit Card Application" fieldNames = ["Name","Street Address","City","State","ZipCode"] fieldValues = [] # we start with blanks for the values fieldValues = multenterbox(msg,title, fieldNames) # make sure that none of the fields was left blank while 1: if fieldValues is None: break errmsg = "" for i in range(len(fieldNames)): if fieldValues[i].strip() == "": errmsg += ('"%s" is a required field.\n\n' % fieldNames[i]) if errmsg == "": break # no problems found fieldValues = multenterbox(errmsg, title, fieldNames, fieldValues) print("Reply was: %s" % str(fieldValues)) :param str msg: the msg to be displayed. :param str title: the window title :param list fields: a list of fieldnames. :param list values: a list of field values :return: String """ if run: mb = MultiBox(msg, title, fields, values, mask_last=False, callback=callback) reply = mb.run() return reply else: mb = MultiBox(msg, title, fields, values, mask_last=False, callback=callback) return mb
class MultiBox(object): """ Show multiple data entry fields This object does a number of things: - chooses a GUI framework (wx, qt) - checks the data sent to the GUI - performs the logic (button ok should close the window?) - defines what methods the user can invoke and what properties he can change. - calls the ui in defined ways, so other gui frameworks can be used without breaking anything to the user """ def __init__(self, msg, title, fields, values, mask_last, callback): """ Create box object Parameters ---------- msg : string text displayed in the message area (instructions...) title : str the window title fields: list names of fields values: list initial values callback: function if set, this function will be called when OK is pressed run: bool if True, a box object will be created and returned, but not run Returns ------- self The MultiBox object """ self.callback = callback self.fields, self.values = self.check_fields(fields, values) self.ui = GUItk(msg, title, self.fields, self.values, mask_last, self.callback_ui) def run(self): """ Start the ui """ self.ui.run() self.ui = None return self.values def stop(self): """ Stop the ui """ self.ui.stop() def callback_ui(self, ui, command, values): """ This method is executed when ok, cancel, or x is pressed in the ui. """ if command == 'update': # OK was pressed self.values = values if self.callback: # If a callback was set, call main process self.callback(self) else: self.stop() elif command == 'x': self.stop() self.values = None elif command == 'cancel': self.stop() self.values = None # methods to change properties -------------- @property def msg(self): """Text in msg Area""" return self._msg @msg.setter def msg(self, msg): self.ui.set_msg(msg) @msg.deleter def msg(self): self._msg = "" self.ui.set_msg(self._msg) # Methods to validate what will be sent to ui --------- def check_fields(self, fields, values): if len(fields) == 0: return None fields = list(fields[:]) # convert possible tuples to a list values = list(values[:]) # convert possible tuples to a list # TODO RL: The following seems incorrect when values>fields. Replace # below with zip? if len(values) == len(fields): pass elif len(values) > len(fields): fields = fields[0:len(values)] else: while len(values) < len(fields): values.append("") return fields, values class GUItk(object): """ This object contains the tk root object. It draws the window, waits for events and communicates them to MultiBox, together with the entered values. The position in wich it is drawn comes from a global variable. It also accepts commands from Multibox to change its message. """ def __init__(self, msg, title, fields, values, mask_last, callback): self.callback = callback self.boxRoot = tk.Tk() self.create_root(title) self.set_pos(global_state.window_position) # GLOBAL POSITION self.create_msg_widget(msg) self.create_entryWidgets(fields, values, mask_last) self.create_buttons() self.entryWidgets[0].focus_force() # put the focus on the entryWidget # Run and stop methods --------------------------------------- def run(self): self.boxRoot.mainloop() # run it! self.boxRoot.destroy() # Close the window def stop(self): # Get the current position before quitting self.get_pos() self.boxRoot.quit() def x_pressed(self): self.callback(self, command='x', values=self.get_values()) def cancel_pressed(self, event): self.callback(self, command='cancel', values=self.get_values()) def ok_pressed(self, event): self.callback(self, command='update', values=self.get_values()) # Methods to change content --------------------------------------- def set_msg(self, msg): self.messageWidget.configure(text=msg) self.entryWidgets[0].focus_force() # put the focus on the entryWidget def set_pos(self, pos): self.boxRoot.geometry(pos) def get_pos(self): # The geometry() method sets a size for the window and positions it on # the screen. The first two parameters are width and height of # the window. The last two parameters are x and y screen coordinates. # geometry("250x150+300+300") geom = self.boxRoot.geometry() # "628x672+300+200" global_state.window_position = '+' + geom.split('+', 1)[1] def get_values(self): values = [] for entryWidget in self.entryWidgets: values.append(entryWidget.get()) return values # Initial configuration methods --------------------------------------- # These ones are just called once, at setting. def create_root(self, title): self.boxRoot.protocol('WM_DELETE_WINDOW', self.x_pressed) self.boxRoot.title(title) self.boxRoot.iconname('Dialog') self.boxRoot.bind("<Escape>", self.cancel_pressed) def create_msg_widget(self, msg): # -------------------- the msg widget ---------------------------- self.messageWidget = tk.Message(self.boxRoot, width="4.5i", text=msg) self.messageWidget.configure( font=(global_state.PROPORTIONAL_FONT_FAMILY, global_state.PROPORTIONAL_FONT_SIZE)) self.messageWidget.pack( side=tk.TOP, expand=1, fill=tk.BOTH, padx='3m', pady='3m') def create_entryWidgets(self, fields, values, mask_last): self.entryWidgets = [] lastWidgetIndex = len(fields) - 1 for widgetIndex in range(len(fields)): name = fields[widgetIndex] value = values[widgetIndex] entryFrame = tk.Frame(master=self.boxRoot) entryFrame.pack(side=tk.TOP, fill=tk.BOTH) # --------- entryWidget ------------------------------------------- labelWidget = tk.Label(entryFrame, text=name) labelWidget.pack(side=tk.LEFT) entryWidget = tk.Entry(entryFrame, width=40, highlightthickness=2) self.entryWidgets.append(entryWidget) entryWidget.configure( font=(global_state.PROPORTIONAL_FONT_FAMILY, global_state.TEXT_ENTRY_FONT_SIZE)) entryWidget.pack(side=tk.RIGHT, padx="3m") self.bindArrows(entryWidget) entryWidget.bind("<Return>", self.ok_pressed) entryWidget.bind("<Escape>", self.cancel_pressed) # for the last entryWidget, if this is a multpasswordbox, # show the contents as just asterisks if widgetIndex == lastWidgetIndex: if mask_last: self.entryWidgets[widgetIndex].configure(show="*") # put text into the entryWidget if value is None: value = '' self.entryWidgets[widgetIndex].insert( 0, '{}'.format(value)) def create_buttons(self): self.buttonsFrame = tk.Frame(master=self.boxRoot) self.buttonsFrame.pack(side=tk.BOTTOM) self.create_cancel_button() self.create_ok_button() def create_ok_button(self): okButton = tk.Button(self.buttonsFrame, takefocus=1, text="OK") self.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 = self.ok_pressed for selectionEvent in global_state.STANDARD_SELECTION_EVENTS: commandButton.bind("<%s>" % selectionEvent, handler) def create_cancel_button(self): cancelButton = tk.Button(self.buttonsFrame, takefocus=1, text="Cancel") self.bindArrows(cancelButton) cancelButton.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 = cancelButton handler = self.cancel_pressed for selectionEvent in global_state.STANDARD_SELECTION_EVENTS: commandButton.bind("<%s>" % selectionEvent, handler) def bindArrows(self, widget): widget.bind("<Down>", self.tabRight) widget.bind("<Up>", self.tabLeft) widget.bind("<Right>", self.tabRight) widget.bind("<Left>", self.tabLeft) def tabRight(self, event): self.boxRoot.event_generate("<Tab>") def tabLeft(self, event): self.boxRoot.event_generate("<Shift-Tab>") def demo1(): msg = "Enter your personal information" title = "Credit Card Application" fieldNames = ["Name", "Street Address", "City", "State", "ZipCode"] fieldValues = [] # we start with blanks for the values # make sure that none of the fields was left blank while True: fieldValues = multenterbox(msg, title, fieldNames, fieldValues) cancelled = fieldValues is None errors = [] if cancelled: pass else: # check for errors for name, value in zip(fieldNames, fieldValues): if value.strip() == "": errors.append('"{}" is a required field.'.format(name)) all_ok = not errors if cancelled or all_ok: break # no problems found msg = "\n".join(errors) print("Reply was: {}".format(fieldValues)) class Demo2(): def __init__(self): msg = "Without flicker. Enter your personal information" title = "Credit Card Application" fieldNames = ["Name", "Street Address", "City", "State", "ZipCode"] fieldValues = [] # we start with blanks for the values fieldValues = multenterbox(msg, title, fieldNames, fieldValues, callback=self.check_for_blank_fields) print("Reply was: {}".format(fieldValues)) def check_for_blank_fields(self, box): # make sure that none of the fields was left blank cancelled = box.values is None errors = [] if cancelled: pass else: # check for errors for name, value in zip(box.fields, box.values): if value.strip() == "": errors.append('"{}" is a required field.'.format(name)) all_ok = not errors if cancelled or all_ok: box.stop() # no problems found box.msg = "\n".join(errors) if __name__ == '__main__': demo1() Demo2()