实现自定义弹出 tkinter 对话框的正确方法 [英] Correct way to implement a custom popup tkinter dialog box

查看:23
本文介绍了实现自定义弹出 tkinter 对话框的正确方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我刚开始学习如何创建自定义弹出对话框;事实证明,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.

我建议不要使用全局变量.与其让对话框销毁自己,不如让它只销毁实际的小部件,但让对象保持活动状态.然后,从您的主要逻辑中调用类似 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.

我之所以这么问,是因为我想让弹出框在我按下提交按钮后被销毁;因为毕竟,我想让它恢复到主程序,更新某些东西等等.在这种情况下,按钮方法 send 应该做什么?因为这个特定示例中的想法是允许用户根据需要反复执行此操作.

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()

推荐答案

使用 全局声明 是不必要的.

  1. 您想编写一个对话框,该对话框可以导入使用主 GUI
  2. 您想要编写一个对话框,该对话框可以导入使用主 GUI
  1. you want to code a dialog box that can be imported to use with a main GUI
  2. 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.pyyour_python_folderLibsite-packages 或与主 GUI 文件相同的文件夹中.

You can save the following as mbox.py in your_python_folderLibsite-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 看到子类 TopLevel 和 tkSimpleDialog(py3 中的 tkinter.simpledialog)的示例.

值得注意的是,ttk 小部件可与本示例中的 tkinter 小部件互换.

It's worth noting that ttk widgets are interchangeable with the tkinter widgets in this example.

要准确居中对话框阅读 → this.

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()

<小时>

编写一个对话框,可以导入使用主 GUI

<小时>

创建一个包含对话框类的模块(此处为 MessageBox).此外,包括一个创建该类实例的函数,并最终返回按下的按钮的值(或来自 Entry 小部件的数据).


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.
your_python_folderLibsite-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_folderLibsite-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)

和:

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 的实例后,它启动主循环,
这有效地停止了该函数,直到通过 root.quit() 退出主循环.
mbox 函数然后可以访问 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.

示例:

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)

这篇关于实现自定义弹出 tkinter 对话框的正确方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