Treeview will nicht

Fragen zu Tkinter.
Antworten
we1303
User
Beiträge: 14
Registriert: Montag 22. März 2021, 21:51

Hallo zusammen,

ich habe ein Problem mit Treeview und keine Lösung gefunden, auch nicht im Netz. Benutze Treeview in Verbindung mit Scrollbar und .grid, bei Text ebenfalls mit Scrollbar und .grid ist alles in Ordnung: Unabhängig vom Inhalt und der wrap-Einstellung hab ich ein Textfenster gleichbleibender Größe in einem Rahmen in einem Fenster. Bei Treeview sieht das anders aus: Bei breiten Tabellen läuft Treeview über die rechten Ränder von Fenster und Bildschirm hinaus, d.h. rechte Spalten sind nicht erreichbar, die Scrollbars ebenso bzw. deaktiviert, weil (aus Sicht des Treeview) die Taballe in voller Breite angezeigt wird. Der intgeressante Teil des Codes beginnt mit root = tk.Tk().

Der Code:
Bild

Code: Alles auswählen

test = True                         # True|False = Zugriff auf SQLite
file = 'Ersatzteile.db'             #\
table = 'etl_utf'                   #/
mode = 'grid'                       # 'grid'|'packh'|'packv'
char_width, base_width = 6.3, 8     # Spaltenbreite

import tkinter as tk
from tkinter import ttk

if test:
    data = []
    for i in range(5):
        data.append(['', 'MAE214613', '', '', 'HW', 'Motorschild 60903', ''])
        data.append(['xxxx', 'LGE8.845050.6', '', '', '2m', 'Steckdose 2-polig, Verbindungskabel', 'z.B. Stainz-Tender'])
        data.append(['xxxx', 'LGE8.839040.7', '', '', '2m', 'Steckdose 2-polig', 'z.B. Stainz'])
        data.append(['xxxx', 'LGE1.22500.170', '', '', '2m', 'Steckdose 2-polig, Halter für   122500.170.2', 'z.B. Stainz'])
        columns = ('ort', 'nr', 'stk', 'pg', 'spur', 'bez', 'komm')
else:
    import sqlite3
    conn = sqlite3.connect(file)
    sql_cmd = "PRAGMA TABLE_INFO(" + table + ")"
    data = conn.execute(sql_cmd)  # ,oid = recno()
    columns = [ elem[1] for elem in list(data) ]
    sql_cmd = "SELECT * FROM " + table
    data = conn.execute(sql_cmd)
col_types = (4, 17, 3, 2, 4, 46, 40)

root = tk.Tk()
root.geometry('650x350')
root.title(file)
root.config(background='magenta')
frame0 = tk.Frame(root, background='yellow')
frame1 = tk.Frame(frame0, background='red')

tree = ttk.Treeview(frame1)
tree['show'] = 'headings'       # suppress white spaces left of columns
tree['selectmode'] = 'browse'   # select one line at a time only
tree['columns'] = columns
for i, col in enumerate(columns):
    width = int(col_types[i] * char_width + base_width)
    tree.column(col, width=width, anchor='w')
    tree.heading(col, text=col, anchor='w')
for i, row in enumerate(data):
    tree.insert('', i, text='', values=(row))

if mode == 'packh':
    xbar = ttk.Scrollbar(frame0, orient='horizontal')
    ybar = ttk.Scrollbar(frame1, orient='vertical')
elif mode == 'packv':
    xbar = ttk.Scrollbar(frame1, orient='horizontal')
    ybar = ttk.Scrollbar(frame0, orient='vertical')
elif mode == 'grid':
    xbar = ttk.Scrollbar(frame1, orient='horizontal')
    ybar = ttk.Scrollbar(frame1)
ybar.configure(command=tree.yview)
tree.configure(yscrollcommand=ybar.set)
xbar.configure(command=tree.xview)
tree.configure(xscrollcommand=xbar.set)

if mode == 'packh':
    tree.pack(side='left')
    ybar.pack(side='right', fill='y')
    frame1.pack(padx=10, pady=10)
    xbar.pack(fill='x')
    frame0.pack(padx=20, pady=20)
elif mode == 'packv':
    tree.pack()
    xbar.pack(fill='x')
    frame1.pack(side='left', padx=10, pady=10)
    ybar.pack(side='right', fill='y')
    frame0.pack(padx=20, pady=20)
