正确的方法来实现自定义弹出窗口对话框 [英] Correct way to implement a custom popup tkinter dialog box
问题描述
我刚开始学习如何创建一个自定义的弹出对话框;而事实证明, tkinter messagebox
真的很容易使用,但也不会太多。这是我尝试创建一个对话框,将输入并存储在用户名。
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.
我的问题是推荐的风格是什么?正如Bryan Oakley在此评论中所建议的。
My question is what is the recommended style to implement this? As Bryan Oakley suggested in this comment.
Bryan Oakley写道:
Bryan Oakley wrote:
我建议不要使用全局变量。而不是让对话框破坏自己,它只会破坏实际的小部件,而使对象活着。然后,调用类似于inputDialog.get_string(),然后调用del inputDialog从你的主逻辑。
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 then del inputDialog from your main logic.
也许使用全局变量返回我的字符串不是最好的主意,但为什么?那建议的方法是什么?我感到困惑,因为我不知道如何在窗口被破坏时触发getstring,而关于摧毁实际小部件的行,我不知道他是否指的是 TopLevel
。
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()
感谢您的建议和提示。 / p>
Thank you for your suggestion and tip.
推荐答案
使用全局语句在需要考虑的两种情况下是不必要的。
Using the global statement is unnecessary in the two scenarios that come to mind.
- 你想要编辑一个可以导入的对话框,使用使用主GUI
- 您想要编辑一个可导入的对话框,使用而不使用主GUI
- you want to code a dialog box that can be imported to use with a main GUI
- you want to code a dialog box that can be imported to use without a main GUI
可以导入一个对话框,使用使用主GUI
可以通过传递一个字典&键创建对话框的实例。字典&键可以通过使用 lambda 与按钮的命令相关联。这将创建一个匿名函数,将在按下按钮时执行您的函数调用(使用args)。
code a dialog box that can be imported to use with a main GUI
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.
您可以避免在每次创建一个将对象框的实例绑定到一个类属性(在本例中为root)。
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).
您可以将 mbox.py
保存在 your_python_folder\Lib\或者与主GUI文件相同的文件夹。
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()
你可以看到在 effbot 。
值得注意的是,小工具可以互换这个例子中的tkinter小部件。
It's worth noting that ttk widgets are interchangeable with the tkinter widgets in this example.
为了准确对齐对话框,请点击→这个
To accurately center the dialog box read → this.
使用示例:
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()
$ b
创建一个包含对话框类(MessageBox)的模块。此外,还包括一个创建该类的实例的函数,最后返回按下的按钮的值(或来自条目窗口小部件的数据)。
code a dialog box that can be imported to use without a main GUI
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).
这是一个完整的模块,您可以在以下参考资料的帮助下进行自定义: NMTech & Effbot 。
将以下代码另存为 mbox.py
your_python_folder\Lib\site-packages
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: http://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)
和:
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
在 mbox 创建一个 MessageBox 的实例后,它将启动mainloop,
,通过 root.quit()
退出mainloop,可以有效地停止该功能。
mbox 函数可以访问 m sgbox.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.
示例:
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)
这篇关于正确的方法来实现自定义弹出窗口对话框的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!