Home | History | Annotate | Download | only in tkinter
      1 #
      2 # An Introduction to Tkinter
      3 #
      4 # Copyright (c) 1997 by Fredrik Lundh
      5 #
      6 # This copyright applies to Dialog, askinteger, askfloat and asktring
      7 #
      8 # fredrik (at] pythonware.com
      9 # http://www.pythonware.com
     10 #
     11 """This modules handles dialog boxes.
     12 
     13 It contains the following public symbols:
     14 
     15 SimpleDialog -- A simple but flexible modal dialog box
     16 
     17 Dialog -- a base class for dialogs
     18 
     19 askinteger -- get an integer from the user
     20 
     21 askfloat -- get a float from the user
     22 
     23 askstring -- get a string from the user
     24 """
     25 
     26 from tkinter import *
     27 from tkinter import messagebox
     28 
     29 import tkinter # used at _QueryDialog for tkinter._default_root
     30 
     31 class SimpleDialog:
     32 
     33     def __init__(self, master,
     34                  text='', buttons=[], default=None, cancel=None,
     35                  title=None, class_=None):
     36         if class_:
     37             self.root = Toplevel(master, class_=class_)
     38         else:
     39             self.root = Toplevel(master)
     40         if title:
     41             self.root.title(title)
     42             self.root.iconname(title)
     43         self.message = Message(self.root, text=text, aspect=400)
     44         self.message.pack(expand=1, fill=BOTH)
     45         self.frame = Frame(self.root)
     46         self.frame.pack()
     47         self.num = default
     48         self.cancel = cancel
     49         self.default = default
     50         self.root.bind('<Return>', self.return_event)
     51         for num in range(len(buttons)):
     52             s = buttons[num]
     53             b = Button(self.frame, text=s,
     54                        command=(lambda self=self, num=num: self.done(num)))
     55             if num == default:
     56                 b.config(relief=RIDGE, borderwidth=8)
     57             b.pack(side=LEFT, fill=BOTH, expand=1)
     58         self.root.protocol('WM_DELETE_WINDOW', self.wm_delete_window)
     59         self._set_transient(master)
     60 
     61     def _set_transient(self, master, relx=0.5, rely=0.3):
     62         widget = self.root
     63         widget.withdraw() # Remain invisible while we figure out the geometry
     64         widget.transient(master)
     65         widget.update_idletasks() # Actualize geometry information
     66         if master.winfo_ismapped():
     67             m_width = master.winfo_width()
     68             m_height = master.winfo_height()
     69             m_x = master.winfo_rootx()
     70             m_y = master.winfo_rooty()
     71         else:
     72             m_width = master.winfo_screenwidth()
     73             m_height = master.winfo_screenheight()
     74             m_x = m_y = 0
     75         w_width = widget.winfo_reqwidth()
     76         w_height = widget.winfo_reqheight()
     77         x = m_x + (m_width - w_width) * relx
     78         y = m_y + (m_height - w_height) * rely
     79         if x+w_width > master.winfo_screenwidth():
     80             x = master.winfo_screenwidth() - w_width
     81         elif x < 0:
     82             x = 0
     83         if y+w_height > master.winfo_screenheight():
     84             y = master.winfo_screenheight() - w_height
     85         elif y < 0:
     86             y = 0
     87         widget.geometry("+%d+%d" % (x, y))
     88         widget.deiconify() # Become visible at the desired location
     89 
     90     def go(self):
     91         self.root.wait_visibility()
     92         self.root.grab_set()
     93         self.root.mainloop()
     94         self.root.destroy()
     95         return self.num
     96 
     97     def return_event(self, event):
     98         if self.default is None:
     99             self.root.bell()
    100         else:
    101             self.done(self.default)
    102 
    103     def wm_delete_window(self):
    104         if self.cancel is None:
    105             self.root.bell()
    106         else:
    107             self.done(self.cancel)
    108 
    109     def done(self, num):
    110         self.num = num
    111         self.root.quit()
    112 
    113 
    114 class Dialog(Toplevel):
    115 
    116     '''Class to open dialogs.
    117 
    118     This class is intended as a base class for custom dialogs
    119     '''
    120 
    121     def __init__(self, parent, title = None):
    122 
    123         '''Initialize a dialog.
    124 
    125         Arguments:
    126 
    127             parent -- a parent window (the application window)
    128 
    129             title -- the dialog title
    130         '''
    131         Toplevel.__init__(self, parent)
    132 
    133         self.withdraw() # remain invisible for now
    134         # If the master is not viewable, don't
    135         # make the child transient, or else it
    136         # would be opened withdrawn
    137         if parent.winfo_viewable():
    138             self.transient(parent)
    139 
    140         if title:
    141             self.title(title)
    142 
    143         self.parent = parent
    144 
    145         self.result = None
    146 
    147         body = Frame(self)
    148         self.initial_focus = self.body(body)
    149         body.pack(padx=5, pady=5)
    150 
    151         self.buttonbox()
    152 
    153         if not self.initial_focus:
    154             self.initial_focus = self
    155 
    156         self.protocol("WM_DELETE_WINDOW", self.cancel)
    157 
    158         if self.parent is not None:
    159             self.geometry("+%d+%d" % (parent.winfo_rootx()+50,
    160                                       parent.winfo_rooty()+50))
    161 
    162         self.deiconify() # become visible now
    163 
    164         self.initial_focus.focus_set()
    165 
    166         # wait for window to appear on screen before calling grab_set
    167         self.wait_visibility()
    168         self.grab_set()
    169         self.wait_window(self)
    170 
    171     def destroy(self):
    172         '''Destroy the window'''
    173         self.initial_focus = None
    174         Toplevel.destroy(self)
    175 
    176     #
    177     # construction hooks
    178 
    179     def body(self, master):
    180         '''create dialog body.
    181 
    182         return widget that should have initial focus.
    183         This method should be overridden, and is called
    184         by the __init__ method.
    185         '''
    186         pass
    187 
    188     def buttonbox(self):
    189         '''add standard button box.
    190 
    191         override if you do not want the standard buttons
    192         '''
    193 
    194         box = Frame(self)
    195 
    196         w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE)
    197         w.pack(side=LEFT, padx=5, pady=5)
    198         w = Button(box, text="Cancel", width=10, command=self.cancel)
    199         w.pack(side=LEFT, padx=5, pady=5)
    200 
    201         self.bind("<Return>", self.ok)
    202         self.bind("<Escape>", self.cancel)
    203 
    204         box.pack()
    205 
    206     #
    207     # standard button semantics
    208 
    209     def ok(self, event=None):
    210 
    211         if not self.validate():
    212             self.initial_focus.focus_set() # put focus back
    213             return
    214 
    215         self.withdraw()
    216         self.update_idletasks()
    217 
    218         try:
    219             self.apply()
    220         finally:
    221             self.cancel()
    222 
    223     def cancel(self, event=None):
    224 
    225         # put focus back to the parent window
    226         if self.parent is not None:
    227             self.parent.focus_set()
    228         self.destroy()
    229 
    230     #
    231     # command hooks
    232 
    233     def validate(self):
    234         '''validate the data
    235 
    236         This method is called automatically to validate the data before the
    237         dialog is destroyed. By default, it always validates OK.
    238         '''
    239 
    240         return 1 # override
    241 
    242     def apply(self):
    243         '''process the data
    244 
    245         This method is called automatically to process the data, *after*
    246         the dialog is destroyed. By default, it does nothing.
    247         '''
    248 
    249         pass # override
    250 
    251 
    252 # --------------------------------------------------------------------
    253 # convenience dialogues
    254 
    255 class _QueryDialog(Dialog):
    256 
    257     def __init__(self, title, prompt,
    258                  initialvalue=None,
    259                  minvalue = None, maxvalue = None,
    260                  parent = None):
    261 
    262         if not parent:
    263             parent = tkinter._default_root
    264 
    265         self.prompt   = prompt
    266         self.minvalue = minvalue
    267         self.maxvalue = maxvalue
    268 
    269         self.initialvalue = initialvalue
    270 
    271         Dialog.__init__(self, parent, title)
    272 
    273     def destroy(self):
    274         self.entry = None
    275         Dialog.destroy(self)
    276 
    277     def body(self, master):
    278 
    279         w = Label(master, text=self.prompt, justify=LEFT)
    280         w.grid(row=0, padx=5, sticky=W)
    281 
    282         self.entry = Entry(master, name="entry")
    283         self.entry.grid(row=1, padx=5, sticky=W+E)
    284 
    285         if self.initialvalue is not None:
    286             self.entry.insert(0, self.initialvalue)
    287             self.entry.select_range(0, END)
    288 
    289         return self.entry
    290 
    291     def validate(self):
    292         try:
    293             result = self.getresult()
    294         except ValueError:
    295             messagebox.showwarning(
    296                 "Illegal value",
    297                 self.errormessage + "\nPlease try again",
    298                 parent = self
    299             )
    300             return 0
    301 
    302         if self.minvalue is not None and result < self.minvalue:
    303             messagebox.showwarning(
    304                 "Too small",
    305                 "The allowed minimum value is %s. "
    306                 "Please try again." % self.minvalue,
    307                 parent = self
    308             )
    309             return 0
    310 
    311         if self.maxvalue is not None and result > self.maxvalue:
    312             messagebox.showwarning(
    313                 "Too large",
    314                 "The allowed maximum value is %s. "
    315                 "Please try again." % self.maxvalue,
    316                 parent = self
    317             )
    318             return 0
    319 
    320         self.result = result
    321 
    322         return 1
    323 
    324 
    325 class _QueryInteger(_QueryDialog):
    326     errormessage = "Not an integer."
    327     def getresult(self):
    328         return self.getint(self.entry.get())
    329 
    330 def askinteger(title, prompt, **kw):
    331     '''get an integer from the user
    332 
    333     Arguments:
    334 
    335         title -- the dialog title
    336         prompt -- the label text
    337         **kw -- see SimpleDialog class
    338 
    339     Return value is an integer
    340     '''
    341     d = _QueryInteger(title, prompt, **kw)
    342     return d.result
    343 
    344 class _QueryFloat(_QueryDialog):
    345     errormessage = "Not a floating point value."
    346     def getresult(self):
    347         return self.getdouble(self.entry.get())
    348 
    349 def askfloat(title, prompt, **kw):
    350     '''get a float from the user
    351 
    352     Arguments:
    353 
    354         title -- the dialog title
    355         prompt -- the label text
    356         **kw -- see SimpleDialog class
    357 
    358     Return value is a float
    359     '''
    360     d = _QueryFloat(title, prompt, **kw)
    361     return d.result
    362 
    363 class _QueryString(_QueryDialog):
    364     def __init__(self, *args, **kw):
    365         if "show" in kw:
    366             self.__show = kw["show"]
    367             del kw["show"]
    368         else:
    369             self.__show = None
    370         _QueryDialog.__init__(self, *args, **kw)
    371 
    372     def body(self, master):
    373         entry = _QueryDialog.body(self, master)
    374         if self.__show is not None:
    375             entry.configure(show=self.__show)
    376         return entry
    377 
    378     def getresult(self):
    379         return self.entry.get()
    380 
    381 def askstring(title, prompt, **kw):
    382     '''get a string from the user
    383 
    384     Arguments:
    385 
    386         title -- the dialog title
    387         prompt -- the label text
    388         **kw -- see SimpleDialog class
    389 
    390     Return value is a string
    391     '''
    392     d = _QueryString(title, prompt, **kw)
    393     return d.result
    394 
    395 
    396 
    397 if __name__ == '__main__':
    398 
    399     def test():
    400         root = Tk()
    401         def doit(root=root):
    402             d = SimpleDialog(root,
    403                          text="This is a test dialog.  "
    404                               "Would this have been an actual dialog, "
    405                               "the buttons below would have been glowing "
    406                               "in soft pink light.\n"
    407                               "Do you believe this?",
    408                          buttons=["Yes", "No", "Cancel"],
    409                          default=0,
    410                          cancel=2,
    411                          title="Test Dialog")
    412             print(d.go())
    413             print(askinteger("Spam", "Egg count", initialvalue=12*12))
    414             print(askfloat("Spam", "Egg weight\n(in tons)", minvalue=1,
    415                            maxvalue=100))
    416             print(askstring("Spam", "Egg label"))
    417         t = Button(root, text='Test', command=doit)
    418         t.pack()
    419         q = Button(root, text='Quit', command=t.quit)
    420         q.pack()
    421         t.mainloop()
    422 
    423     test()
    424