Tkinter scrollbar im canvas funktioniert nicht.

Fragen zu Tkinter.
Antworten
TechHippie420
User
Beiträge: 11
Registriert: Montag 22. Februar 2021, 10:03

Moin zusammen,

Ich habe ein Problem mit scrollbars in einem tkinter canvas.
Unzwar lasse ich mir Einträge aus einem pandas Dataframe in eben diesem Canvas anzeigen.
Das Problem ist das dies zu viele Einträge sind und sie deshalb nicht alle angezeigt werden.
Nun wollte ich 'einfach' eine scrollbar hinzufügen (warum erkennt tkinter das eigentlich nicht von selbst?)
Jedenfalls probiere ich nun seit gestern rum und versuche erfolglos diese Scrollbar zum laufen zu bekommen.

zunächst mal der Code:

Code: Alles auswählen

def create_user_view_entrys(self, parent, controller, user_view_canvas, lbl_PortfolioOverview):

        self.lbl_PortfolioOverview.destroy()

        df = pd.read_csv('potential_portfolio.csv')
        df.drop(columns=['Unnamed: 0'], inplace=True)
        df_user_view_entrys = pd.DataFrame(df)
        rows = len(df_user_view_entrys)

        # self.scrollable_canvas = tk.Canvas(self.user_view_canvas, bg='grey')
        # self.scrollable_canvas.grid_columnconfigure((0,1,2,3,4,5), weight=1)
        # self.scrollable_canvas.grid_rowconfigure((rows), weight=0)
        # self.scrollable_canvas.grid(column=0, row=1, columnspan=5, rowspan=3, padx=10, pady=10, sticky='NESW')

        self.entry_canvas = tk.Canvas(self.user_view_canvas, bg='green')
        self.entry_canvas.grid_columnconfigure((0,1,2,3,4,5), weight=1)
        self.entry_canvas.grid_rowconfigure((rows), weight=1)
        self.entry_canvas.grid(column=0, row=1, columnspan=6, rowspan=3, padx=10, pady=10, sticky='NESW')

        self.entry_canvas_scrollbar = ttk.Scrollbar(self.entry_canvas, orient='vertical', command=self.entry_canvas.yview)
        self.entry_canvas_scrollbar.grid(column=6, row=0, sticky='ns')

        self.entry_canvas.configure(yscrollcommand=self.entry_canvas_scrollbar.set)
        
        try:

            for i, row in df_user_view_entrys.iterrows():

                check_button_value = tk.BooleanVar()
                check_button_value.set(True)

                # print(f'Index: {i}')
                # print(f'{row}\n')

                self.entry_label_canvas = tk.Label(self.entry_canvas, bg='blue')
                self.entry_label_canvas.grid_columnconfigure((0,1,2,3,4,5,6), weight=1)
                self.entry_label_canvas.grid_rowconfigure((0,1), weight=0)
                self.entry_label_canvas.grid(column=0, row=i, columnspan=6, padx=2, pady=2, sticky='NESW')

                self.checkbutton = tk.Checkbutton(self.entry_label_canvas, variable=check_button_value)
                self.checkbutton.grid(column=0, row=1, padx=2, pady=2)

                self.entry_label = tk.Label(self.entry_label_canvas, text=row.to_frame().T)
                self.entry_label.grid(column=1, row=1, columnspan=4, padx=2, pady=2, sticky='NESW')

                self.show_info_button = ttk.Button(self.entry_label_canvas, text='Mehr Infos')
                self.show_info_button.grid(column=5, row=1, columnspan=2, padx=2, pady=2, sticky='NESW')                

        except Exception:
                LOG.exception("doesn't work ...")
der läuft zwar ohne Fehlermeldung durch, allerdings ist das Ergebnis nicht wie gedacht.

hier mal der link zum screenshot auf google drive
https://drive.google.com/file/d/13i251s ... sp=sharing

hoffe das klappt, hat mit dem Bild in den img Tags glaube ich nicht funktioniert.

