2

I want to make a lot of notebook tabs, and I want to put them in canvas and to add a horizontal scrollbar so that I can scroll trough them. I set the canvas size, but canvas size keep changing when I add new tab. Also, scrollbar does not work, can you tell me what am I doing wrong?

The program does not show me any error. This is the code:

from tkinter import *
from tkinter import ttk

myApp = Tk()
myApp.title(" Program ")                         
myApp.geometry("900x500")

CanvasTabs = Canvas(myApp, width=50, height=50)
CanvasTabs.grid(row=0,column=0)

tabs = ttk.Notebook(CanvasTabs, width=100, height=100)

tab1 = ttk.Frame(tabs)
tabs.add(tab1,text="  Tab 1  ")

tab2 = ttk.Frame(tabs)
tabs.add(tab2,text="  Tab 2  ")

tab3 = ttk.Frame(tabs)
tabs.add(tab3,text="  Tab 3  ")

tab4 = ttk.Frame(tabs)
tabs.add(tab4,text="  Tab 4  ")


hbar=Scrollbar(CanvasTabs,orient=HORIZONTAL)
hbar.pack(side=TOP,fill=X)
hbar.config(command=CanvasTabs.xview)

CanvasTabs.config(xscrollcommand=hbar.set)


tabs.pack(expand=1, fill="both")   

myApp.mainloop()
taga
  • 3,537
  • 13
  • 53
  • 119
  • I believe the question on how to properly set up a scrollbar on a canvas to scroll over many widgets has been asked already several time. – Mike - SMT Jun 29 '18 at 16:17
  • @Mike-SMT I have read a lot of posts like that , and I didnt find the way to do this. – taga Jun 29 '18 at 16:28

2 Answers2

5

I code a widget to fix the problem. Here is a real solution: https://github.com/muhammeteminturgut/ttkScrollableNotebook

Demonstration

# -*- coding: utf-8 -*-

# Copyright (c) Muhammet Emin TURGUT 2020
# For license see LICENSE
from tkinter import *
from tkinter import ttk

class ScrollableNotebook(ttk.Frame):
    def __init__(self,parent,*args,**kwargs):
        ttk.Frame.__init__(self, parent, *args)
        self.xLocation = 0
        self.notebookContent = ttk.Notebook(self,**kwargs)
        self.notebookContent.pack(fill="both", expand=True)

        self.notebookTab = ttk.Notebook(self,**kwargs)
        self.notebookTab.bind("<<NotebookTabChanged>>",self._tabChanger)

        slideFrame = ttk.Frame(self)
        slideFrame.place(relx=1.0, x=0, y=1, anchor=NE)
        leftArrow = ttk.Label(slideFrame, text="\u25c0")
        leftArrow.bind("<1>",self._leftSlide)
        leftArrow.pack(side=LEFT)
        rightArrow = ttk.Label(slideFrame, text=" \u25b6")
        rightArrow.bind("<1>",self._rightSlide)
        rightArrow.pack(side=RIGHT)
        self.notebookContent.bind( "<Configure>", self._resetSlide)

    def _tabChanger(self,event):
        self.notebookContent.select(self.notebookTab.index("current"))

    def _rightSlide(self,event):
        if self.notebookTab.winfo_width()>self.notebookContent.winfo_width()-30:
            if (self.notebookContent.winfo_width()-(self.notebookTab.winfo_width()+self.notebookTab.winfo_x()))<=35:
                self.xLocation-=20
                self.notebookTab.place(x=self.xLocation,y=0)
    def _leftSlide(self,event):
        if not self.notebookTab.winfo_x()== 0:
            self.xLocation+=20
            self.notebookTab.place(x=self.xLocation,y=0)

    def _resetSlide(self,event):
        self.notebookTab.place(x=0,y=0)
        self.xLocation = 0

    def add(self,frame,**kwargs):
        if len(self.notebookTab.winfo_children())!=0:
            self.notebookContent.add(frame, text="",state="hidden")
        else:
            self.notebookContent.add(frame, text="")
        self.notebookTab.add(ttk.Frame(self.notebookTab),**kwargs)

    def forget(self,tab_id):
        self.notebookContent.forget(tab_id)
        self.notebookTab.forget(tab_id)

    def hide(self,tab_id):
        self.notebookContent.hide(tab_id)
        self.notebookTab.hide(tab_id)

    def identify(self,x, y):
        return self.notebookTab.identify(x,y)

    def index(self,tab_id):
        return self.notebookTab.index(tab_id)

    def insert(self,pos,frame, **kwargs):
        self.notebookContent.insert(pos,frame, **kwargs)
        self.notebookTab.insert(pos,frame,**kwargs)

    def select(self,tab_id):
        self.notebookContent.select(tab_id)
        self.notebookTab.select(tab_id)

    def tab(self,tab_id, option=None, **kwargs):
        return self.notebookTab.tab(tab_id, option=None, **kwargs)

    def tabs(self):
        return self.notebookContent.tabs()

    def enable_traversal(self):
        self.notebookContent.enable_traversal()
        self.notebookTab.enable_traversal()
