2012-04-07 12 views
15

Tôi vừa mới bắt đầu tìm hiểu cách tạo hộp thoại bật lên tùy chỉnh; và khi nó quay ra, tkinter messagebox thực sự dễ sử dụng, nhưng nó cũng không làm quá nhiều. Đây là nỗ lực của tôi để tạo ra một hộp thoại sẽ đưa đầu vào và sau đó lưu trữ trong tên người dùng.Cách đúng để triển khai hộp thoại tkinter bật lên tùy chỉnh

Câu hỏi của tôi là phong cách được đề xuất để triển khai điều này là gì? Như Bryan Oakley đã đề xuất trong this comment.

Bryan Oakley đã viết:

tôi sẽ tham mưu chống sử dụng một biến toàn cầu. Thay vì để hộp thoại tự hủy, nó chỉ phá hủy tiện ích thực tế nhưng để đối tượng còn sống. Sau đó, gọi một cái gì đó như inputDialog.get_string() và sau đó del inputDialog từ logic chính của bạn.

Có thể sử dụng biến toàn cục để trả về chuỗi của tôi không phải là ý tưởng hay nhất, nhưng tại sao? Và cách đề xuất là gì? Tôi bị lẫn lộn vì tôi không biết làm thế nào để kích hoạt các dây rút một khi cửa sổ bị phá hủy, và ... dòng về phá hủy các widget thực tế, tôi không chắc chắn nếu anh ta đang đề cập đến TopLevel.

Lý do tôi hỏi là vì tôi muốn hộp bật lên bị hủy sau khi tôi nhấn nút gửi; bởi vì sau khi tất cả, tôi muốn nó tiếp tục trở lại chương trình chính, cập nhật một cái gì đó, vv Những gì nên các phương pháp nút send làm trong trường hợp này? Bởi vì ý tưởng trong ví dụ cụ thể này là để cho phép người dùng làm điều đó hơn và hơn, nếu anh ta mong muốn.

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

Cảm ơn đề xuất và mẹo của bạn.

Trả lời

26

Sử dụng global statement là không cần thiết trong hai trường hợp cần lưu ý.

  1. bạn muốn mã hóa một hộp thoại có thể được nhập khẩu để sử dụng với một giao diện chính
  2. bạn muốn mã hóa một hộp thoại có thể được nhập khẩu để sử dụng mà không một giao diện chính

mã một hộp thoại có thể được nhập khẩu để sử dụng với một giao diện chính

012.351.

Tránh tuyên bố chung có thể được thực hiện bằng cách chuyển từ điển & khi bạn tạo một thể hiện của hộp thoại. Từ điển & sau đó có thể được liên kết với lệnh của nút, bằng cách sử dụng lambda. Điều đó tạo ra một hàm ẩn danh sẽ thực hiện cuộc gọi hàm của bạn (với args) khi nhấn nút.

Bạn có thể tránh sự cần thiết phải vượt qua phụ huynh mỗi khi bạn tạo một thể hiện của hộp thoại bằng cách ràng buộc cha mẹ với một thuộc tính lớp (gốc trong ví dụ này).

Bạn có thể lưu các mục sau dưới dạng mbox.py trong your_python_folder\Lib\site-packages hoặc trong cùng thư mục với tệp GUI chính của bạn.

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

Bạn có thể xem các ví dụ mà phân lớp mục cấp đầu và tkSimpleDialog (tkinter.simpledialog trong py3) tại effbot.

Cần lưu ý rằng ttk widgets có thể hoán đổi cho nhau với các tiện ích tkinter trong ví dụ này.

Để căn giữa chính xác hộp thoại đọc → this.

Ví dụ về sử dụng:

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

mã một hộp thoại có thể được nhập khẩu để sử dụng mà không một giao diện chính


Tạo một module có chứa một lớp hộp thoại (MessageBox ở đây). Ngoài ra, bao gồm một hàm tạo ra một thể hiện của lớp đó, và cuối cùng trả về giá trị của nút được nhấn (hoặc dữ liệu từ một widget Entry).

Dưới đây là mô-đun hoàn chỉnh mà bạn có thể tùy chỉnh với sự trợ giúp của các tham chiếu sau: NMTech & Effbot.
Lưu đoạn mã sau như mbox.py trong 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) 

và:

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 

Sau mbox tạo ra một thể hiện của MessageBox nó bắt đầu mainloop,
mà hiệu quả dừng chức năng có cho đến khi trục chính được thoát qua root.quit().
Chức năng mbox sau đó có thể truy cập msgbox.returning và trả lại giá trị của nó.

Ví dụ:

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) 
+0

Thật tuyệt. Thx mate. – Marcin

+0

Làm cách nào để sử dụng mã thứ hai để giao diện người dùng chính không thể nhấp được trong khi mbox được gọi? – DRTauli

+0

@DRTauli Cá nhân tôi sẽ ẩn cửa sổ nếu tôi không muốn mọi người tương tác với nó; bởi vì làm cho nó không phản hồi có thể khiến người dùng nghĩ chương trình bị đóng băng. Tuy nhiên, bạn có thể tạm thời tắt hầu hết các tiện ích. Tôi khuyên bạn nên hỏi điều này như một câu hỏi mới tổng quát; ý kiến ​​là để làm rõ và gợi ý. –

7

Kể từ khi đối tượng inputDialog không bị phá hủy, tôi đã có thể truy cập vào các thuộc tính đối tượng. Tôi đã thêm chuỗi trả lại làm thuộc tính:

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

Bạn có thể giải thích sự khác biệt này và cải thiện khi câu trả lời được chấp nhận không? – skrrgwasme

+1

Tôi thích thực tế là câu trả lời được chấp nhận có các ví dụ để tạo một hộp thoại có hoặc không có vòng lặp chính gốc. Nó cũng cho bạn thấy làm thế nào để vượt qua đối số cho một lệnh nút. Nhưng tôi thích cách đơn giản hơn để lưu đối số trả về dưới dạng thuộc tính của lớp (như đã đề cập trong phần thứ hai của câu trả lời được chấp nhận). Câu trả lời này là nhiều hơn về việc kết hợp các phần mà tôi thích để làm cho nó đơn giản và dễ đọc. – ashwinjv

+0

Nghe hay với tôi. Cảm ơn! – skrrgwasme