Hallo
Ich habe vor eine Tabelle zu erstellen, die aus mehreren Spalten und Zeilen besteht. Ich habe ein Mockup-Bild angehängt.
Die Tabelle habe ich schon. Ich habe eine Funktion create_table(rows, columns, startrow, startcolumn) geschrieben, die im wesentlichen in Schleifen eine Tabelle aus Entry-Felder erstellt. Ich benötige 3 Tabellen nebeneinandner, wie im Bild zu sehen. Das funktioniert. Nun die Herausforderung. Ich möchte 20 Zeilen sichtbar machen und einen Scrollbar daneben, falls mehr als 20 Zeilen notwendig sind. Ich dachte daran die Tabellen aus 30 Zeilen zu erstellen, und die letzten 10 Zeilen scrollbar zu machen. Dazu habe ich nichts gefunden.
Ich dachte auch schon an ein Frame-Widget, um die Tabellen als Objekt zusammenzufassen und das Frame scrollbar zu machen.
Dann dachte ich es wäre besser keine Entry-Felder sondern Tabellen aus Listboxen zu erstellen. Da bekomme ich das besser mit den Scrollbars hin, da habe ich viel dazu gefunden. Aber die Listbox sieht nun mal nicht wie eine Tabelle aus, z.B. müssten schon mal die Zeilen durch gridlines separiert werden, sonst verliert man bei einer großen Tabelle die Zugehörigkeit.
Zusammengefasst: Ich hätte gerne eine Tabelle aus drei verschiedenen Spalten und Zeilen (siehe Bild), wobei weitere Zeilen scrollbar sein sollen, aber ich habe keine Idee, wie ich es hinbekommen soll.
Es muss möglich sein, dass weiß ich. Kann mir jemand helfen? Vielen Dank!
Grüße,
Tuvok
Tabelle mit Scrollbar erstellen
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Also, den Frame kann man nicht scrollen. Aber richtig ist: die Entries in einen Frame. Dann den Frame in einen Canvas und zwar in einem Window mit Koordinaten x=0,y=0. Neben dem Canvas eine Scrollbar und Scrollbar und Canvas entsprechend verbinden. Den Canvas kann man nämlich scrollen.LtTuvok hat geschrieben:Ich dachte auch schon an ein Frame-Widget, um die Tabellen als Objekt zusammenzufassen und das Frame scrollbar zu machen.
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Das wäre ein Beispiel. Brauchst nur Deine Tabelle einfügen und die Höhe entsprechend setzen:LtTuvok hat geschrieben:Hallo
Zusammengefasst: Ich hätte gerne eine Tabelle aus drei verschiedenen Spalten und Zeilen (siehe Bild), wobei weitere Zeilen scrollbar sein sollen, aber ich habe keine Idee, wie ich es hinbekommen soll.
Es muss möglich sein, dass weiß ich. Kann mir jemand helfen? Vielen Dank!
Grüße,
Tuvok
Code: Alles auswählen
# -*- coding: utf-8 -*-
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
SCROLLHEIGHT = 200 # pixel
class Application(tk.Tk):
def __init__(self,**kwargs):
tk.Tk.__init__(self,**kwargs)
# widget definitions ===================================
self.scroll_tabelle = ScrollTabelle(self)
self.scroll_tabelle.pack()
class ScrollTabelle(tk.LabelFrame):
def __init__(self,master,**kwargs):
tk.LabelFrame.__init__(self,master,**kwargs)
self.config(text='scroll_tabelle')
# widget definitions ===================================
canvas = Canvas_1(self)
canvas.grid(row=0, sticky='nesw', column=1)
scrollbar = tk.Scrollbar(self)
scrollbar.grid(row=0, sticky='ns')
canvas.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=canvas.yview)
class Canvas_1(tk.Canvas):
def __init__(self,master,**kwargs):
tk.Canvas.__init__(self,master,**kwargs)
# widget definitions ===================================
self.frame = Frame_1(self)
coords = (0,0)
item = self.create_window(*coords)
self.itemconfig(item,anchor = 'nw',window = self.frame)
class Frame_1(tk.Frame):
def __init__(self,master,**kwargs):
tk.Frame.__init__(self,master,**kwargs)
# widget definitions ===================================
self.canvas = self.master
self.canvas.bind('<Configure>',self.canvas_configure)
self.bind('<Configure>',self.frame_configure)
# Daten zum Anzeigen === dafür dann Tabelle reisetzen
tk.Label(self,text='Config des Canvas',font='Helvetica 30').grid(row=0,columnspan=2)
maxwidth=0
for value in self.master.config().values():
maxwidth = max(maxwidth,len(str(value)))
print(maxwidth)
row = 1
for key,value in self.master.config().items():
tk.Label(self,text=key).grid(row=row,sticky='e')
entry = tk.Entry(self,width=maxwidth)
entry.grid(row=row,column=1)
entry.insert(0,value)
row +=1
# ====================================
# was man auch mit dem Canvas tut, er muß sich immer an die Breite des Frame anpassen - an die tatsächliche Breite
def canvas_configure(self,event=None):
if self.canvas['width'] != self.winfo_width():
self.canvas['width'] = self.winfo_width()
# hier ist der layoutmanager noch nicht fertig, daher den canvas an die vorraussichtliche Größe des frames anpassen
def frame_configure(self,event=None):
# Scroll region des canvas so groß wie man für den Frame braucht
self.canvas.config(scrollregion="0 0 %s %s" % (self.winfo_reqwidth(), self.winfo_reqheight()))
# breite des canvas so breit wie man für den frame braucht
self.canvas.config(width=self.winfo_reqwidth())
# wenn die benötigte frame höhe größer als SCROLLHEIGHT pixel ist, dann canvas höhe auf SCROLLHEIGHT pixel beschränken
if self.winfo_reqheight() > SCROLLHEIGHT:
self.canvas.config(height=SCROLLHEIGHT)
# sonst canvas höhe so hoch wie man für den frame braucht
else:
self.canvas.config(height=self.winfo_reqheight())
if __name__ == '__main__':
Application().mainloop()
Hallo Alfons
Danke nochmal für deinen Code. Ich habe versucht ihn nachzuvollziehen und für meine Zwecke zu vereinfachen. Insbesondere das Konzept mit .create_window war mir neu. Das war lange Zeit der Knackpunkt bis ich ihn zu verstanden glaubte. Mich wundert es immer noch ein bisschen, dass window sogar die Darstellung (z.B. .pack() oder grid()) ersetzt. Denn dies brauche ich für Entry nicht mehr, wenn sich window um das Entry stülpt. Dann muss nur noch das Window dargestellt werden.
Was hältst du von dem Code?
Viele Grüße und Danke nochmal,
Tuvok
Danke nochmal für deinen Code. Ich habe versucht ihn nachzuvollziehen und für meine Zwecke zu vereinfachen. Insbesondere das Konzept mit .create_window war mir neu. Das war lange Zeit der Knackpunkt bis ich ihn zu verstanden glaubte. Mich wundert es immer noch ein bisschen, dass window sogar die Darstellung (z.B. .pack() oder grid()) ersetzt. Denn dies brauche ich für Entry nicht mehr, wenn sich window um das Entry stülpt. Dann muss nur noch das Window dargestellt werden.
Was hältst du von dem Code?
Code: Alles auswählen
import tkinter as tk
root = tk.Tk()
frame=tk.Frame(root,width=300,height=300)
frame.grid(row=0,column=0)
canvas = tk.Canvas(frame, bg='gray90', width=300, height=300, scrollregion=(0,0,0,1200))
canvas.grid(column=1, row=0)
vbar=tk.Scrollbar(frame,orient="vertical") #scrollbar
vbar.grid(column=0, row=0, ipady=125)
vbar.config(command=canvas.yview)
canvas.config(yscrollcommand=vbar.set)
row=column=0
entry_names=[]
row_column_names=[]
rows=50
columns=3
specific_name="test"
Tk_instance=canvas
field_width=10
for row in range(rows):
temp_coordinates_row_list=[]
for column in range(columns):
element_name=specific_name+"row"+str(row)+"column"+str(column) #this ElementName is to identify the StringVar()
element_name=tk.StringVar()
temp_coordinates_row_list.append(element_name)
eintrag=tk.Entry(Tk_instance, textvariable=element_name, width=field_width) #the variable "Eintrag" is only needed for the grid once
row_coordinate=5+20*row
column_coordinate=5+field_width*column*6.5
canvas.create_window(column_coordinate, row_coordinate, window=eintrag, anchor="nw")
row_column_names.append(temp_coordinates_row_list)
row_column_names[0][0].set("Hello")
print(row_column_names[0][0].get())
root.mainloop()
Tuvok
@LtTuvok: auf oberster Ebene sollten keine Anweisungen stehen, statt dessen solltest Du alles in Funktionen schreiben. Eingerückt wird immer mit 4 Leerzeichen pro Ebene (nicht 3).
Zeile 17: row und column werden nicht gebraucht.
Zeile 18: entry_names wird nicht verwendet.
Zeile 23/24: wird nicht gebraucht.
Zeile 32: wird nicht verwendet.
Zeile 35: der Kommentar macht irgendwie keinen Sinn.
Zeile 17: row und column werden nicht gebraucht.
Zeile 18: entry_names wird nicht verwendet.
Zeile 23/24: wird nicht gebraucht.
Zeile 32: wird nicht verwendet.
Zeile 35: der Kommentar macht irgendwie keinen Sinn.
Hallo Sirius3
Danke für die Hinweise. Ich werde sie berücksichtigen.
Hast Du ansonsten noch inhaltliche Kommentare zur Strukture mit Frame, Canvas, Scrollbar und .create_window?
Was ich mich als nächsten Schritt Frage ist, wie es möglich ist mit einem Scrollbar zwei Canvas zu scrollen. Wie ich gesehen habe wird durch
Die Scroll-Funktionalität auf das canvas gesetzt. Ich kann nicht ein canvas1 und ein canvas2 definieren (das kann ich schon noch, aber) und dann mein eines vbar sowohl dem canvas1 UND dem canvas2 zuordnen.
Eine Idee wäre die Scrollposition auszulesen und einen zweiten nicht sichtbaren Scrollbar zu übertragen, aber wie dies geht und ob es nicht einen besseren Weg gibt (wahrscheinlich) weiß ich nicht.
Ich hätte gerne verschiedene Canvas mit potentiell verschiedener Hintergrundfarben und jeweils ähnlicher Tabellen, die aber alle über den gleichen Scrollbar gesteuert werden können.
Viele Grüße,
Tuvok
Danke für die Hinweise. Ich werde sie berücksichtigen.
Hast Du ansonsten noch inhaltliche Kommentare zur Strukture mit Frame, Canvas, Scrollbar und .create_window?
Was ich mich als nächsten Schritt Frage ist, wie es möglich ist mit einem Scrollbar zwei Canvas zu scrollen. Wie ich gesehen habe wird durch
Code: Alles auswählen
vbar.config(command=canvas.yview)
Eine Idee wäre die Scrollposition auszulesen und einen zweiten nicht sichtbaren Scrollbar zu übertragen, aber wie dies geht und ob es nicht einen besseren Weg gibt (wahrscheinlich) weiß ich nicht.
Ich hätte gerne verschiedene Canvas mit potentiell verschiedener Hintergrundfarben und jeweils ähnlicher Tabellen, die aber alle über den gleichen Scrollbar gesteuert werden können.
Viele Grüße,
Tuvok
Das sollte so klappen:
Das ist jetzt nur ein Ausschnitt, auf dem iPad tippt es sich mistig
Code: Alles auswählen
def multi_yview(canvases, *args):
for c in canvases:
c.yview(*args)
command=functools.partial(multi_yview, [canvas1, canvas2])
Hallo deets
Danke für den Hinweis. functiontools kannte ich noch nicht. Ich habe mal versucht einen möglichst einfachen Code zu schreiben (und ein paar Hinweise von Sirius3 zu berücksichtigen). Siehe unten
Ich weiß aber noch nicht genau wie ich den Befehl richtig in meinen Code einbaue.
Viele Grüße,
Tuvok
Danke für den Hinweis. functiontools kannte ich noch nicht. Ich habe mal versucht einen möglichst einfachen Code zu schreiben (und ein paar Hinweise von Sirius3 zu berücksichtigen). Siehe unten
Ich weiß aber noch nicht genau wie ich den Befehl richtig in meinen Code einbaue.
Viele Grüße,
Tuvok
Code: Alles auswählen
import tkinter as tk
import functools as ft
def multi_yview(canvases, *args):
for c in canvases:
c.yview(*args)
root = tk.Tk()
frame1=tk.Frame(root,width=220,height=300)
frame1.grid(row=0,column=0)
frame2=tk.Frame(root,width=120,height=300)
frame2.grid(row=0,column=1)
canvas1 = tk.Canvas(frame1, bg='gray90', width=220, height=300, scrollregion=(0,0,0,500))
canvas1.grid(column=1, row=0)
canvas2 = tk.Canvas(frame2, bg='gray90', width=120, height=300, scrollregion=(0,0,0,500))
canvas2.grid(column=0, row=0)
vbar=tk.Scrollbar(frame1,orient="vertical")
vbar.grid(column=0, row=0, ipady=125)
vbar.config(command=canvas1.yview)
canvas1.config(yscrollcommand=vbar.set)
canvas2.config(yscrollcommand=vbar.set)
command=ft.partial(multi_yview, [canvas1, canvas2])
row_column_names1=[]
Tk_instance1=canvas1
rows=50
columns=3
Tk_instance=canvas1
field_width1=10
for row in range(rows):
temp_coordinates_row_list=[]
for column in range(columns):
element_name=tk.StringVar()
temp_coordinates_row_list.append(element_name)
eintrag=tk.Entry(Tk_instance1, textvariable=element_name, width=field_width1) #the variable "Eintrag" is only needed for the grid once
row_coordinate=5+20*row
column_coordinate=5+field_width1*column*6.5
canvas1.create_window(column_coordinate, row_coordinate, window=eintrag, anchor="nw")
row_column_names1.append(temp_coordinates_row_list)
row_column_names2=[]
Tk_instance2=canvas2
field_width2=5
for row in range(rows):
temp_coordinates_row_list=[]
for column in range(columns):
element_name=tk.StringVar()
temp_coordinates_row_list.append(element_name)
eintrag=tk.Entry(Tk_instance2, textvariable=element_name, width=field_width2) #the variable "Eintrag" is only needed for the grid once
row_coordinate=5+20*row
column_coordinate=5+field_width2*column*6.5
canvas2.create_window(column_coordinate, row_coordinate, window=eintrag, anchor="nw")
row_column_names2.append(temp_coordinates_row_list)
row_column_names1[0][0].set("Hello")
#print(row_column_names1[0][0].get())
root.mainloop()
@LtTuvok: warum benennst Du canvasX in Tk_instanceX um? Statt Copy-Paste schreibt man beim Programmieren eine Funktion, die man für jedes Canvas aufrufen kann. Wie eigentlich alles ab Zeile 8 in eine Funktion gehört.
Code: Alles auswählen
import tkinter as tk
from functools import partial
def multi_yview(canvases, *args):
for c in canvases:
c.yview(*args)
def generate_table(root, vbar, width, field_width, num_rows=50, num_columns=3):
canvas = tk.Canvas(root, bg='gray90', width=width, height=300, scrollregion=(0,0,0,500))
canvas.config(yscrollcommand=vbar.set)
rows = []
for row in range(num_rows):
cells = []
for column in range(num_columns):
contents = tk.StringVar()
cells.append(contents)
eintrag = tk.Entry(canvas, textvariable=contents, width=field_width)
row_coordinate = 5 + row * 20
column_coordinate = 5 + field_width * column * 6.5
canvas.create_window(column_coordinate, row_coordinate, window=eintrag, anchor="nw")
rows.append(cells)
return canvas, rows
def main():
root = tk.Tk()
vbar = tk.Scrollbar(root, orient="vertical")
vbar.grid(row=0, column=0, ipady=125)
canvas1, rows1 = generate_table(root, vbar, width=220, field_width=10)
canvas1.grid(row=0, column=1)
canvas2, rows2 = generate_table(root, vbar, width=120, field_width=5)
canvas2.grid(row=0, column=2)
command = partial(multi_yview, [canvas1, canvas2])
vbar.config(command=command)
root.mainloop()
if __name__ == '__main__':
main()
Hallo Sirius3
Danke für die Anmerkung. Da ich noch am Anfang meines Python-Programmierlernens bin, bin ich immer dankbar für jedes Kommentar, vor allem was Konventionen, Best Practice u.ä. angeht.
Du hast natürlich recht. Genau das was Du geschrieben hast mache ich auch in meinem eigentlichen Programm. Das war nur auf die schnelle zusammengebastelt und für mich war es in dem Moment einfacher. Da ich in meinem eigentlichen Code noch wesentlich mehr Tabellen habe, mache ich das dann über eine Funktion.
Viele Grüße,
Tuvok
Danke für die Anmerkung. Da ich noch am Anfang meines Python-Programmierlernens bin, bin ich immer dankbar für jedes Kommentar, vor allem was Konventionen, Best Practice u.ä. angeht.
Du hast natürlich recht. Genau das was Du geschrieben hast mache ich auch in meinem eigentlichen Programm. Das war nur auf die schnelle zusammengebastelt und für mich war es in dem Moment einfacher. Da ich in meinem eigentlichen Code noch wesentlich mehr Tabellen habe, mache ich das dann über eine Funktion.
Viele Grüße,
Tuvok
Hallo
Nebenbei bemerkt, eine etwas unschöne Sache ist, dass die Tabelle bzw. das jeweilige ganz unten und ganz oben stehende Entry-Feld über den Canvas hinausschaut. Ich habe schon versucht ein drittes Canvas unten in die nächste Row zu setzen und mit columnspan über die beiden Tabellen "zu kleistern", aber da bleibt ja auch noch der kleine Rahmen (den ich an sich ganz nett finde) übrig, wohinein die Entry-Felder überstehen.
Hat jemand dazu eine Idee, wie man das eventuell wegbekommt?
Viele Grüße,
Tuvok
Nebenbei bemerkt, eine etwas unschöne Sache ist, dass die Tabelle bzw. das jeweilige ganz unten und ganz oben stehende Entry-Feld über den Canvas hinausschaut. Ich habe schon versucht ein drittes Canvas unten in die nächste Row zu setzen und mit columnspan über die beiden Tabellen "zu kleistern", aber da bleibt ja auch noch der kleine Rahmen (den ich an sich ganz nett finde) übrig, wohinein die Entry-Felder überstehen.
Hat jemand dazu eine Idee, wie man das eventuell wegbekommt?
Viele Grüße,
Tuvok