vielleicht kann mir ja jemand sagen was ich da schon wieder falsch mache.
Benutzeravatar
peterpy
User
Beiträge: 188
Registriert: Donnerstag 7. März 2013, 11:35

Hallo TechHippie420
schau mal hier:
https://www.delftstack.com/de/tutorial/ ... scrollbar/
Gruss Peter
TechHippie420
User
Beiträge: 11
Registriert: Montag 22. Februar 2021, 10:03

Moin peterpy,

erstmal danke für Deine Antwort.
Natürlich habe ich aber selbst schon nach möglichen Lösungen gesucht, bevor ich hier ein neues Thema erstellt habe.
Leider löst das aber mein Problem nicht weil die Scrollbar einfach nicht funktioniert.
Wie Du auf dem Screenshot sehen kannst, ist sie ja nicht einmal richtig (über die ganze Höhe des Canvas) positioniert.
Habe auch schon versucht den Canvas in einen anderen Canvas bzw. auch einen anderen Frame zu packen und dann diesen scrollbar zu machen, hat aber alles nicht funktioniert.
Nun ja, ich werde dann mal in den nächsten Tagen mein Glück weiter versuchen.

Trotzdem nochmals danke für Deine Antwort und

beste Grüße

TechHippie420
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@TechHippie420: Auf dem Screenshot sieht man sehr schön das der Scrollbalken genau dort posititioniert ist, wo ihn der Code positioniert hat: In Zeile 0 von dem Grid, genau wie das das erste `Label` in dem Grid, weshalb der Scrollbalken auch die gleiche Höhe hat. Ein `Label` ist an der Stelle übrigens das falsche Widget. Ich bin überrascht, dass das überhaupt geht. Da würde man einen `Frame` verwenden, denn Du benutzt ja nichts von einem `Label`.

Beziehungsweise würde man für so eine Tabellenstruktur *einen* `Frame` verwenden und nicht einen `Frame` pro Zeile.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
TechHippie420
User
Beiträge: 11
Registriert: Montag 22. Februar 2021, 10:03

Moin __blackjack__,

auch Dir gleich erstmal danke für Deine schnelle Antwort.
Leider kann ich damit überhaupt nichts anfangen. Ich verstehe zwar das was Du bzgl. der Positionierung der Scrollbar in Zeile 0 des Grids schreibst, wenn ich aber den code so ändere, das ich das alles in einen Frame packe ...

Code: Alles auswählen

def create_user_view_entrys(self, parent, controller, user_view_canvas, lbl_PortfolioOverview):

        self.lbl_PortfolioOverview.destroy()

        df = pd.read_csv('potential_portfolio.csv')
        df.drop(columns=['Unnamed: 0'], inplace=True)
        df_user_view_entrys = pd.DataFrame(df)
        rows = len(df_user_view_entrys)

        self.scrollable_frame = tk.Frame(self.user_view_canvas, bg='grey')
        self.scrollable_frame.grid_columnconfigure((0,1,2,3,4,5), weight=1)
        self.scrollable_frame.grid_rowconfigure(0, weight=0)
        self.scrollable_frame.grid(column=0, row=1, columnspan=5, rowspan=3, padx=10, pady=10, sticky='NESW')

        self.entry_canvas = tk.Canvas(self.scrollable_frame, bg='green')
        self.entry_canvas.grid_columnconfigure((0,1,2,3,4,5), weight=1)
        self.entry_canvas.grid_rowconfigure((rows), weight=1)
        self.entry_canvas.grid(column=0, row=1, columnspan=6, rowspan=3, padx=10, pady=10, sticky='NESW')

        self.entry_canvas_scrollbar = ttk.Scrollbar(self.scrollable_frame, orient='vertical', command=self.entry_canvas.yview)
        self.entry_canvas_scrollbar.grid(column=6, row=0, sticky='ns')

        self.entry_canvas.configure(yscrollcommand=self.scrollable_frame.set)
        
        try:

            for i, row in df_user_view_entrys.iterrows():

                check_button_value = tk.BooleanVar()
                check_button_value.set(True)

                # print(f'Index: {i}')
                # print(f'{row}\n')

                self.entry_label_canvas = tk.Label(self.entry_canvas, bg='blue')
                self.entry_label_canvas.grid_columnconfigure((0,1,2,3,4,5,6), weight=1)
                self.entry_label_canvas.grid_rowconfigure((0,1), weight=0)
                self.entry_label_canvas.grid(column=0, row=i, columnspan=6, padx=2, pady=2, sticky='NESW')

                self.checkbutton = tk.Checkbutton(self.entry_label_canvas, variable=check_button_value)
                self.checkbutton.grid(column=0, row=1, padx=2, pady=2)

                self.entry_label = tk.Label(self.entry_label_canvas, text=row.to_frame().T)
                self.entry_label.grid(column=1, row=1, columnspan=4, padx=2, pady=2, sticky='NESW')

                self.show_info_button = ttk.Button(self.entry_label_canvas, text='Mehr Infos')
                self.show_info_button.grid(column=5, row=1, columnspan=2, padx=2, pady=2, sticky='NESW')                

        except Exception:
                LOG.exception("doesn't work ...")
