2012-04-07 1 views
15

Ho appena iniziato a imparare come creare una finestra di dialogo pop-up personalizzata; e come risulta, lo tkinter messagebox è davvero facile da usare, ma non fa troppo. Ecco il mio tentativo di creare una finestra di dialogo che richiederà input e quindi la memorizzeremo nel nome utente.Modo corretto per implementare una finestra di dialogo popup popup personalizzata

La mia domanda è qual è lo stile consigliato per implementarlo? Come suggerito da Bryan Oakley nel numero this comment.

Bryan Oakley ha scritto:

Vorrei consigliare di non usare una variabile globale. Invece di fare in modo che il dialogo si autodistrugga, fallo distruggere solo il widget attuale ma lascia l'oggetto vivo. Quindi, chiama qualcosa come inputDialog.get_string() e poi del inputDialog dalla tua logica principale.

Forse utilizzare la variabile globale per restituire la stringa non è l'idea migliore, ma perché? E qual è il modo suggerito? Mi sento confuso perché non so come attivare il metodo una volta che la finestra è stata distrutta, e ... la riga sulla distruzione del widget attuale, non sono sicuro che si riferisca a TopLevel.

Il motivo per cui lo chiedo è perché voglio che la finestra di pop-up venga distrutta dopo aver premuto il pulsante di invio; perché dopotutto, voglio che riprenda il programma principale, aggiorni qualcosa, ecc. Cosa dovrebbe fare il metodo dei pulsanti send in questo caso? Perché l'idea in questo particolare esempio è di permettere all'utente di farlo più e più volte, se lo desidera.

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

Grazie per il suggerimento e la mancia.

risposta

26

L'utilizzo dello global statement non è necessario nei due scenari che vengono in mente.

  1. si desidera codificare una finestra di dialogo che può essere importato da usare con una GUI principale
  2. si desidera codificare una finestra di dialogo che può essere importato da usare senza una GUI principale

codice di una finestra di dialogo che può essere importato da usare con una GUI principale

012.351.

È possibile evitare l'istruzione globale passando una chiave del dizionario & quando si crea un'istanza di una finestra di dialogo. Il tasto dizionario & può quindi essere associato al comando del pulsante, utilizzando lambda. Ciò crea una funzione anonima che eseguirà la chiamata di funzione (con argomenti) quando si preme il pulsante.

È possibile evitare la necessità di passare il padre ogni volta che si crea un'istanza della finestra di dialogo vincolando il genitore a un attributo di classe (root in questo esempio).

È possibile salvare quanto segue come mbox.py in your_python_folder\Lib\site-packages o nella stessa cartella del file della GUI principale.

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

Si può vedere esempi che sottoclasse TopLevel e tkSimpleDialog (tkinter.simpledialog in PY3) a effbot.

Vale la pena notare che ttk widgets sono intercambiabili con i widget tkinter in questo esempio.

Per centrare accuratamente la finestra di dialogo, leggere → this.

Esempio di utilizzo:

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

codice di una finestra di dialogo che può essere importato da usare senza una GUI principale


Creare un modulo contenente una classe di finestra di dialogo (MessageBox qui). Inoltre, includi una funzione che crea un'istanza di quella classe e infine restituisce il valore del pulsante premuto (o i dati da un widget di voce).

Ecco un modulo completo che è possibile personalizzare con l'aiuto di questi riferimenti: NMTech & Effbot.
Salvare il seguente codice come mbox.py in 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) 

e:

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 

Dopo Mbox crea un'istanza di MessageBox avvia il ciclo principale,
che si ferma in modo efficace la funzione di lì fino all'uscita dal mainloop tramite root.quit().
La funzione mbox può quindi accedere a msgbox.returning e restituirne il valore.

Esempio:

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

Questo è bello. Collega. – Marcin

+0

Come posso utilizzare il secondo codice in modo che l'interfaccia utente principale non sia selezionabile mentre viene chiamata la mbox? – DRTauli

+0

@DRTauli Personalmente avrei nascosto la finestra se non volevo che le persone interagissero con essa; perché renderlo insensibile potrebbe far pensare all'utente che il programma si bloccasse. Tuttavia, è possibile disabilitare temporaneamente la maggior parte dei widget. Consiglio di porre questa domanda come una nuova domanda generalizzata; i commenti sono per chiarimenti e suggerimenti. –

7

Poiché l'oggetto InputDialog non viene distrutto, ho potuto accedere all'attributo dell'oggetto. Ho aggiunto la stringa di restituzione come attributo:

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

Puoi spiegare come questo è diverso e migliora la risposta accettata? – skrrgwasme

+1

Mi piace il fatto che la risposta accettata abbia esempi per la creazione di una finestra di dialogo con o senza un ciclo principale di root. Mostra anche come passare argomento a un comando di pulsante. Ma preferisco il modo più semplice di salvare gli argomenti di ritorno come un attributo della classe (come menzionato nella seconda parte della risposta accettata). Questa risposta è stata più sulla combinazione delle parti che mi piaceva per renderla semplice e leggibile dall'utente. – ashwinjv

+0

Mi sembra buono. Grazie! – skrrgwasme