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