... dann bekomme ich gar nichts mehr angezeigt.

Ich habe leider keine Ahnung, welchen Frame/Canvas/Label ich worein packen muss. Ist alles für mich ein absolut unverständliches System.
Ich verstehe auch immer noch nicht warum der Container nicht automatisch scrollbar wird, wenn die Einträge nicht hinein passen - aber gut.

Jedenfalls wenn ich den Code so ändere das ich alles in einen Frame packe bekomme ich das:

https://drive.google.com/file/d/12x6wIC ... sp=sharing

Wie gesagt, diese scrollbar Geschichte ist für mich echt böhmische Dörfer wie man so schön sagt.

Trotzdem nochmals Danke für Deine schnelle Antwort und auch Dir

beste Grüße

TechHippie420
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@TechHippie420: Du machst das falsch herum. Der Canvas ist ”scrollbar”, weil man *dem* Scrollbalken zuordnen kann. Und in den `Canvas` steckt man dann das rein was zu gross ist. In der Regel einen Frame der als Container für den zu grossen Inhalt dient.

Und das tut man auch nicht per `master`-Argument sondern per `Canvas.create_window()` denn wenn der Inhalt fertig ist muss man dem `Canvas`-Objekt noch sagen welcher Ausschnitt des unendlich grossen Canvas denn eigentlich die `scrollregion` ist. Dafür verwendet man die „bounding box“ die alles auf dem `Canvas`-Objekt umfasst. Kann man mit `Canvas.bbox()` abfragen.

Warum in Deinem aktuellen Code nichts mehr angezeigt wird sollte klar werden wenn Du mal schaust welche Ausnahme der Code auslöst bevor er zu der Schleife kommt.

Was auch komisch bis falsch, weil so nicht gedacht ist, ist das definieren von vielen Grid-Spalten, deren Gewicht auf 1 setzen und dann grundsätzlich in jeder Zeile immer die gleichen zusammen zu fassen. Dafür sind die Gewichte gedacht, dass man im Grid nicht irgendwelche künstlichen Spalten braucht. Du hast da drei logische Spalten mit den Gewichten 1, 4, und 2, und nicht 7 Spalten mit dem Gewicht 1. Wobei zumindest vom bisher gezeigten Inhalt eigentlich nur die mittlere Spalte sinnvoll expandierbar ist, also nur die ein Gewicht von ≠0 bräuchte. Der Inhalt der ersten und dritten Spalte profitiert da nicht wirklich von, weil das in jeder Zeile das gleiche und relativ kurz ist. Da reicht als Platz der Platz den der Inhalt tatsächlich nur braucht völlig aus.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
peterpy
User
Beiträge: 188
Registriert: Donnerstag 7. März 2013, 11:35