elif mode == 'grid':
    tree.grid(row=2, column=1)
    xbar.grid(row=3, column=1, sticky='we')
    ybar.grid(row=2, column=2, sticky='ns')
    frame0.grid(row=0, column=0, padx=10, pady=10)
    frame1.grid()       # padx=10, pady=10

root.mainloop()
Das Ganze sollte lauffähig sein. Mache ich einen Fehler oder Python? Hat jemand eine Idee, wie man das zum Laufen bringt? Danke für gute Ideen und Vorschläge
Benutzeravatar
peterpy
User
Beiträge: 188
Registriert: Donnerstag 7. März 2013, 11:35

Hallo we1303,
gib den Spalten eine minimale Breite, z:B:

Code: Alles auswählen

minwidth=150
Dein gestutzter Code:

Code: Alles auswählen

test = True                         # True|False = Zugriff auf SQLite
file = 'Ersatzteile.db'             #\
table = 'etl_utf'                   #/
mode = 'grid'                       # 'grid'|'packh'|'packv'
char_width, base_width = 6.3, 8     # Spaltenbreite

import tkinter as tk
from tkinter import ttk

if test:
    data = []
    for i in range(5):
        data.append(['', 'MAE214613', '', '', 'HW', 'Motorschild 60903', ''])
        data.append(['xxxx', 'LGE8.845050.6', '', '', '2m', 'Steckdose 2-polig, Verbindungskabel', 'z.B. Stainz-Tender'])
        data.append(['xxxx', 'LGE8.839040.7', '', '', '2m', 'Steckdose 2-polig', 'z.B. Stainz'])
        data.append(['xxxx', 'LGE1.22500.170', '', '', '2m', 'Steckdose 2-polig, Halter für   122500.170.2', 'z.B. Stainz'])
        columns = ('ort', 'nr', 'stk', 'pg', 'spur', 'bez', 'komm')
else:
    import sqlite3
    conn = sqlite3.connect(file)
    sql_cmd = "PRAGMA TABLE_INFO(" + table + ")"
    data = conn.execute(sql_cmd)  # ,oid = recno()
    columns = [ elem[1] for elem in list(data) ]
    sql_cmd = "SELECT * FROM " + table
    data = conn.execute(sql_cmd)
col_types = (4, 17, 3, 2, 4, 46, 40)

root = tk.Tk()
#root.geometry('650x350')
root.title(file)
root.config(background='cyan')
frame0 = tk.Frame(root, background='yellow')
frame1 = tk.Frame(frame0, background='red', width=600, height=6)

frame0.pack()
frame1.pack(side='left', expand='yes')

tree = ttk.Treeview(frame1)
tree['show'] = 'headings'       # suppress white spaces left of columns
tree['selectmode'] = 'browse'   # select one line at a time only
tree['columns'] = columns
for i, col in enumerate(columns):
    width = int(col_types[i] * char_width + base_width)
    tree.column(col, width=width, anchor='w', minwidth=150)
    tree.heading(col, text=col, anchor='w')
for i, row in enumerate(data):
    tree.insert('', i, text='', values=(row))

xbar = ttk.Scrollbar(frame1, orient='horizontal', command=tree.xview)
ybar = ttk.Scrollbar(frame1, command=tree.yview)
tree.configure(yscrollcommand=ybar.set)
tree.configure(xscrollcommand=xbar.set)

tree.grid(row=0, column=1)
xbar.grid(row=1, column=1, sticky='we')
ybar.grid(row=0, column=2, sticky='ns')
frame1.config(width=100)

root.mainloop()
Gruss Peter
we1303
User
Beiträge: 14
Registriert: Montag 22. März 2021, 21:51

Hallo Peter,
Hallo Community,

vielen Dank für deinen Vorschlag. Obwohl das logisch keinen Sinn macht, hatte ich minwidth bereits ausprobiert. Das Probleme wird dadurch nur verschlimmert. Ich hatte deshalb gehofft, dass Du (oder andere Leser) bei sich den Code laufen lassen würden, um die merkwürdige Optik zu erleben.

Ich schrieb unbestimmte Spaltenzahl und -breite, d.h. z.B. eine Tabelle mit 50 Spalten mit Breiten bis zu 500 Zeichen (beides sind willkürliche Annahmen, um Problem und Größenordnung zu erklären). Auch ohne minwidth lassen sich die linken Spalten nicht so schmal ziehen, dass man die rechten sehen könnte, bzw. die unsichtbaren Spalten können so nicht in den sichtbaren Bereich gezogen werden. Außerdem ist der Rollbalken rechts nicht erreichbar, was das Navigieren auch vertikal stark erschwert.

