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