Scrollbar in ttk Notebook einfügen

Fragen zu Tkinter.
Antworten
NinoBaumann
User
Beiträge: 77
Registriert: Samstag 25. April 2020, 19:03

Hallo,

ich baue mir gerade eine Anwendung, mit der ich bei uns den Essens- und Einkaufsalltag vereinfachen möchte. Mit einem ttk.Notebook habe ich mir verschiedene Tabs erstellt, in denen dann in Frames Gerichte und ihre Basisdaten stehen. Mittlerweile sind so viele Frames im Schachbrettmuster vorhanden, dass diese am unteren Rand verschwinden. Ich brauche also eine Scrollbar. Ich habe mich schon durch das Internet gewühlt. Aber ich habe nicht richtig verstanden, wie ich bei mir eine Scrollbar intergrieren kann. Kann mir jemand mit einer konkreten Lösung helfen?
Anbei der zum Verständnis wichtige Codeabschnitt.

Code: Alles auswählen

class MealFrame(tk.Frame):
    def __init__(self, master, name, image_path, nutritional_values):
        tk.Frame.__init__(self, master, highlightbackground="grey", highlightthickness=2)
        self.name = name
        self._food_image = ImageTk.PhotoImage(
            Image.open(image_path).resize((300, 200), Image.Resampling.LANCZOS)
        )
        tk.Label(self, image=self._food_image).pack()
        
        self.label_frame_text = tk.LabelFrame(self)
        self.label_frame_text.pack()
        
        self._selected_var = tk.BooleanVar()
        tk.Checkbutton(self.label_frame_text, text=name, variable=self._selected_var).grid(row = 1, column = 0)
        
        self.label_frame_servings = tk.LabelFrame(self.label_frame_text, borderwidth = 0)
        self.label_frame_servings.grid(row = 2, column = 0, padx = 10)
        tk.Label(self.label_frame_servings, text='Portionen: ', bg="light grey").pack(side = tk.LEFT)
        self._number_servings = tk.Entry(self.label_frame_servings, justify='center', width=10)
        self._number_servings.insert(0, 2)
        self._number_servings.pack(side = tk.RIGHT)
        
        self.label_frame_nutritional_values = tk.LabelFrame(self.label_frame_text)
        self.label_frame_nutritional_values.grid(row = 1, column = 1, rowspan = 2)
        tk.Label(self.label_frame_nutritional_values, text = 'Nährwerte:').grid(row = 0, column = 0, padx = 5, columnspan = 2)
        j = 0
        for i in nutritional_values:
            j = j + 1
            tk.Label(self.label_frame_nutritional_values, text = i).grid(row = j, column = 0, padx = 5)
            tk.Label(self.label_frame_nutritional_values, text = nutritional_values.get(i)).grid(row = j, column = 1, padx = 5)        
        
    @property
    def is_selected(self):
        return self._selected_var.get()
    @property
    def is_quantified(self):
        return self._number_servings.get()

class App(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master)
        self.master = master
        
        style = ttk.Style()

        style.theme_create( "yummy", parent="alt", settings={
                "TNotebook": {"configure": {"tabmargins": [2, 5, 2, 0] } },
                "TNotebook.Tab": {
                    "configure": {"padding": [62, 5], "background": 'grey',
                                  "font" : ('URW Gothic L', '10', 'bold')},
                    "map":       {"background": [("selected", 'red3')],
                                  "expand": [("selected", [1, 1, 1, 0])] } } } )
        
        style.theme_use("yummy")

        self.notebook = ttk.Notebook(self.master)
        
        frame_chicken = ttk.Frame(self.notebook)
        frame_pig = ttk.Frame(self.notebook)
        frame_beef = ttk.Frame(self.notebook)
        frame_vegetarian = ttk.Frame(self.notebook)
        frame_fast_food = ttk.Frame(self.notebook)
        frame_chicken.grid(row=0, column=0, padx = 5, pady = 5)
        frame_pig.grid(row=0, column=1, padx = 5, pady = 5)
        frame_beef.grid(row=0, column=2, padx = 5, pady = 5)
        frame_vegetarian.grid(row=0, column=3, padx = 5, pady = 5)
        frame_fast_food.grid(row=0, column=4, padx = 5, pady = 5)
        cuisine_frames = [frame_chicken, frame_pig, frame_beef, frame_vegetarian, frame_fast_food]
        
        self.notebook.add(frame_chicken, text='Hähnchen')
        self.notebook.add(frame_pig, text='Schwein')
        self.notebook.add(frame_beef, text='Rind')
        self.notebook.add(frame_vegetarian, text='Vegetarisch')
        self.notebook.add(frame_fast_food, text='Fast-Food')
        .
        .
        .
        .
        def main():
  	root = tk.Tk()
   	root.title('Lisa & Ninos virtuelle Einkaufsliste')
    	window = App(root)
   	window.mainloop()


	if __name__ == "__main__":
    	main()
Benutzeravatar
__blackjack__
User
Beiträge: 13572
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@NinoBaumann: Ein Notebook sollte einfach nicht so viele Reiter haben, falls ich das Problem richtig verstanden habe.

