Obligando a un widget Trekiew Tkinter.ttk a cambiar de tamaño después de reducir el ancho de sus columnas

Fondo

Python-2.7.14 x64, Tk-8.5.15 (incluido)

Está bien documentado que el widget Treeview en Tk tiene muchos problemas, y Tkinter, al ser un contenedor delgado, no hace mucho para lidiar con ellos. Un problema común es lograr que un Treeview en modo de browse funcione correctamente con una barra de desplazamiento horizontal. Establecer el ancho de la vista de árbol general requiere establecer el ancho de cada columna. Pero si uno tiene muchas columnas, esto estira el contenedor principal horizontalmente y no activa la barra de desplazamiento horizontal.

La solución que he encontrado es, en el momento del diseño, para cada columna, establecer el width al tamaño deseado que desea y almacenar este valor en un lugar seguro. Cuando agrega, edita o elimina una fila, debe recorrer las columnas y consultar su valor de ancho actual, y si es más grande que su ancho en caché, vuelva a establecer el width al valor en caché original y también establezca minwidth al ancho de columna actual . La última columna también necesita que su propiedad de stretch esté configurada en “Verdadero”, por lo que puede consumir el espacio restante. Esto activa la barra de desplazamiento horizontal y le permite panoramizar adecuadamente el contenido de la vista de árbol, sin cambiar el ancho del widget general.

Advertencia: en algún momento , Tk restablece el width internamente al minwidth igual, pero no fuerza un redibujado de inmediato. Más adelante se sorprenderá cuando cambie el widget, como al agregar o eliminar una fila, por lo que tendrá que repetir lo anterior cada vez que se cambie el widget. Este no es un problema realmente grande si atrapas todos los lugares donde puede ocurrir un redibujo.

Problema

Cambiar una propiedad de un estilo Ttk desencadena un rediseño forzado de toda la aplicación, por lo que aparece la advertencia que mencioné anteriormente, el widget de Vista de árbol se expande en forma horizontal y la barra de desplazamiento horizontal se desactiva.

Recorrido

El siguiente código demuestra:

 # imports from Tkinter import * from ttk import * from tkFont import * # root root=Tk() # font config ff10=Font(family="Consolas", size=10) ff10b=Font(family="Consolas", size=10, weight=BOLD) # style config s=Style() s.configure("Foo2.Treeview", font=ff10, padding=1) s.configure("Foo2.Treeview.Heading", font=ff10b, padding=1) # init a treeview tv=Treeview(root, selectmode=BROWSE, height=8, show="tree headings", columns=("key", "value"), style="Foo2.Treeview") tv.heading("key", text="Key", anchor=W) tv.heading("value", text="Value", anchor=W) tv.column("#0", width=0, stretch=False) tv.column("key", width=78, stretch=False) tv.column("value", width=232, stretch=False) tv.grid(padx=8, pady=(8,0)) # init a scrollbar sb=Scrollbar(root, orient=HORIZONTAL) sb.grid(row=1, sticky=EW, padx=8, pady=(0,8)) tv.configure(xscrollcommand=sb.set) sb.configure(command=tv.xview) # insert a row that has data longer than the initial column width and # then update width/minwidth to activate the scrollbar. tv.insert("", END, values=("foobar", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")) tv.column("key", width=78, stretch=False, minwidth=78) tv.column("value", width=232, stretch=True, minwidth=372) 

La ventana resultante se verá así:

Ejemplo de widget de vista en árbol con barra de desplazamiento horizontal

Tenga en cuenta que la barra de desplazamiento horizontal está activa y que hay un ligero desbordamiento de la última columna más allá del borde del widget. Las últimas dos llamadas a tv.column son las que lo permiten, pero al tv.column las propiedades de la columna de la última columna, vemos que Tk ha actualizado el width y el width silenciosa:

 tv.column("value") {'minwidth': 372, 'width': 372, 'id': 'value', 'anchor': u'w', 'stretch': 1} 

Cambiar cualquier propiedad de cualquier estilo activará un redibujado forzado del widget. Por ejemplo, la siguiente línea establece el color de foreground en rojo para una clase de estilo Derivada:

 s.configure("Foo2.TCheckbutton", foreground="red") 

Este es el widget Treeview ahora:

Aparato Treeview después de cambiar una propiedad ttk.Style

El problema ahora es reducir todo el widget. La última columna se puede forzar a volver a su tamaño original estableciendo el width en el tamaño originalmente almacenado en caché, stretch a “Falso”, y el minwidth al tamaño máximo de columna:

 tv.column("value", width=232, stretch=False, minwidth=372) 

Aparato Treeview después de forzar el tamaño de columna a original

Pero el ancho del widget Treeview no retrocedió.

He encontrado dos soluciones posibles, ambas vinculadas al evento , utilizando la última columna de visualización:

  1. Indica un cambio de temas:

     tv.column("value", width=232, stretch=True, minwidth=372) tv.event_generate("<>") 
  2. Ocultar y volver a mostrar:

     tv.grid_remove() tv.column("value", width=232, stretch=True, minwidth=372) tv.grid() 

Ambos funcionan porque son las únicas formas que puedo encontrar para invocar la función Tk interna TtkResizeWidget() . El primero funciona porque <> fuerza un recálculo completo de la geometría del widget, mientras que el segundo funciona porque Treeview llamará a TtkResizeWidget() si está en un estado no asignado cuando se reconfigura la columna.

La desventaja de ambos métodos es que a veces puede ver la ventana expandirse para un solo marco y luego contraerse. Esta es la razón por la que considero que no son óptimas y espero que alguien más conozca un mejor enfoque. O al menos de un evento vinculable que ocurre antes de o antes de que ocurra la expansión, desde el cual puedo usar uno de los métodos anteriores.

Algunas referencias que indican que esto ha sido un problema en Tk durante mucho tiempo:

  1. Los problemas de Treeview con una barra de desplazamiento horizontal en el Tcl Wiki (búsqueda de [ofv] 2009-05-30 ).
  2. Ticket # 3519160 .
  3. Mensaje en la lista de correo de Tkinter-Discuss.

Y, el problema aún es reproducible en Python-3.6.4, que incluye Tk-8.6.6.