Ich könnte die insert-Methode um einen Zähler für die Spaltenbreite ergänzen und abbrechen, bevor die Bildschirmbreite erreicht ist, so dass zumindest in einem maximierten Fenster wenigstens ein Teil der Tabelle normal angezeigt würde. Ich könnte eine sehr breite Spalten nicht (oder nur teilweise) in Treeviev anzeigen und dieses Feld in einem eigenen Text-Fenster anzeigen. Ich könnte mir zahllose weitere Umgehungsstrategien ausdenken und voraussichtlich auch umsetzen. Das sind aber alles Krücken und das Problem ist damit nicht gelöst.

Das Text-Widget kann ebenfalls (wrap=None) sehr breite Texte anzeigen. Trotzdem bleibt die vorgesehene Breite des Text-Fenster erhalten, bei Bedarf wird der horizontale Rollbalken aktiviert und man erhält das Verhalten, wie man es von kommerziellen Programme kennt. Dagegen machen meine Versuche mit Treeview Probleme:
  • Obwohl die Tabelle breiter als das Treeview-Fenster ist, wird der horizontale Rollbalken nicht aktiviert.
  • Wenn die Tabelle ausreichend breit ist, wird das Treeview-Fenter rechts unendlich weit erweitert und überschreitet dabei den rechten Rand das Anwendungsfensters, den rechten Rand des Bildschirms und auch die Gesamtbreite des Bildschirms.
  • Wenn die Tabelle breiter als Anwendungsfenster bzw. Bildschirm ist, verschwindet der (theoretisch vorhandene) vertikale Rollbalken in den nicht mehr sichtbaren Bereich, kann also ebenfalls nicht genutzt werden.
Diese Macken lassen sich sehr gut an meinem lauffähigen Testprogramm beobachten, bei der eingestellten Fenstergröße sieht man die Macken. Maximiert sieht man, dass alle Elemente funktionsfähig vorhanden sind. Es wäre leicht zu sagen, dass Python/tkinter fehlerhaft sind, es könnte aber auch ein dummer Fehler von mir sein, oder ich habe einen Parameter (z.B. tree-width) nicht gefunden oder er ist nicht dokumentiert. Allerdings fand ich in den TkDocs "The overall requested width for the widget is based on the sum of the column widths.", was mich ahnen läßt, dass damit ein Bug schöngeredet werden soll.

Wolfgang
we1303
User
Beiträge: 14
Registriert: Montag 22. März 2021, 21:51

PS: Ein Hardcopy mit Treeview.grid findet sich hier: https://imgur.com/a/K6l6Jn2

Sorry, habe die Änderung auf pack nicht gesehen. Sieht zunächst gut aus, benimmt sich aber merkwürdig: Hab auf die Schnelle 2 Zeilen eingefügt eingefügt ( disp = columns * 3 und tree['displaycolumns'] = disp), kann dann einige Spalten schmaler ziehen, andere nicht.

Erinnere außerdem, dass ein grid in der Hierachie über pack merkwürdige Dinge machte, hatte mich deshalb nur widerwillig mit dem verkorksten alten pack beschäftigt. Mag ja sein, das taugt was (wenn es auch extrem umständlich ist). Ich muss mal probieren, ob das jetzt funzt und ich ohne grid klar komme. Danke erstmal.
we1303
User
Beiträge: 14
Registriert: Montag 22. März 2021, 21:51

Hallo Peter,

hab mir das Konstrukt noch einmal angesehen, meines Erachtens ist das so etwas wie ein Zufallstreffer. Jede Breitenänderung der Tabelle verändert die Breite des Treeview-Fensters, dies kann innerhalb einer Anwendung aber grundsätzlich nicht gut sein, denn es wird einem dadurch die gesamten Maskengestaltung zerschossen. Deshalb hatte ich die Breite des root-Fensters und die Rahmenbreiten festgelegt. Habe den Code geändert, dass die Tabelle (z.B. mit test=2) einfach verbreitert werden kann.

Code: Alles auswählen

