Tkinter: ¿rejilla o paquete dentro de una rejilla?

Estoy creando una GUI para un software y quiero lograr esto:

###################################### # | some title # # menu upper |----------------------# # | # # | CANVAS # # menu lower | # # | # #------------------------------------# # statusbar # ###################################### 

El menú superior tiene una funcionalidad de alto nivel, el menú inferior está cambiando en función de la entrada del usuario. La barra de estado cambia su contenido a menudo.


Desafortunadamente, Tkinter se niega a trabajar.

Usando el administrador de diseño de cuadrícula no pude crear un diseño estable y agregar contenido como tags y botones al menú en el lado izquierdo:

 self.root = tk.Tk() self.root.resizable(width=0, height=0) self.root.title("some application") # menu left self.menu_left = tk.Frame(self.root, width=150, bg="#ababab") self.menu_left.grid(row=0, column=0, rowspan=2, sticky="ns") self.menu_left_upper = tk.Frame(self.menu_left, width=150, height=150, bg="red") self.menu_left_upper.grid(row=0, column=0) # this label breaks the design #self.test = tk.Label(self.menu_left_upper, text="test") #self.test.pack() self.menu_left_lower = tk.Frame(self.menu_left, width=150, bg="blue") self.menu_left_lower.grid(row=1, column=0) # right area self.some_title_frame = tk.Frame(self.root, bg="#dfdfdf") self.some_title_frame.grid(row=0, column=1, sticky="we") self.some_title = tk.Label(self.some_title_frame, text="some title", bg="#dfdfdf") self.some_title.pack() self.canvas_area = tk.Canvas(self.root, width=500, height=400, background="#ffffff") self.canvas_area.grid(row=1, column=1) self.root.mainloop() 

Este diseño funcionó sin contenidos en el menú del lado izquierdo . Cada vez que agregué algo en self.menu_left_upper o self.menu_left_lower , como la etiqueta de test , mi diseño se rompió: los marcos de menú desaparecieron. Además, incluso con las columnas , tuve que eliminar la barra de estado, porque cuando se agregó, los menús de la izquierda desaparecieron de nuevo.


Usando el gestor de diseño del paquete conseguí esto:

 ###################################### # | some title # # |----------------------# # menu upper | # # | CANVAS # # | # # menu lower | # # |----------------------# # | statusbar # ###################################### 

Como quería que el marco del menú de la izquierda consumiera todo el espacio y, lo hice crecer con el pack(side="left", expand=True, fill="both") , pero esta configuración siempre niega la barra de estado para El ancho completo.

Además, el código del gestor de paquetes puro parece “feo” . Creo que un diseño con un administrador de red es “más claro”. Por lo tanto, pensé que una cuadrícula o un diseño de paquete dentro de una cuadrícula sería bueno ?


¿Alguien puede ayudar? Estoy atrapado en el infierno de GUI: /

La clave para hacer el diseño es ser metódico y usar la herramienta adecuada para el trabajo. También significa que a veces necesitas ser creativo. Eso significa usar la grid al colocar las cosas en una grid , y usar el pack al colocar las cosas de arriba a abajo o de izquierda a derecha.

La otra clave es agrupar el código de diseño. Es mucho más fácil visualizar y modificar el diseño cuando está todo en un bloque de código.

En su caso, parece tener tres o cuatro áreas distintas, dependiendo de cómo cuente. Si desea utilizar la grid , será más fácil combinar “menú superior” y “menú inferior” en un marco, y tratar todo el marco como una celda de tabla. Parece que ya estás haciendo eso, lo cual es bueno.

Entonces, comencemos con esas áreas principales:

 self.menu_left.grid(row=0, column=0, rowspan=2, sticky="nsew") self.some_title_frame.grid(row=0, column=1, sticky="ew") self.canvas_area.grid(row=1, column=1, sticky="nsew") # you don't have a status bar in the example code, but if you # did, this is where you would put it # self.status_frame.grid(row=2, column=0, columnspan=2, sticky="ew") 

Cada vez que use la grid , debe darle a al menos una fila y una columna un peso positivo para que tkinter sepa dónde usar cualquier espacio no asignado. Por lo general, hay un widget que es el widget “principal”. En este caso es el canvas. Desea asegurarse de que la fila y la columna del canvas tengan un peso de 1 (uno):

 self.root.grid_rowconfigure(1, weight=1) self.root.grid_columnconfigure(1, weight=1) 

nota: usar el pack lugar de la grid le ahorraría dos líneas de código, ya que el pack no requiere que establezca los pesos de la forma en grid hace la grid .

