正确的方法来实现自定义弹出tkinter对话框

时间:2012-04-07 19:34:05

标签: python tkinter dialog tkmessagebox

我刚开始学习如何创建自定义弹出对话框;事实证明,tkinter messagebox非常容易使用,但它也没有做太多。这是我尝试创建一个对话框,该对话框将接收输入,然后将其存储在用户名中。

我的问题是推荐的实施方式是什么?正如布莱恩·奥克利在this comment中所说的那样。

  

我建议不要使用全局变量。不要让对话框自行销毁,而是让它只销毁实际的小部件,但让对象保持活着状态。然后,从主逻辑中调用类似inputDialog.get_string()然后del inputDialog的内容。

也许使用全局变量来返回我的字符串不是最好的主意,但为什么呢?建议的方式是什么?我感到困惑,因为一旦窗口被销毁,我不知道如何触发getstring,以及...关于销毁实际widget的行,我不确定他是否指的是TopLevel

我问的原因是因为我想按下提交按钮后弹出框被销毁;因为毕竟,我希望它恢复到主程序,更新一些东西等等。在这种情况下,按钮方法send应该做什么?因为这个特定例子中的想法是允许用户一遍又一遍地做,如果他愿意的话。

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

5 个答案:

答案 0 :(得分:29)

在我想到的两个场景中,不需要使用global statement

  1. 您想编写一个可导入的对话框,以便使用主GUI
  2. 您想编写一个对话框,可以导入该对话框以使用而不使用主GUI

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


    避免全局声明可以通过传递字典&创建对话框的实例时键。字典&然后,可以使用lambda将键与按钮的命令相关联。这创建了一个匿名函数,当按下按钮时,它将执行函数调用(使用args)。

    通过将父级绑定到类属性(本示例中为root),每次创建对话框实例时都可以避免传递父级。

    您可以将以下内容保存为mbox.py your_python_folder\Lib\site-packages或与主GUI文件位于同一文件夹中。

    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 widgets可以与此示例中的tkinter小部件互换。

    要准确居中对话框,请阅读→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小部件中的数据)。

    以下是一个完整的模块,您可以借助这些参考资料进行自定义:NMTech&amp; Effbot
    将以下代码保存为mbox.py

    中的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)
    

    和:

    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并返回其值。

    示例:

    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)
    

答案 1 :(得分:9)

由于没有销毁对象inputDialog,我能够访问object属性。我将返回字符串添加为属性:

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

答案 2 :(得分:4)

可以使用simpledialog代替使用消息框。它也是tkinter的一部分。它就像一个模板,而不是完全定义您自己的类。 simpledialog解决了必须自己添加“确定”和“取消”按钮的问题。我本人也遇到了这个问题,java2s上有一个很好的示例,说明了如何使用简单对话框创建自定义对话框。这是他们的两个文本字段和两个标签对话框的示例。尽管它是Python 2,所以您需要对其进行更改。希望这会有所帮助:)

from Tkinter import *
import tkSimpleDialog

class MyDialog(tkSimpleDialog.Dialog):

    def body(self, master):

        Label(master, text="First:").grid(row=0)
        Label(master, text="Second:").grid(row=1)

        self.e1 = Entry(master)
        self.e2 = Entry(master)

        self.e1.grid(row=0, column=1)
        self.e2.grid(row=1, column=1)
        return self.e1 # initial focus

    def apply(self):
        first = self.e1.get()
        second = self.e2.get()
        print first, second 

root = Tk()
d = MyDialog(root)
print d.result

来源:http://www.java2s.com/Code/Python/GUI-Tk/Asimpledialogwithtwolabelsandtwotextfields.htm

答案 3 :(得分:2)

我使用了Honest Abe's 2nd part of the code标题为

  

编写一个对话框,无需主GUI即可导入使用

作为模板并进行了一些修改。我需要一个组合框而不是输入框,因此我也实现了它。如果您还需要其他功能,则修改起来应该很容易。

以下是更改

  • 充当孩子
  • 对父母的模范
  • 以父母为中心
  • 不可调整大小
  • 组合框而不是条目
  • 单击十字(X)以关闭对话框

已删除

  • 框架,计时器,剪贴板

将以下内容另存为char中的int或与主GUI文件相同的文件夹中。

mbox.py

它应该快速且易于使用。这是一个示例:

your_python_folder\Lib\site-packages

答案 4 :(得分:1)

// get a count of all the current table rows
    let rows = 0;
    cy.get('table').find('tr').its('length')
        .then((l) => {
          console.log(l + ' rows detected')
          rows = l
          console.log(' rows set to ', rows)
          // NOTE this is set to 18 but only
          // after I have done the evaluation below

       //this should now work
       cy.get('table').find('tr').its('length')
       .should('be.lt', rows) 

    })