'''
geändert, we: test von bool auf int, data, columns, col_types um Faktor test 
            ergänzt. Tabelle wird dadurch um den Faktor breiter.
'''
test = 2                            # >1|0 = Zugriff auf SQLite
file = 'Ersatzteile.db'             #\
table = 'etl_utf'                   #/
mode = 'grid'                       # 'grid'|'packh'|'packv'
char_width, base_width = 6.3, 8     # Spaltenbreite

import tkinter as tk
from tkinter import ttk

if test:
    data = []
    for i in range(5):
        data.append(['', 'MAE214613', '', '', 'HW', 'Motorschild 60903', ''] * test)
        data.append(['xxxx', 'LGE8.845050.6', '', '', '2m', 'Steckdose 2-polig, Verbindungskabel', 'z.B. Stainz-Tender'] * test)
        data.append(['xxxx', 'LGE8.839040.7', '', '', '2m', 'Steckdose 2-polig', 'z.B. Stainz'] * test)
        data.append(['xxxx', 'LGE1.22500.170', '', '', '2m', 'Steckdose 2-polig, Halter für   122500.170.2', 'z.B. Stainz'] * test)
        columns = ['ort', 'nr', 'stk', 'pg', 'spur', 'bez', 'komm'] * test
else:
    import sqlite3
    conn = sqlite3.connect(file)
    sql_cmd = "PRAGMA TABLE_INFO(" + table + ")"
    data = conn.execute(sql_cmd)  # ,oid = recno()
    columns = [ elem[1] for elem in list(data) ]
    sql_cmd = "SELECT * FROM " + table
    data = conn.execute(sql_cmd)
col_types = [4, 17, 3, 2, 4, 46, 40] * max(1, test)

root = tk.Tk()
#root.geometry('650x350')
root.title(file)
root.config(background='cyan')
frame0 = tk.Frame(root, background='yellow')
frame1 = tk.Frame(frame0, background='red', width=600, height=6)

frame0.pack()
frame1.pack(side='left', expand='yes')

tree = ttk.Treeview(frame1)
tree['show'] = 'headings'       # suppress white spaces left of columns
tree['selectmode'] = 'browse'   # select one line at a time only
tree['columns'] = columns
for i, col in enumerate(columns):
    width = int(col_types[i] * char_width + base_width)
    tree.column(col, width=width, anchor='w', minwidth=150)
    tree.heading(col, text=col, anchor='w')
for i, row in enumerate(data):
    tree.insert('', i, text='', values=(row))

xbar = ttk.Scrollbar(frame1, orient='horizontal', command=tree.xview)
ybar = ttk.Scrollbar(frame1, command=tree.yview)
tree.configure(yscrollcommand=ybar.set)
tree.configure(xscrollcommand=xbar.set)

tree.grid(row=0, column=1)
xbar.grid(row=1, column=1, sticky='we')
ybar.grid(row=0, column=2, sticky='ns')
frame1.config(width=100)

root.mainloop()

Benutzeravatar
peterpy
User
Beiträge: 188
Registriert: Donnerstag 7. März 2013, 11:35

Hallo Wolfgang,

pack erachte ich ganz und gar nicht als verkorkst, es ist einfach anders als grid und ermöglicht auch Layouts die nicht an ein Gitter gebunden sind.
Nur dürfen pack und grid nicht zusmmen im selben Widget verwendet werden.
Aber um einen Frame in einen Frame zu setzen, genügt pack. Da ist grid schon Luxus.

Auch das Root-Fenster zu bergenzen, ist keine gute Idee. Wenn schon, wird das betroffene Widget (frame1) begrenzt. proagate(0) begrenzt frame1 auf die angegebene Grösse width=900 , height=260

Also ich hab mal deinen Beispiel- Datensatz erweitert, so dass auch mein Blidschirm für Treeviwew zu klein wird.
columnconfigure(1, weight=1) räumt der Spalte 1 im frame1, also der Vertikalen Scrollbar, Platz ein, damit diese sichtbar bleibt. Wenn Du in deinem Projekt dann sehr viele Zeilen hast, solltest Du auch rowconfigure für die Zeile der horizontalen Scrollbar verwenden.
Hier der Code:

Code: Alles auswählen

test = True                         # True|False = Zugriff auf SQLite
file = 'Ersatzteile.db'             #\
table = 'etl_utf'                   #/
mode = 'grid'                       # 'grid'|'packh'|'packv'
char_width, base_width = 6.3, 8     # Spaltenbreite

