2012-04-07 14 views
15

tkinter Acabo de empezar a aprender cómo crear un cuadro de diálogo pop-up costumbre; y como resulta, el tkinter messagebox es realmente fácil de usar, pero tampoco hace demasiado. Aquí está mi intento de crear un cuadro de diálogo que tomará entrada y luego almacenar eso en el nombre de usuario.forma correcta para implementar un cuadro de diálogo emergente personalizado

Mi pregunta es ¿cuál es el estilo recomendado para implementar esto? Como Bryan Oakley sugirió en this comment.

Bryan Oakley escribió:

Yo aconsejaría contra el uso de una variable global. En lugar de que el cuadro de diálogo se destruya a sí mismo, haga que destruya solo el widget real pero deje el objeto vivo. Luego, llame a algo como inputDialog.get_string() y luego del inputDialog desde su lógica principal.

Quizás usar la variable global para devolver mi cadena no sea la mejor idea, pero ¿por qué? ¿Y cuál es el camino sugerido? Me confundo porque no sé cómo activar la getString una vez que la ventana se destruye, y ... la línea de destruir el widget real, no estoy seguro de si se está refiriendo a TopLevel.

La razón que pido es porque quiero que el cuadro emergente que será destruido después de presionar el botón de envío; porque después de todo, lo quiero para reanudar de nuevo al programa principal, actualizar algo, etc. Cuál debería ser el método del botón send hacer en este caso? Porque la idea en este ejemplo particular es permitirle al usuario hacerlo una y otra vez, si lo desea.

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

Gracias por su sugerencia y consejo.

Respuesta

26

Utilizando el global statement es innecesario en los dos escenarios que vienen a la mente.

  1. que desea codificar un cuadro de diálogo que puede ser importado a utilizar con una interfaz de usuario principal
  2. que desea codificar un cuadro de diálogo que puede ser importado a utilizar sin una interfaz de usuario principal

código de un cuadro de diálogo que puede ser importado a utilizar con una interfaz de usuario principal


Evitar la declaración global se puede lograr pasando una clave de diccionario & al crear una instancia de un cuadro de diálogo. La clave de diccionario & continuación, se puede asociar a la orden del botón, mediante el uso de lambda. Eso crea una función anónima que ejecutará su llamada de función (con args) cuando se presiona el botón.

Usted puede evitar la necesidad de aprobar el padre cada vez que se crea una instancia del cuadro de diálogo mediante la unión del padre a un atributo de clase (raíz en este ejemplo).

Puede guardar lo siguiente como mbox.py en your_python_folder\Lib\site-packages o en la misma carpeta que el archivo de su GUI principal.

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

Se puede ver ejemplos que subclase TopLevel y tkSimpleDialog (tkinter.simpledialog en el AP3) en effbot.

Vale la pena señalar que ttk widgets son intercambiables con los widgets tkinter en este ejemplo.

Para centrar con precisión el cuadro de diálogo, lea → this.

Ejemplo de uso:

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

código un cuadro de diálogo que puede ser importado a utilizar sin una GUI principal


Crear un módulo que contiene una clase de cuadro de diálogo (MessageBox aquí). Además, incluya una función que crea una instancia de esa clase, y finalmente devuelve el valor del botón presionado (o datos de un widget de Entrada).

Aquí hay un módulo completo que puede personalizar con la ayuda de estas referencias: NMTech & Effbot.
guarde el código siguiente como mbox.py en 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) 

y:

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 

Después mbox crea una instancia de de mensaje de que comience la mainloop,
que se detiene con eficacia la función que hay hasta que se salga del enlace principal a través del root.quit().
La función mbox puede acceder a msgbox.returning y devolver su valor.

Ejemplo:

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

Eso es bueno. Thx mate – Marcin

+0

¿Cómo puedo usar el segundo código para que no se pueda hacer clic en la interfaz de usuario principal mientras se llama al mbox? – DRTauli

+0

@DRTauli Personalmente ocultaría la ventana si no quisiera que las personas interactúen con ella; porque hacerlo insensible puede hacer que el usuario piense que el programa se congeló. Sin embargo, puede desactivar la mayoría de los widgets temporalmente. Recomiendo hacer esto como una nueva pregunta generalizada; los comentarios son para aclaraciones y sugerencias. –

7

Puesto que el objeto InputDialog no se destruye, yo era capaz de acceder al atributo de objeto. Agregué la cadena de retorno como un atributo:

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

¿Puede explicar de qué manera es diferente y mejora la respuesta aceptada? – skrrgwasme

+1

Me gusta el hecho de que la respuesta aceptada tenga ejemplos para crear un cuadro de diálogo con o sin un bucle principal raíz. También le muestra cómo pasar el argumento a un comando de botón. Pero prefiero la manera más simple de guardar los argumentos de devolución como un atributo de la clase (como se menciona en la segunda parte de la respuesta aceptada). Esta respuesta fue más sobre combinar las partes que me gustaban para que sea simple y legible para el usuario. – ashwinjv

+0

Me parece bien. ¡Gracias! – skrrgwasme

Cuestiones relacionadas