A continuación, tenemos que resolver el problema de las áreas del menú. De forma predeterminada, los marcos se reducen para adaptarse a su contenido, por lo que agregar la etiqueta rompió su diseño. No le estaba diciendo a tkinter qué hacer con el espacio adicional, por lo que los marcos se encogieron y el espacio adicional no se utilizó.

Como desea que “menu_upper” y “menu_lower” compartan el 50% de esa área, pack es la solución más sencilla. Puede usar la grid , pero requiere más líneas de código para agregar los pesos de fila y columna.

 self.menu_left_upper.pack(side="top", fill="both", expand=True) self.menu_left_lower.pack(side="top", fill="both", expand=True) 

Aquí hay una versión que funciona, con barra de estado. Observe cómo se comporta exactamente como debería cuando cambia el tamaño de la ventana:

 import Tkinter as tk class Example(): def __init__(self): self.root = tk.Tk() self.root.title("some application") # menu left self.menu_left = tk.Frame(self.root, width=150, bg="#ababab") self.menu_left_upper = tk.Frame(self.menu_left, width=150, height=150, bg="red") self.menu_left_lower = tk.Frame(self.menu_left, width=150, bg="blue") self.test = tk.Label(self.menu_left_upper, text="test") self.test.pack() self.menu_left_upper.pack(side="top", fill="both", expand=True) self.menu_left_lower.pack(side="top", fill="both", expand=True) # right area self.some_title_frame = tk.Frame(self.root, bg="#dfdfdf") self.some_title = tk.Label(self.some_title_frame, text="some title", bg="#dfdfdf") self.some_title.pack() self.canvas_area = tk.Canvas(self.root, width=500, height=400, background="#ffffff") self.canvas_area.grid(row=1, column=1) # status bar self.status_frame = tk.Frame(self.root) self.status = tk.Label(self.status_frame, text="this is the status bar") self.status.pack(fill="both", expand=True) self.menu_left.grid(row=0, column=0, rowspan=2, sticky="nsew") self.some_title_frame.grid(row=0, column=1, sticky="ew") self.canvas_area.grid(row=1, column=1, sticky="nsew") self.status_frame.grid(row=2, column=0, columnspan=2, sticky="ew") self.root.grid_rowconfigure(1, weight=1) self.root.grid_columnconfigure(1, weight=1) self.root.mainloop() Example() 

En una nota no relacionada: le recomiendo encarecidamente que no elimine la capacidad para que el usuario cambie el tamaño de la ventana. Ellos saben mejor que tú cuáles son sus requisitos. Si usa la grid y el pack correctamente, la GUI cambiará de tamaño perfectamente.

Agregar el siguiente código justo antes de self.root.mainloop() logra lo que estás buscando

 self.some_status = tk.Label(self.root, text="status bar", bg="#dfdfdf") self.some_status.grid(row=3, column=0, columnspan=2, sticky="we") 

Al poner en la línea:

 menu_left_upper.grid_propagate(False) 

Entre su marco menu_left_upper.mainloop() y menu_left_upper.mainloop()

Esto funciona como:

De forma predeterminada, un widget contenedor se expande o se colapsa para que sea lo suficientemente grande como para contener su contenido. Por lo tanto, cuando se llama paquete, hace que el marco se contraiga. Esta característica se llama propagación de la geometría .

Para la gran mayoría de las aplicaciones, este es el comportamiento que desea. Para las raras ocasiones en las que desea establecer explícitamente el tamaño de un contenedor, puede desactivar esta función. Para desactivarlo, llame a pack_propagate o grid_propagate en el contenedor (dependiendo de si está utilizando la cuadrícula o el paquete en ese contenedor), dándole un valor de False .

Ver enlace a otra pregunta de donde vino esto.

Para obtener su barra de estado, simplemente implemente otro método de marco y cuadrícula:

 status_bar_frame = Frame(root, bg="#dfdfdf") status_bar_frame.grid(row=3, column=0, columnspan=2, sticky="we") status_bar = Label(status_bar_frame, text="status bar", bg="#dfdfdf") status_bar.pack() 

Entonces tu plan funciona. Espero eso ayude 🙂

PD. Además, ¿por qué todos los atributos del self ?

EDITAR: Para trabajar tienes que hacer:

 menu_left_upper = Frame(menu_left, width=225, height=225, bg="red") menu_left_upper.grid_propagate(False) menu_left_upper.grid(row=0, column=0) # this label breaks the design test = Label(menu_left_upper, text="test", bg='red') test.grid() 

Mi resultado