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 28 29import tkinter # used at _QueryDialog for tkinter._default_root 30 31 32class SimpleDialog: 33 34 def __init__(self, master, 35 text='', buttons=[], default=None, cancel=None, 36 title=None, class_=None): 37 if class_: 38 self.root = Toplevel(master, class_=class_) 39 else: 40 self.root = Toplevel(master) 41 if title: 42 self.root.title(title) 43 self.root.iconname(title) 44 self.message = Message(self.root, text=text, aspect=400) 45 self.message.pack(expand=1, fill=BOTH) 46 self.frame = Frame(self.root) 47 self.frame.pack() 48 self.num = default 49 self.cancel = cancel 50 self.default = default 51 self.root.bind('<Return>', self.return_event) 52 for num in range(len(buttons)): 53 s = buttons[num] 54 b = Button(self.frame, text=s, 55 command=(lambda self=self, num=num: self.done(num))) 56 if num == default: 57 b.config(relief=RIDGE, borderwidth=8) 58 b.pack(side=LEFT, fill=BOTH, expand=1) 59 self.root.protocol('WM_DELETE_WINDOW', self.wm_delete_window) 60 self._set_transient(master) 61 62 def _set_transient(self, master, relx=0.5, rely=0.3): 63 widget = self.root 64 widget.withdraw() # Remain invisible while we figure out the geometry 65 widget.transient(master) 66 widget.update_idletasks() # Actualize geometry information 67 if master.winfo_ismapped(): 68 m_width = master.winfo_width() 69 m_height = master.winfo_height() 70 m_x = master.winfo_rootx() 71 m_y = master.winfo_rooty() 72 else: 73 m_width = master.winfo_screenwidth() 74 m_height = master.winfo_screenheight() 75 m_x = m_y = 0 76 w_width = widget.winfo_reqwidth() 77 w_height = widget.winfo_reqheight() 78 x = m_x + (m_width - w_width) * relx 79 y = m_y + (m_height - w_height) * rely 80 if x+w_width > master.winfo_screenwidth(): 81 x = master.winfo_screenwidth() - w_width 82 elif x < 0: 83 x = 0 84 if y+w_height > master.winfo_screenheight(): 85 y = master.winfo_screenheight() - w_height 86 elif y < 0: 87 y = 0 88 widget.geometry("+%d+%d" % (x, y)) 89 widget.deiconify() # Become visible at the desired location 90 91 def go(self): 92 self.root.wait_visibility() 93 self.root.grab_set() 94 self.root.mainloop() 95 self.root.destroy() 96 return self.num 97 98 def return_event(self, event): 99 if self.default is None: 100 self.root.bell() 101 else: 102 self.done(self.default) 103 104 def wm_delete_window(self): 105 if self.cancel is None: 106 self.root.bell() 107 else: 108 self.done(self.cancel) 109 110 def done(self, num): 111 self.num = num 112 self.root.quit() 113 114 115class Dialog(Toplevel): 116 117 '''Class to open dialogs. 118 119 This class is intended as a base class for custom dialogs 120 ''' 121 122 def __init__(self, parent, title = None): 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 255class _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 325class _QueryInteger(_QueryDialog): 326 errormessage = "Not an integer." 327 328 def getresult(self): 329 return self.getint(self.entry.get()) 330 331 332def askinteger(title, prompt, **kw): 333 '''get an integer from the user 334 335 Arguments: 336 337 title -- the dialog title 338 prompt -- the label text 339 **kw -- see SimpleDialog class 340 341 Return value is an integer 342 ''' 343 d = _QueryInteger(title, prompt, **kw) 344 return d.result 345 346 347class _QueryFloat(_QueryDialog): 348 errormessage = "Not a floating point value." 349 350 def getresult(self): 351 return self.getdouble(self.entry.get()) 352 353 354def askfloat(title, prompt, **kw): 355 '''get a float from the user 356 357 Arguments: 358 359 title -- the dialog title 360 prompt -- the label text 361 **kw -- see SimpleDialog class 362 363 Return value is a float 364 ''' 365 d = _QueryFloat(title, prompt, **kw) 366 return d.result 367 368 369class _QueryString(_QueryDialog): 370 def __init__(self, *args, **kw): 371 if "show" in kw: 372 self.__show = kw["show"] 373 del kw["show"] 374 else: 375 self.__show = None 376 _QueryDialog.__init__(self, *args, **kw) 377 378 def body(self, master): 379 entry = _QueryDialog.body(self, master) 380 if self.__show is not None: 381 entry.configure(show=self.__show) 382 return entry 383 384 def getresult(self): 385 return self.entry.get() 386 387 388def askstring(title, prompt, **kw): 389 '''get a string from the user 390 391 Arguments: 392 393 title -- the dialog title 394 prompt -- the label text 395 **kw -- see SimpleDialog class 396 397 Return value is a string 398 ''' 399 d = _QueryString(title, prompt, **kw) 400 return d.result 401 402 403if __name__ == '__main__': 404 405 def test(): 406 root = Tk() 407 def doit(root=root): 408 d = SimpleDialog(root, 409 text="This is a test dialog. " 410 "Would this have been an actual dialog, " 411 "the buttons below would have been glowing " 412 "in soft pink light.\n" 413 "Do you believe this?", 414 buttons=["Yes", "No", "Cancel"], 415 default=0, 416 cancel=2, 417 title="Test Dialog") 418 print(d.go()) 419 print(askinteger("Spam", "Egg count", initialvalue=12*12)) 420 print(askfloat("Spam", "Egg weight\n(in tons)", minvalue=1, 421 maxvalue=100)) 422 print(askstring("Spam", "Egg label")) 423 t = Button(root, text='Test', command=doit) 424 t.pack() 425 q = Button(root, text='Quit', command=t.quit) 426 q.pack() 427 t.mainloop() 428 429 test() 430