3

Taking Bryan's example on this post and modifying it to include your Notebook code we get a functioning scrollbar that will allow you to scroll over your Notebook widget if it exceeds the limit of the window.

Bryan's example uses the pack() geometry manager however I personally find grid() easier to visualize so I replace pack with grid() in my example.

UPDATE:

import tkinter as tk
import tkinter.ttk as ttk


class Example(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.canvas = tk.Canvas(self, borderwidth=0)
        self.frame = tk.Frame(self.canvas)

        self.vsb = tk.Scrollbar(self, orient="horizontal", command=self.canvas.xview)
        self.vsb.grid(row=1, column=0, sticky="nsew")

        self.canvas.configure(xscrollcommand=self.vsb.set)
        self.canvas.grid(row=0, column=0, sticky="nsew")
        self.canvas.create_window((3,2), window=self.frame, anchor="nw", tags="self.frame")

        self.frame.bind("<Configure>", self.frame_configure)
        self.populate()

    def populate(self):
        tabs = ttk.Notebook(self.frame, width=100, height=100)
        for tab in range(50):
            tabs.add(ttk.Frame(tabs), text=" Tab {}  ".format(tab))
        tabs.grid(row=0, column=0, sticky="ew")


    def frame_configure(self, event):
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

if __name__ == "__main__":
    app = Example()
    app.mainloop()

Updated results:

enter image description here

Per your request in the comments here is a Non-OOP example:

import tkinter as tk
import tkinter.ttk as ttk


root = tk.Tk()
canvas = tk.Canvas(root, borderwidth=0)
frame = tk.Frame(canvas)

vsb = tk.Scrollbar(root, orient="horizontal", command=canvas.xview)
vsb.grid(row=1, column=0, sticky="nsew")

canvas.configure(xscrollcommand=vsb.set)
canvas.grid(row=0, column=0, sticky="nsew")
canvas.create_window((3,2), window=frame, anchor="nw", tags="frame")

tabs = ttk.Notebook(frame, width=100, height=100)
for tab in range(50):
    tabs.add(ttk.Frame(tabs), text=" Tab {}  ".format(tab))
tabs.grid(row=0, column=0, sticky="ew")

def frame_configure(event):
    global canvas
    canvas.configure(scrollregion=canvas.bbox("all"))

frame.bind("<Configure>", frame_configure)

root.mainloop()
Mike - SMT
  • 14,784
  • 4
  • 35
  • 79
  • Is there a way to make this without using class? When I set the program to full screen, the scrollbar disappears. Also, when I add more tabs, size of canvas is changed. I want to have fixed size of canvas, with a lot of tabs. – taga Jun 29 '18 at 17:00
  • The scrollbar goes away when its not needed. The canvas in my example is set to expand with the window if you put the canvas inside of a frame you can manage growth better. – Mike - SMT Jun 29 '18 at 17:30
  • @taga see my updated answer. We use weights to manage things like the resizing of widgets in a give row/column. – Mike - SMT Jun 29 '18 at 17:41
  • Thanks, and is there a way to do this without using class and `def __init_(self):` ? – taga Jun 29 '18 at 18:59
  • `def __init__(self):` is part of how a class initializes its content. Sure this can be done outside of a class however a class is the better chose. There are many reason to build in a class for maintenance and for manageability. – Mike - SMT Jun 29 '18 at 19:05
  • I understand that, I ask because i have more than 1000 lines of code for my tkinter project and those code is outside of the class, so implementing this would be problem because Its inside of the class, and this part of code (that you helped me with) is the final part of the project – taga Jun 29 '18 at 19:13
  • You actually can combine class data with non class data. That said converting what I have here to a non class form should not be hard. Just make sure that when you convert the methods back to non-class functions that you add the global statement for the global variables you need. I have a meeting to go to however I will convert this to a non-class example when I get back. – Mike - SMT Jun 29 '18 at 19:21
  • @taga I have added a Non-OOP example. – Mike - SMT Jun 29 '18 at 20:17