I just started learning how to create a custom pop up dialog box; and as it turns out, the tkinter messagebox
is really easy to use, but it also does not do too much. Here is my attempt to create a dialog box that will take input and then store that in the username.
My question is what is the recommended style to implement this? As Bryan Oakley suggested in this comment.
I would advise against using a global variable. Instead of having the dialog destroy itself, have it destroy only the actual widget but leave the object alive. Then, call something like
inputDialog.get_string()
and thendel inputDialog
from your main logic.
Maybe using the global variable to return my string is not the best idea, but why? And what is the suggested way? I get confused because I don't know how to trigger the getstring once the window is destroyed, and... the line about destroying the actual widget, I am not sure if he is referring to TopLevel
.
The reason I ask is because I want the pop up box to be destroyed after I press the submit button; because after all, I want it to resume back to the main program, update something, etc. What should the button method send
do in this case? Because the idea in this particular example is to allow the user to do it over and over, if he desires.
import tkinter as tk class MyDialog: def __init__(self, parent): top = self.top = tk.Toplevel(parent) self.myLabel = tk.Label(top, text='Enter your username below') self.myLabel.pack() self.myEntryBox = tk.Entry(top) self.myEntryBox.pack() self.mySubmitButton = tk.Button(top, text='Submit', command=self.send) self.mySubmitButton.pack() def send(self): global username username = self.myEntryBox.get() self.top.destroy() def onClick(): inputDialog = MyDialog(root) root.wait_window(inputDialog.top) print('Username: ', username) username = 'Empty' root = tk.Tk() mainLabel = tk.Label(root, text='Example for pop up input box') mainLabel.pack() mainButton = tk.Button(root, text='Click me', command=onClick) mainButton.pack() root.mainloop()
Popup window in Tkinter can be created by defining the Toplevel(win) window. A Toplevel window manages to create a child window along with the parent window. It always opens above all the other windows defined in any application.
Using the global statement is unnecessary in the two scenarios that come to mind.
Avoiding the global statement can be accomplished by passing a dictionary & key when you create an instance of a dialog box. The dictionary & key can then be associated with the button's command, by using lambda. That creates an anonymous function that will execute your function call (with args) when the button is pressed.
You can avoid the need to pass the parent every time you create an instance of the dialog box by binding the parent to a class attribute (root in this example).
You can save the following as mbox.py
in your_python_folder\Lib\site-packages
or in the same folder as your main GUI's file.
import tkinter class Mbox(object): root = None def __init__(self, msg, dict_key=None): """ msg = <str> the message to be displayed dict_key = <sequence> (dictionary, key) to associate with user input (providing a sequence for dict_key creates an entry for user input) """ tki = tkinter self.top = tki.Toplevel(Mbox.root) frm = tki.Frame(self.top, borderwidth=4, relief='ridge') frm.pack(fill='both', expand=True) label = tki.Label(frm, text=msg) label.pack(padx=4, pady=4) caller_wants_an_entry = dict_key is not None if caller_wants_an_entry: self.entry = tki.Entry(frm) self.entry.pack(pady=4) b_submit = tki.Button(frm, text='Submit') b_submit['command'] = lambda: self.entry_to_dict(dict_key) b_submit.pack() b_cancel = tki.Button(frm, text='Cancel') b_cancel['command'] = self.top.destroy b_cancel.pack(padx=4, pady=4) def entry_to_dict(self, dict_key): data = self.entry.get() if data: d, key = dict_key d[key] = data self.top.destroy()
You can see examples that subclass TopLevel and tkSimpleDialog (tkinter.simpledialog in py3) at effbot.
It's worth noting that ttk widgets are interchangeable with the tkinter widgets in this example.
To accurately center the dialog box read → this.
Example of use:
import tkinter import mbox root = tkinter.Tk() Mbox = mbox.Mbox Mbox.root = root D = {'user':'Bob'} b_login = tkinter.Button(root, text='Log in') b_login['command'] = lambda: Mbox('Name?', (D, 'user')) b_login.pack() b_loggedin = tkinter.Button(root, text='Current User') b_loggedin['command'] = lambda: Mbox(D['user']) b_loggedin.pack() root.mainloop()
Create a module containing a dialog box class (MessageBox here). Also, include a function that creates an instance of that class, and finally returns the value of the button pressed (or data from an Entry widget).
Here is a complete module that you can customize with the help of these references: NMTech & Effbot.
Save the following code as mbox.py
in your_python_folder\Lib\site-packages
import tkinter class MessageBox(object): def __init__(self, msg, b1, b2, frame, t, entry): root = self.root = tkinter.Tk() root.title('Message') self.msg = str(msg) # ctrl+c to copy self.msg root.bind('<Control-c>', func=self.to_clip) # remove the outer frame if frame=False if not frame: root.overrideredirect(True) # default values for the buttons to return self.b1_return = True self.b2_return = False # if b1 or b2 is a tuple unpack into the button text & return value if isinstance(b1, tuple): b1, self.b1_return = b1 if isinstance(b2, tuple): b2, self.b2_return = b2 # main frame frm_1 = tkinter.Frame(root) frm_1.pack(ipadx=2, ipady=2) # the message message = tkinter.Label(frm_1, text=self.msg) message.pack(padx=8, pady=8) # if entry=True create and set focus if entry: self.entry = tkinter.Entry(frm_1) self.entry.pack() self.entry.focus_set() # button frame frm_2 = tkinter.Frame(frm_1) frm_2.pack(padx=4, pady=4) # buttons btn_1 = tkinter.Button(frm_2, width=8, text=b1) btn_1['command'] = self.b1_action btn_1.pack(side='left') if not entry: btn_1.focus_set() btn_2 = tkinter.Button(frm_2, width=8, text=b2) btn_2['command'] = self.b2_action btn_2.pack(side='left') # the enter button will trigger the focused button's action btn_1.bind('<KeyPress-Return>', func=self.b1_action) btn_2.bind('<KeyPress-Return>', func=self.b2_action) # roughly center the box on screen # for accuracy see: https://stackoverflow.com/a/10018670/1217270 root.update_idletasks() xp = (root.winfo_screenwidth() // 2) - (root.winfo_width() // 2) yp = (root.winfo_screenheight() // 2) - (root.winfo_height() // 2) geom = (root.winfo_width(), root.winfo_height(), xp, yp) root.geometry('{0}x{1}+{2}+{3}'.format(*geom)) # call self.close_mod when the close button is pressed root.protocol("WM_DELETE_WINDOW", self.close_mod) # a trick to activate the window (on windows 7) root.deiconify() # if t is specified: call time_out after t seconds if t: root.after(int(t*1000), func=self.time_out) def b1_action(self, event=None): try: x = self.entry.get() except AttributeError: self.returning = self.b1_return self.root.quit() else: if x: self.returning = x self.root.quit() def b2_action(self, event=None): self.returning = self.b2_return self.root.quit() # remove this function and the call to protocol # then the close button will act normally def close_mod(self): pass def time_out(self): try: x = self.entry.get() except AttributeError: self.returning = None else: self.returning = x finally: self.root.quit() def to_clip(self, event=None): self.root.clipboard_clear() self.root.clipboard_append(self.msg)
and:
def mbox(msg, b1='OK', b2='Cancel', frame=True, t=False, entry=False): """Create an instance of MessageBox, and get data back from the user. msg = string to be displayed b1 = text for left button, or a tuple (<text for button>, <to return on press>) b2 = text for right button, or a tuple (<text for button>, <to return on press>) frame = include a standard outerframe: True or False t = time in seconds (int or float) until the msgbox automatically closes entry = include an entry widget that will have its contents returned: True or False """ msgbox = MessageBox(msg, b1, b2, frame, t, entry) msgbox.root.mainloop() # the function pauses here until the mainloop is quit msgbox.root.destroy() return msgbox.returning
After mbox creates an instance of MessageBox it starts the mainloop,
which effectively stops the function there until the mainloop is exited via root.quit()
.
The mbox function can then access msgbox.returning
, and return its value.
Example:
user = {} mbox('starting in 1 second...', t=1) user['name'] = mbox('name?', entry=True) if user['name']: user['sex'] = mbox('male or female?', ('male', 'm'), ('female', 'f')) mbox(user, frame=False)
Since the object inputDialog is not destroyed, I was able to access the object attribute. I added the return string as an attribute:
import tkinter as tk class MyDialog: def __init__(self, parent): top = self.top = tk.Toplevel(parent) self.myLabel = tk.Label(top, text='Enter your username below') self.myLabel.pack() self.myEntryBox = tk.Entry(top) self.myEntryBox.pack() self.mySubmitButton = tk.Button(top, text='Submit', command=self.send) self.mySubmitButton.pack() def send(self): self.username = self.myEntryBox.get() self.top.destroy() def onClick(): inputDialog = MyDialog(root) root.wait_window(inputDialog.top) print('Username: ', inputDialog.username) root = tk.Tk() mainLabel = tk.Label(root, text='Example for pop up input box') mainLabel.pack() mainButton = tk.Button(root, text='Click me', command=onClick) mainButton.pack() root.mainloop()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With