17

Вопрос по python – Правильный способ реализации настраиваемого всплывающего диалогового окна tkinter

Я только начал изучать, как создать собственное всплывающее диалоговое окно; и, как выясняется,tkinter messagebox действительно прост в использовании, но он также не делает слишком много. Вот моя попытка создать диалоговое окно, которое будет принимать ввод и затем сохранять его в имени пользователя.

Мой вопрос: какой стиль рекомендуется использовать для реализации этого? Как Брайан Оукли предложил вэтот комментарий.

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.

Может быть, использование глобальной переменной для возврата моей строки не лучшая идея, но почему? И каков предложенный способ? Я запутался, потому что я не знаю, как вызвать gettring после того, как окно разрушено, и ... строка об уничтожении фактического виджета, я не уверен, что он ссылается на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()
  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • 9

    Поскольку объект inputDialog не уничтожен

    я смог получить доступ к атрибуту объекта. Я добавил возвращаемую строку в качестве атрибута:

    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

    я использовал

    Честная Абэ 2-я часть кода под названием:

    code a dialog box that can be imported to use without a main GUI

    в качестве шаблона и внесены некоторые изменения. Мне нужен комбо-бокс вместо входа, поэтому я также реализовал его. Если вам нужно что-то еще, это должно быть довольно легко изменить.

    Following are the changes

    Acts as a child Modal to the parent Centered on top of the parent Not resizable Combobox instead of entry Click cross (X) to close the dialog

    Removed

    frame, timer, clipboard

    Сохранить следующее какmbox.py вyour_python_folder\Lib\site-packages или в той же папке, что и файл основного графического интерфейса пользователя.

    import tkinter
    import tkinter.ttk as ttk
    
    class MessageBox(object):
    
        def __init__(self, msg, b1, b2, parent, cbo, cboList):
    
            root = self.root = tkinter.Toplevel(parent)
    
            root.title('Choose')
            root.geometry('100x100')
            root.resizable(False, False)
            root.grab_set() # modal
    
            self.msg = str(msg)
            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)
            if cbo: message.pack(padx=8, pady=8)
            else: message.pack(padx=8, pady=20)
            # if entry=True create and set focus
            if cbo:
                self.cbo = ttk.Combobox(frm_1, state="readonly", justify="center", values= cboList)
                self.cbo.pack()
                self.cbo.focus_set()
                self.cbo.current(0)
            # 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
            if cbo: btn_1.pack(side='left', padx=5)
            else: btn_1.pack(side='left', padx=10)
            if not cbo: btn_1.focus_set()
            btn_2 = tkinter.Button(frm_2, width=8, text=b2)
            btn_2['command'] = self.b2_action
            if cbo: btn_2.pack(side='left', padx=5)
            else: btn_2.pack(side='left', padx=10)
            # 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()
            root.geometry("210x110+%d+%d" % (parent.winfo_rootx()+7,
                                             parent.winfo_rooty()+70))
    
            root.protocol("WM_DELETE_WINDOW", self.close_mod)
    
            # a trick to activate the window (on windows 7)
            root.deiconify()
    
        def b1_action(self, event=None):
            try: x = self.cbo.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 = sel,f.b2_return
            self.root.quit()
    
        def close_mod(self):
            # top right corner cross click: return value ;`x`;
            # we need to send it a value, otherwise there will be an exception when closing parent window
            self.returning = ";`x`;"
            self.root.quit()
    

    Он должен быть быстрым и простым в использовании. Вот пример:

    from mbox import MessageBox
    from tkinter import *
    
    root = Tk()
    
    
    def mbox(msg, b1, b2, parent, cbo=False, cboList=[]):
        msgbox = MessageBox(msg, b1, b2, parent, cbo, cboList)
        msgbox.root.mainloop()
        msgbox.root.destroy()
        return msgbox.returning
    
    
    prompt = {}
    
    # it will only show 2 buttons & 1 label if (cbo and cboList) aren't provided
    # click on 'x' will return ;`x`;
    prompt['answer'] = mbox('Do you want to go?', ('Go', 'go'), ('Cancel', 'cancel'), root)
    ans = prompt['answer']
    print(ans)
    if ans == 'go':
        # do stuff
        pass
    else:
        # do stuff
        pass
    
    
    allowedItems = ['phone','laptop','battery']
    prompt['answer'] = mbox('Select product to, take', ('Take', 'take'), ('Cancel', 'cancel'), root, cbo=True, cboList=allowedItems)
    ans = prompt['answer']
    print(ans)
    if (ans == 'phone'):
        # do stuff
        pass
    elif (ans == 'laptop'):
        # do stuff
        pass
    else:
        # do stuff
        pass
    

  • 29

    С использованием

    глобальное заявление не нужно в двух сценариях, которые приходят на ум.

    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 code a dialog box that can be imported to use with a main GUI

    Избежать глобального оператора можно, передав словарь & amp; ключ при создании экземпляра диалогового окна. Словарь & amp; ключ может быть связан с командой кнопки, используялямбда, Это создает анонимную функцию, которая будет выполнять вызов вашей функции (с аргументами) при нажатии кнопки.

    Вы можете избежать необходимости передавать родительский элемент каждый раз, когда создаете экземпляр диалогового окна, привязывая родительский элемент к атрибуту класса (в данном примере root).

    Вы можете сохранить следующее какmbox.py вyour_python_folder\Lib\site-packages или в той же папке, что и файл основного графического интерфейса пользователя.

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

    Вы можете увидеть примеры, которые подкласс TopLevel и tkSimpleDialog (tkinter.simpledialog в py3) вeffbot.

    Стоит отметить, чтоттк виджеты взаимозаменяемы с виджетами tkinter в этом примере.

    Чтобы точно отцентрировать диалоговое окно, прочитайте & # x2192;этот.

    Пример использования:

    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()
    
    code a dialog box that can be imported to use without a main GUI

    Создайте модуль, содержащий класс диалогового окна (MessageBox здесь). Также добавьте функцию, которая создает экземпляр этого класса и, наконец, возвращает значение нажатой кнопки (или данные из виджета Entry).

    Вот полный модуль, который вы можете настроить с помощью этих ссылок:NMTech & Амп;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)