복붙노트

[PYTHON] 사용자 정의 팝업 tkinter 대화 상자를 구현하는 올바른 방법

PYTHON

사용자 정의 팝업 tkinter 대화 상자를 구현하는 올바른 방법

방금 사용자 지정 팝업 대화 상자를 만드는 방법을 배우기 시작했습니다. 실제로 tkinter messagebox는 사용하기가 쉽지만 너무 많이하지는 않습니다. 다음은 입력을 받아서 사용자 이름에 저장하는 대화 상자를 만드는 나의 시도입니다.

내 질문은 이것을 구현하는 데 권장되는 스타일은 무엇입니까? Bryan Oakley가이 의견에서 제안했듯이.

어쩌면 전역 변수를 사용하여 내 문자열을 반환하는 것이 가장 좋은 방법은 아니지만 그 이유는 무엇입니까? 그리고 제안 된 방법은 무엇입니까? 창문이 파손되면 어떻게 끈을 묶는지를 모르기 때문에 혼란스러워집니다. 그리고 실제 위젯을 파괴하는 것에 대해, 그가 TopLevel을 참조하는지 확신 할 수 없습니다.

내가 물어 보는 이유는 제출 버튼을 누른 후에 팝업 상자가 파괴되기를 원하기 때문입니다. 결국 주 프로그램으로 다시 돌아가서 무언가를 업데이트하기를 원하기 때문입니다.이 경우 버튼 메서드가 무엇을 보내야합니까? 이 특정 예제의 아이디어는 사용자가 원하는 경우 반복해서 수행하도록 허용하기위한 것입니다.

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. ==============================

    1.마음에 오는 두 가지 시나리오에서 global 문을 사용하는 것은 불필요합니다.

    마음에 오는 두 가지 시나리오에서 global 문을 사용하는 것은 불필요합니다.

    대화 상자의 인스턴스를 만들 때 사전과 키를 전달하면 전역 명령문을 피할 수 있습니다. 그런 다음 람다를 사용하여 사전 및 키를 단추의 명령과 연결할 수 있습니다. 이렇게하면 버튼을 눌렀을 때 (args로) 함수 호출을 실행할 익명의 함수가 생성됩니다.

    부모를 클래스 속성 (이 예제에서는 root)에 바인딩하여 대화 상자의 인스턴스를 만들 때마다 부모를 전달할 필요가 없습니다.

    your_python_folder \ Lib \ site-packages 또는 메인 GUI의 파일과 같은 폴더에 mbox.py로 다음을 저장할 수 있습니다.

    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 위젯과 호환 될 수 있다는 점에 유의할 가치가 있습니다.

    대화 상자의 중앙을 정확히 맞추려면 → 이것을 읽으십시오.

    사용 예 :

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

    대화 상자 클래스 (여기 MessageBox)가 포함 된 모듈을 만듭니다. 또한 해당 클래스의 인스턴스를 생성하고 마지막으로 눌려진 버튼의 값 (또는 Entry 위젯의 데이터)을 반환하는 함수를 포함하십시오.

    다음은 NMTech & Effbot의 도움으로 사용자 정의 할 수있는 완벽한 모듈입니다. 다음 코드를 your_python_folder \ Lib \ site-packages에 mbox.py로 저장하십시오.

    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)
    
  2. ==============================

    2.inputDialog 객체가 파괴되지 않았으므로 객체 속성에 액세스 할 수있었습니다. 반환 문자열을 특성으로 추가했습니다.

    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()
    
  3. ==============================

    3.나는 Honest Abe의 두 번째 코드 부분을 사용했다 :

    나는 Honest Abe의 두 번째 코드 부분을 사용했다 :

    템플릿으로 사용하고 수정했습니다. 나는 엔트리 대신 콤보 박스가 필요 했으므로 구현했습니다. 다른 것을 필요로한다면 수정하는 것이 아주 쉽습니다.

    다음은 변경 사항입니다.

    제거됨

    다음을 mbox.py로 your_python_folder \ Lib \ site-packages 또는 메인 GUI 파일과 같은 폴더에 저장하십시오.

    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 = self.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
    
  4. from https://stackoverflow.com/questions/10057672/correct-way-to-implement-a-custom-popup-tkinter-dialog-box by cc-by-sa and MIT license