Forçando o redimensionamento de um widget Tkinter.ttk Treeview após reduzir as larguras das colunas

fundo

Python-2.7.14 x64, Tk-8.5.15 (incluído)

Está bem documentado que o widget Treeview no Tk tem muitos problemas, e o Tkinter, por ser um invólucro fino, não faz muito para lidar com eles. Um problema comum é obter um Treeview embrowse para funcionar corretamente com uma barra de rolagem horizontal. A configuração da largura da Treeview geral requer a configuração da largura de cada coluna. Porém, se houver muitas colunas, isso estenderá o contêiner pai horizontalmente e não ativará a barra de rolagem horizontal.

A solução que encontrei é, em tempo de design, para cada coluna, definirwidth para o tamanho desejado e armazene esse valor em cache em algum lugar seguro. Ao adicionar, editar ou excluir uma linha, você precisa percorrer as colunas e consultar o valor atual da largura e, se for maior que a largura em cache, definawidth de volta ao valor em cache original eAlém disso conjuntominwidth para a largura atual da coluna. A última coluna precisa de suastretch propriedade definida como "True" também, para que possa consumir qualquer espaço restante restante. Isso ativa a barra de rolagem horizontal e permite percorrer o conteúdo do Treeview de forma adequada, sem alterar a largura do widget geral.

Advertência: àsalgum ponto, Tk redefine internamentewidth igualarminwidth, mas faznão forçar um redesenho imediatamente. Você se surpreende mais tarde ao alterar o widget, como adicionar ou excluir uma linha, e é necessário repetir o exposto toda vez que o widget for alterado. Este não é um problema muito grande se você pegar todos os lugares onde um redesenho pode acontecer.


Problema

Alterar uma propriedade de um estilo Ttk aciona um redesenho forçado de todo o aplicativo; portanto, a ressalva que mencionei acima aparece, o widget Treeview se expande horizontalmente e a barra de rolagem horizontal é desativada.


Passo a passo

O código abaixo demonstra:

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


A janela resultante será mais ou menos assim:


Observe que a barra de rolagem horizontal está ativa e que há um pequeno estouro da última coluna além da borda do widget. As duas últimas chamadas paratv.column é o que habilita isso, mas ao despejar as propriedades da coluna da última coluna, vemos que Tk foi atualizado silenciosamentewidth eminwidth para ser o mesmo:

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


Mudandoqualquer propriedade dequalquer O estilo acionará um redesenho forçado do widget. Por exemplo, a próxima linha define oforeground cor para vermelho para uma classe de estilo derivada do botão de verificação:

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


Este é o widget Treeview agora:


O problema agora éencolhendo todo o widget de volta. A última coluna pode ser forçada a retornar ao tamanho original, definindowidth para o tamanho originalmente armazenado em cache,stretch para "False" eminwidth para o tamanho máximo da coluna:

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

Mas a largura do widget Treeview não diminuiu.


Encontrei duas soluções possíveis, ambas vinculadas ao<Configure> evento, usando a última coluna de exibição:

Indique uma mudança de temas:

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

Ocultar e mostrar novamente:

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


Ambos funcionam porque são as únicas maneiras de encontrar a função Tk internaTtkResizeWidget(). O primeiro funciona porque<<ThemeChanged>> força um recálculo completo da geometria do widget, enquanto o segundo funciona porque o Treeview chamaráTtkResizeWidget() se estiver em um estado não mapeado quando a coluna for reconfigurada.

A desvantagem de ambos os métodos é que às vezes você pode ver a janela se expandir para um único quadro e depois contrair de volta. É por isso que considero não ideal e espero que outra pessoa conheça uma abordagem melhor. Ou pelo menos um evento vinculável que acontece antes<Configure> ou antes que a expansão aconteça, da qual eu possa usar um dos métodos acima.


Algumas referências indicando que esse foi um problema no Tk por umlongo Tempo:

Os problemas do Treeview com uma barra de rolagem horizontal noTcl Wiki (procurar por[ofv] 2009-05-30)Bilhete# 3519160.mensagem na lista de discussão Tkinter.

E a questão éainda reproduzível no Python-3.6.4, que inclui o Tk-8.6.6.

questionAnswers(0)

yourAnswerToTheQuestion