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

Antecedentes

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 una envoltura delgada, no hace mucho para lidiar con ellos. Un problema común es obtener un Treeview enbrowse modo para trabajar 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 tiempo de diseño, para cada columna, establecerwidth al tamaño deseado que desee y guarde en caché este valor en algún lugar seguro. Cuando agrega, edita o elimina una fila, debe recorrer las columnas y consultar su valor de ancho actual, y si es mayor que su ancho en caché, establezcawidth volver al valor original en caché yademás conjuntominwidth al ancho de columna actual. La última columna necesita sustretch propiedad establecida en "Verdadero" también, por lo que puede consumir cualquier espacio restante. Esto activa la barra de desplazamiento horizontal y le permite desplazar el contenido de Treeview de manera adecuada, sin cambiar el ancho del widget general.

Advertencia: enalgún punto, Tk se restablece internamentewidth A igualminwidth, pero lo haceno forzar un redibujo de inmediato. Te sorprenderás más tarde cuando cambies el widget, como al agregar o eliminar una fila, por lo que debes repetir lo anterior cada vez que se cambia el widget. Este no es un problema realmente grande si detecta todos los lugares donde puede ocurrir un redibujo.


Problema

Cambiar una propiedad de un estilo Ttk desencadena un redibujo forzado de toda la aplicación, por lo que aparece la advertencia que mencioné anteriormente, el widget Treeview se expande horizontalmente y la barra de desplazamiento horizontal se desactiva.


Tutorial

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í:


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 dos últimas llamadas atv.column son los que permiten esto, pero volcando las propiedades de la columna de la última columna, vemos que Tk se ha actualizado silenciosamentewidth yminwidth para ser el mismo:

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


Cambiandoalguna propiedad dealguna style activará un redibujo forzado del widget. Por ejemplo, la siguiente línea establece elforeground color a rojo para una clase de estilo Checkbutton derivada:

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


Este es el widget Treeview ahora:


El problema ahora escontracción todo el widget de vuelta. La última columna puede volver a su tamaño original configurandowidth al tamaño originalmente almacenado en caché,stretch a "Falso", yminwidth hasta el tamaño máximo de columna:

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

Pero el ancho del widget Treeview no se redujo.


He encontrado dos posibles soluciones, ambas vinculadas a<Configure> evento, utilizando la última columna de visualización:

Indicar un cambio de temas:

tv.column("value", width=232, stretch=True, minwidth=372)
tv.event_generate("<<ThemeChanged>>")

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 internaTtkResizeWidget(). El primero funciona porque<<ThemeChanged>> fuerza un recálculo completo de la geometría del widget, mientras que el segundo funciona porque Treeview llamaráTtkResizeWidget() si está en un estado no asignado cuando la columna se reconfigura.

La desventaja de ambos métodos es que a veces puede ver la ventana expandirse para un solo cuadro y luego contraerse nuevamente. Es por eso que considero que no son óptimos y espero que alguien más conozca un mejor enfoque. O al menos de un evento enlazable que ocurre antes<Configure> o antes de que ocurra la expansión, desde la cual puedo usar uno de los métodos anteriores con.


Algunas referencias indican que esto ha sido un problema en Tk para unlargo hora:

Problemas de Treeview con una barra de desplazamiento horizontal en elTcl Wiki (buscar[ofv] 2009-05-30)Boleto# 3519160.Mensaje en Tkinter-discutir lista de correo.

Y, el problema estodavía reproducible en Python-3.6.4, que incluye Tk-8.6.6.

Respuestas a la pregunta(0)

Su respuesta a la pregunta