import tkinter as tk
from tkinter import ttk

if test:
    data = []
    for i in range(5):
        data.append(['A', 'MAE214613', '', '', 'HW', 'Motorschild 60903', '',
                     'B', 'MAE214613', '', '', 'HW', 'Motorschild 60903', '',
                     'C', 'MAE214613', '', '', 'HW', 'Motorschild 60903', ''])
        data.append(['A0xx', 'LGE8.845050.6', '', '', '2m',
                     'Steckdose 2-polig, Verbindungskabel',
                     'z.B. Stainz-Tender',
                     'Bxxx', 'LGE8.845050.6', '', '', '2m',
                     'Steckdose 2-polig, Verbindungskabel',
                     'z.B. Stainz-Tender',
                     'Cxxx', 'LGE8.845050.6', '', '', '2m',
                     'Steckdose 2-polig, Verbindungskabel',
                     'z.B. Stainz-Tender'])
        data.append(['A1xx', 'LGE8.839040.7', '', '', '2m', 'Steckdose 2-polig',
                     'z.B. Stainz',
                     'Bxxx', 'LGE8.839040.7', '', '', '2m', 'Steckdose 2-polig',
                     'z.B. Stainz',
                     'Cxxx', 'LGE8.839040.7', '', '', '2m', 'Steckdose 2-polig',
                     'z.B. Stainz'])        
        data.append(['A2xx', 'LGE1.22500.170', '', '', '2m',
                     'Steckdose 2-polig,  Halter für 122500.170.2',
                     'z.B. Stainz',
                     'Axxx', 'LGE1.22500.170', '', '', '2m',
                     'Steckdose 2-polig,  Halter für 122500.170.2',
                     'z.B. Stainz',
                     'A3xx', 'LGE1.22500.170', '', '', '2m',
                     'Steckdose 2-polig,  Halter für 122500.170.2',
                     'z.B. Stainz'])
        columns = ('0_A_ort', '1_nr', '2_stk', '3_pg', '4_spur', '5_bez',
                   '6_komm', '7_B_ort', '8_nr', '9-stk', '10_pg', '11_spur',
                   '12_bez', '13_komm', '14_C_ort', '15_nr', '16_stk',
                   '17_pg', '18_spur', '19_bez', '20_end of columns')        

col_types = (4, 17, 3, 2, 4, 46, 40, 4, 17, 3, 2, 4, 46, 40,
             4, 17, 3, 2, 4, 46, 40)

root = tk.Tk()
#root.geometry('650x350')
root.title(file)
root.config(background='cyan')

frame1 = tk.Frame(root, background='red', width=900, height=260)
frame1.grid(row=0, column=0)

tree = ttk.Treeview(frame1)
tree['show'] = 'headings'       # suppress white spaces left of columns
tree['selectmode'] = 'browse'   # select one line at a time only
tree['columns'] = columns
for i, col in enumerate(columns):
    width = int(col_types[i] * char_width + base_width)
    tree.column(col, width=width, anchor='w')
    tree.heading(col, text=col, anchor='w')
for i, row in enumerate(data):
    tree.insert('', i, text='', values=(row))

xbar = ttk.Scrollbar(frame1, orient='horizontal', command=tree.xview)
ybar = ttk.Scrollbar(frame1, command=tree.yview)
tree.configure(yscrollcommand=ybar.set)
tree.configure(xscrollcommand=xbar.set)

tree.grid(row=0, column=1)
frame1.columnconfigure(1, weight=1)
xbar.grid(row=1, column=1, sticky='we')
ybar.grid(row=0, column=2, sticky='ns')

frame1.grid_propagate(0)

root.mainloop()
Viel Erfolg, Gruss Peter
we1303
User
Beiträge: 14
Registriert: Montag 22. März 2021, 21:51

Hallo Peter,
Hallo Community,

sorry für die Verzögerung, hatte noch andere Probs.

.columnconfigure(col, weight=1) und .grid-propagate(False) sind die Problemlöser, super vielen Dank dafür.

Nach dem Umschreiben vom Code verstehe ich auch ein wenig die Abneigung gegen .grid: Muss die Höhe ausrechnen und angeben, obwohl ich nur die Breite brauche. Muss Höhe und Breite ausrechnen bevor Treeview diese liefert. Das text widget ist besser implementiert.

Nochmals vielen Dank für die Hilfe

Wolfgang
Antworten