Hallo TechHippie420,

ich hab mal etwas geschrieben, um die Zeilenhöhe der Entries in Abhängigkeit zur Schrifthöhe zu ermitteln.
Da ist auch eine Scrollbar eingebaut.

Code: Alles auswählen

#!/usr/bin/env python3 -u
#-*- coding: #utf-8 -*-

from tkinter import Tk, LabelFrame, Label, Frame, Entry, Scrollbar,\
StringVar, Canvas, font

texthoehe = 11
zeilen = 250
spalten = 5
rahmenhoehe = 3

root = Tk()
spaltennamen = ["Nr.", "A", "B", "C", "D", "E", "F", "G", "H"]
txt = "{:^36}{:^36}{:^36}{:^36}{:^36}".format("Spalte 1", "Spalte 2",
                                              "Spalte 3", "Spalte 4",
                                              "Spalte 5")
Label(root, text=txt, bg='cyan', width=101).grid(row=0,column=0, sticky='w')#Fester Text, z.B Spaltentitel

container = LabelFrame(root, labelanchor="nw", bg='lightgreen')
container.grid(row=1, column=0)
scrolly = Scrollbar(container, orient="vertical", bg='cyan', troughcolor='blue')
scrolly.grid(row=0, column=5, columnspan=2, sticky="n"+"s")

canvas = Canvas(container, width=810, yscrollcommand=scrolly.set,
                bg='yellow')
canvas.grid(row=0, column=0, columnspan=5, sticky='nw')
rollender_frame = Frame(canvas, width=750)
canvas.create_window((0, 0), window=rollender_frame, anchor='nw')
scrolly.config(command=canvas.yview)
schrift = font.Font(root, font=("helvetica", texthoehe, "bold"))

for zeile in range(zeilen):
    for spalte in range(spalten):
        var = StringVar()
        entry = Entry(rollender_frame, textvariable=var,
                      font=("helvetica", texthoehe, "italic"))        
        entry.grid(row = zeile + 1, column=spalte, pady=rahmenhoehe)
        if spalte == 0 and zeile > 0:
            var.set("{}".format(zeile))
        if zeile == 0:
            var.set("{}".format(spaltennamen[spalte]))
            entry.config(state="disabled", disabledbackground="white",
                         disabledforeground="black")
            entry.config(font=schrift)

schriftattribute = schrift.metrics()
zeichenabstand = schriftattribute['linespace']
zeilenhoehe = (zeichenabstand+6) + (2*rahmenhoehe)
canvas.config(scrollregion= (0, 0, 800, zeilen*zeilenhoehe))

root.mainloop()
Gruss Peter
TechHippie420
User
Beiträge: 11
Registriert: Montag 22. Februar 2021, 10:03

Moin _blackjack__, peterpy,

Besten Dank erstmal für eure Antworten.

Leider bringt mich nichts davon weiter und ehrlich gesagt, finde ich es mittlerweile auch zuviel Aufwand für das was ich vorhatte. Einfach nur eine kleine Spielerei für mein Börsenspiel. Habe mich aber jetzt in den vergangenen Tagen schon mehr mit dieser verdammten Scrollbar beschäftigt als insgesamt in den letzten Jahren mit diesem Börsenspiel.
Wenn das so ein Ding ist, da einfach eine Scrollbar einzufügen, dann lasse ich das erstmal. Wer weis? vielleicht erweitert ja irgendwann mal jemand Tkinter um das ganze mit einigermaßen angemessenem Aufwand zu gestalten und ansonsten ist's auch nicht schlimm.

Besten Gruß und nochmals vielen Dank

TechHippie420
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@TechHippie420: Darauf würde ich eher nicht warten, denn Tk gibt's ja nun schon eine ganze Weile. Ich würde da eher empfehlen auf ein moderneres GUI-Rahmenwerk umzusteigen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Antworten