Ansonsten sind Scrollbalken in Tk nur bei bestimmten Widgets möglich, die spezielle Unterstützung haben. Wenn man also für ein beliebiges Widget einen Scrollbalken braucht, dann muss man das in ein `Canvas` stecken, denn *das* kann man mit Scrollbalken verbinden und ein beliebiges Widget da rein stecken.
„Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.“ — Brian W. Kernighan
NinoBaumann
User
Beiträge: 77
Registriert: Samstag 25. April 2020, 19:03

Hallo blackjack,

nein, Du hast es etwas falsch verstanden. Aber liegt wohl eher an meiner Beschreibung. Deshalb hier ein Link zu einem Bild, was meine Vorstellung verbildlicht.
https://ibb.co/kqMyzSG
Ich habe lediglich eine Hand voll Reiter. Aber innerhalb eines Reiters sind sehr viele kleinere Frames angeordnet. Und das soll dann praktisch über einen Scrollbar durchgescrollt werden können.

VG
Nino
Benutzeravatar
__blackjack__
User
Beiträge: 13572
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ah, okay. Die Antwort bleibt die gleiche: Sowas muss man sich in Tk mit einem `Canvas` selbst basteln. Die meisten anderen/modernen Rahmenwerke haben da bereits etwas fertiges.
„Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.“ — Brian W. Kernighan
Fire Spike
User
Beiträge: 329
Registriert: Montag 13. Mai 2019, 16:05
Wohnort: Erde

Canvas sollte gut funktionieren. Habe ich selbst so gemacht. Wenn du willst kann ich dir ein Beispiel geben.
NinoBaumann
User
Beiträge: 77
Registriert: Samstag 25. April 2020, 19:03

Hallo blackjack,

danke für die Info.

Hallo Fire Spike,

es würde ja dann im Endeffekt so funktionieren, dass ich meine Frames in das Canvas packe, dieses dann mit eine Scrollbar versehe und das ganze dann in meinen Reiter packe oder?
Gerne kannst Du mir als Hilfe ein Beispiel geben. Meine Frames habe ich halt mit der grid Methode angeordnet.

VG
Nino
Benutzeravatar
__blackjack__
User
Beiträge: 13572
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@NinoBaumann: Fast. Man würde eher nicht die Frames in ein `Canvas` packen, sondern in einen Frame und *den* dann in das `Canvas`-Widget.
„Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.“ — Brian W. Kernighan
Fire Spike
User
Beiträge: 329
Registriert: Montag 13. Mai 2019, 16:05
Wohnort: Erde

Code: Alles auswählen

class ScrollableFrame(ttk.Frame):
    def __init__(self, master, **kwargs) -> None:
        super().__init__(master, **kwargs)
        self.canvas = tk.Canvas(self, borderwidth=0, background="#fafafa", **kwargs)
        self.canvas.grid(row=0, column=0, sticky=NSEW)

        self.scrollbar_horizontal = ttk.Scrollbar(self, orient=HORIZONTAL, command=self.canvas.xview)
        self.scrollbar_horizontal.grid(row=1, column=0, columnspan=1, sticky=EW)

        self.scrollbar_vertical = ttk.Scrollbar(self, orient=VERTICAL, command=self.canvas.yview)
        self.scrollbar_vertical.grid(row=0, column=1, sticky=NS)

        self.canvas.configure(xscrollcommand=self.scrollbar_horizontal.set, yscrollcommand=self.scrollbar_vertical.set)

        self.scrolled_frame = ttk.Frame(self.canvas)

        self.canvas.create_window((0, 0), window=self.scrolled_frame, anchor=NW)
        self.scrolled_frame.bind("<Configure>", self.on_configure)

    def on_configure(self, event) -> None:
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

class ScrollableFrameHorizontal(ttk.Frame):
    def __init__(self, master, **kwargs) -> None:
        super().__init__(master, **kwargs)
        self.canvas = tk.Canvas(self, borderwidth=0, background="#fafafa", **kwargs)
        self.canvas.grid(row=0, column=0, sticky=NSEW)

        self.scrollbar_horizontal = ttk.Scrollbar(self, orient=HORIZONTAL, command=self.canvas.xview)
        self.scrollbar_horizontal.grid(row=1, column=0, columnspan=1, sticky=EW)

        self.canvas.configure(xscrollcommand=self.scrollbar_horizontal.set)

        self.scrolled_frame = ttk.Frame(self.canvas)

        self.canvas.create_window((0, 0), window=self.scrolled_frame, anchor=NW)
        self.scrolled_frame.bind("<Configure>", self.on_configure)

    def on_configure(self, event) -> None:
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))
Zwei Klassen. Die eine scrollt horizontal die andere vertikal. Scrollbars werden direkt miterstellt.

Verwenden kannst du sie so:

Code: Alles auswählen

scrollable_frame = ScrollableFrame(self, width=900, height=750)
scrollable_frame = scrollable_frame.scrolled_frame
Antworten