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