muhammeteminturgut / ttkscrollablenotebook Goto Github PK
View Code? Open in Web Editor NEWHorizontal Scrollable Tabs For Tkinter's Notebook Widget
License: GNU General Public License v3.0
Horizontal Scrollable Tabs For Tkinter's Notebook Widget
License: GNU General Public License v3.0
I am new to python and tkinter, I found it useful in my code to change the geometry manager of youre library from pack to grid.
posting it here, maybe it would help someone.
from tkinter import *
from tkinter import ttk
class ScrollableNotebook(ttk.Frame):
def __init__(self, parent, wheelscroll=True, tabmenu=True, *args, **kwargs):
super().__init__(master=parent)
self.grid(row=2, column=1, columnspan=2, sticky="nswe")
self.grid_rowconfigure(0, weight=1)
self.configure(style='Custom.TFrame')
self.xLocation = 0
self.notebookContent = ttk.Notebook(self)
self.notebookContent.grid(sticky="nswe")
self.notebookTab = ttk.Notebook(self)
self.notebookTab.bind("<<NotebookTabChanged>>", self._tabChanger)
if wheelscroll:
self.notebookTab.bind("<MouseWheel>", self._wheelscroll)
slide_frame = ttk.Frame(self)
self.menuSpace = 50
if tabmenu:
self.menuSpace = 50
bottom_tab = ttk.Label(slide_frame, text="\u2630")
bottom_tab.bind("<ButtonPress-1>", self._bottomMenu)
left_arrow = ttk.Label(slide_frame, text=" \u276E")
left_arrow.bind("<ButtonPress-1>", self._leftSlideStart)
left_arrow.bind("<ButtonRelease-1>", self._slideStop)
right_arrow = ttk.Label(slide_frame, text=" \u276F")
right_arrow.bind("<ButtonPress-1>", self._rightSlideStart)
right_arrow.bind("<ButtonRelease-1>", self._slideStop)
self.notebookContent.bind("<Configure>", self._resetSlide)
self.contentsManaged = []
slide_frame.grid(row=0, column=1, sticky="nswe")
bottom_tab.grid(row=0, column=1)
left_arrow.grid(row=0, column=0)
right_arrow.grid(row=0, column=2)
def _wheelscroll(self, event):
if event.delta > 0:
self._leftSlide(event)
else:
self._rightSlide(event)
def _bottomMenu(self, event):
tab_list_menu = Menu(self, tearoff=0)
for tab in self.notebookTab.tabs():
tab_list_menu.add_command(label=self.notebookTab.tab(tab, option="text"),
command=lambda temp=tab: self.select(temp))
try:
tab_list_menu.tk_popup(event.x_root, event.y_root)
finally:
tab_list_menu.grab_release()
def _tabChanger(self, event):
try:
self.notebookContent.select(self.notebookTab.index("current"))
except:
print("problem")
def _rightSlideStart(self, event=None):
if self._rightSlide(event):
self.timer = self.after(100, self._rightSlideStart)
def _rightSlide(self, event):
if self.notebookTab.winfo_width() > self.notebookContent.winfo_width() - self.menuSpace:
if (self.notebookContent.winfo_width() - (
self.notebookTab.winfo_width() + self.notebookTab.winfo_x())) <= self.menuSpace + 5:
self.xLocation -= 20
self.notebookTab.place(x=self.xLocation, y=0)
return True
return False
def _leftSlideStart(self, event=None):
if self._leftSlide(event):
self.timer = self.after(100, self._leftSlideStart)
def _leftSlide(self, event):
if not self.notebookTab.winfo_x() == 0:
self.xLocation += 20
self.notebookTab.place(x=self.xLocation, y=0)
return True
return False
def _slideStop(self, event):
if self.timer is not None:
self.after_cancel(self.timer)
self.timer = None
def _resetSlide(self, event=None):
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)
self.contentsManaged.append(frame)
def forget(self, tab_id):
index = self.notebookTab.index(tab_id)
self.notebookContent.forget(self.__ContentTabID(tab_id))
self.notebookTab.forget(tab_id)
self.contentsManaged[index].destroy()
self.contentsManaged.pop(index)
def hide(self, tab_id):
self.notebookContent.hide(self.__ContentTabID(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 __ContentTabID(self, tab_id):
return self.notebookContent.tabs()[self.notebookTab.tabs().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(self.__ContentTabID(tab_id))
self.notebookTab.select(tab_id)
def tab(self, tab_id, option=None, **kwargs):
kwargs_content = kwargs.copy()
kwargs_content["text"] = "" # important
self.notebookContent.tab(self.__ContentTabID(tab_id), option=None, **kwargs_content)
return self.notebookTab.tab(tab_id, option=None, **kwargs)
def tabs(self):
# return self.notebookContent.tabs()
return self.notebookTab.tabs()
def enable_traversal(self):
self.notebookContent.enable_traversal()
self.notebookTab.enable_traversal()
Greetings,
I was trying to use ttkScrollableNotebook in my application. I didn't want to keep adjusting the size of the program to accommodate new tabs so I saw the reference to your library on Stack Overflow.
For the most part the use is almost the same as the original. I included your file and changed the name from notebook to scrollablenotebook and it seemed to work.
The issue I am running into is that when you call the select method without a parameter (tab id) it is suppose to return the current tab_id. Since this is a feature that my program uses I was wondering if this is something you planned to look into.
Thanks in advance.
Peter Hedlund.
Hi,
is possible to add to your code the button features from this Custom notebook? unfortunately i am not that good
the source is this https://stackoverflow.com/a/39459376
thanks
When I create a tab it gives the error:
Traceback (most recent call last):
File "/Users/joshyacktman/Documents/PyApps/Working/JDE/test2.py", line 155, in <module>
notebook.add(text_piece, text="Untitled")
File "/Users/joshyacktman/Documents/PyApps/Working/JDE/ttkScrollableNotebook.py", line 67, in add
if len(self.notebookTab.winfo_children())!=0:
AttributeError: 'CustomNotebook' object has no attribute 'notebookTab'
What should I do?
If you use the clam theme on Macosx and create a few tabs the buttons will not cover the entire tab and it looks kind of janky.
Code:
from tkinter import Tk, Text
from tkinter import ttk
from ScrollableNotebook import ScrollableNotebook
root = Tk()
root.title("Scrollable Notebook")
root.geometry("500x500")
s = ttk.Style()
s.theme_use("clam")
counter = 0
notebook = ScrollableNotebook(root, tabmenu=True, wheelscroll=True)
notebook.pack(fill="both", expand=True)
frames = []
def remove_tab():
current_index = notebook.index("current")
notebook.forget(notebook.tabs()[current_index])
try:
frames[current_index-1].focus_set()
except:
pass
def add_tab():
global counter
frame = Text(notebook)
frame.pack(fill="both", expand=True)
frames.append(frame)
notebook.add(frame, text="Tab {}".format(counter))
tabid = notebook.tabs()[-1]
notebook.select(tabid)
frames[-1].focus_set()
counter += 1
ttk.Button(root, text="Remove", command=remove_tab).pack()
ttk.Button(root, text="Add", command=add_tab).pack()
add_tab()
frames[0].focus_set()
def focus_current(event):
current_focus = notebook.index("current")
frames[current_focus].focus_set()
root.bind("<Button-1>", focus_current)
root.mainloop()
Image:
Gives error:
Traceback (most recent call last):
File "/Users/Moosems/Desktop/JDE/JDE.py", line 72, in <module>
main_notebook.tab(text_boxes[current_focus], text="Untitled.py")
File "/Users/Moosems/Desktop/JDE/ttkScrollableNotebook.py", line 202, in tab
self.notebookContent.tab(self.__ContentTabID(tab_id), option=None, **kwargs_Content)
File "/Users/Moosems/Desktop/JDE/ttkScrollableNotebook.py", line 189, in __ContentTabID
return self.notebookContent.tabs()[self.notebookTab.tabs().index(tab_id)]
ValueError: tuple.index(x): x not in tuple
Hi,
Congratulations for the version of ttk.Notebook, you save my life ;)
While replace my old code with ScrollableNotebook, I notice some issues.
The first is because tkinter does not give the same tab_id for notebookContent and notebookTab
Also _tabChanger crash after hiding all my tabs (I'm reusing tabs)
I also find very useful to bind mouse scroll to move the tabs
self.notebookTab.bind("", self._wheelscroll)
To overcome issues I manage to create a function that gives the correct tab_id.
Please review my changes in the code and let me know if they are useful to you
# -*- 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)
self.notebookTab.bind("<MouseWheel>", self._wheelscroll)
slideFrame = ttk.Frame(self)
slideFrame.place(relx=1.0, x=0, y=1, anchor=NE)
leftArrow = ttk.Label(slideFrame, text=" ❮")
leftArrow.bind("<1>",self._leftSlide)
leftArrow.pack(side=LEFT)
rightArrow = ttk.Label(slideFrame, text=" ❯ ")
rightArrow.bind("<1>",self._rightSlide)
rightArrow.pack(side=RIGHT)
self.notebookContent.bind("<Configure>", self._resetSlide)
def _wheelscroll(self, event):
if event.delta > 0:
self._leftSlide(event)
else:
self._rightSlide(event)
def _tabChanger(self,event):
try: self.notebookContent.select(self.notebookTab.index("current"))
except: pass
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=None):
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(self.__ContentTabID(tab_id))
self.notebookTab.forget(tab_id)
def hide(self,tab_id):
self.notebookContent.hide(self.__ContentTabID(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 __ContentTabID(self,tab_id):
return self.notebookContent.tabs()[self.notebookTab.tabs().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(self.__ContentTabID(tab_id))
self.notebookTab.select(tab_id)
def tab(self,tab_id, option=None, **kwargs):
kwargs_Content = kwargs.copy()
kwargs_Content["text"] = "" # important
self.notebookContent.tab(self.__ContentTabID(tab_id), option=None, **kwargs_Content)
return self.notebookTab.tab(tab_id, option=None, **kwargs)
def tabs(self):
## return self.notebookContent.tabs()
return self.notebookTab.tabs()
def enable_traversal(self):
self.notebookContent.enable_traversal()
self.notebookTab.enable_traversal()
Hi I have been able to, implement your code to mine, and in addition add it, the buttons to close tab, that we have answer in a photo.
And I have a problem closing, the pestles, when I change the name of the tab, I see that they are created, some nameless windows, nothing like it comes out in the image
when in the pricipal window of workspapce, open DEVIATIONS, with this code:
def open issuesDeviation (self): global deviation deviation = Deviation (self.notebook) self.cuaderno.add (deviation, text= 'Issues DEVIATIONS') deviting.clean_Widgets ()
self.notebook, call your code:
self.notebook = ScrollableNotebook( self.root, tabmenu = True) self.containedor= ttk.Frame (self.notebook) self.containedor.columnconfigure (1, weight= 1) self.containedor.rowconfigure (1, weight= 1) self.logbook .add(self.container, text='WorkSpace ', underline= 0) self.pack .pack (fill="both ", expand=True)
when I open, the option menu, and I choose an option I change, the name of the tab, the function to the call is:
def change_NamePestana (self, customer): app.cuaderno.tab( idOpenTab, option = None, text = 'DEVIATIONS: {}' .format (customer))
then, when I close the tabs, with the X button on the tab, I close it but, as you see these tabs are created without names and it is annoying, some suggestion, you have passed the code, yours and the one to close the tabs, with stylo and that as I have ..
`
> # -*- coding: utf-8 -*-
>
> # Copyright (c) Muhammet Emin TURGUT 2020
> # For license see LICENSE
> from os import sendfile
> from tkinter import *
> from tkinter import ttk
> import tkinter as tk
> import tkinter.font as tkFont
> id_tab = 0
> class ScrollableNotebook(ttk.Frame):
> _initialized = False
> def __init__(self,parent,wheelscroll=False,tabmenu=False,*args,**kwargs):
> ttk.Frame.__init__(self, parent, *args)
> if not self._initialized:
> self._initialize()
> self._inititialized = True
> kwargs["style"] = "ScrollableNotebook"
> self._active = None
> 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)
> if wheelscroll==True: self.notebookTab.bind("<MouseWheel>", self._wheelscroll)
> slideFrame = ttk.Frame(self)
> slideFrame.place(relx=1.0, x=0, y=1, anchor=NE)
> self.menuSpace=30
> if tabmenu==True:
> self.menuSpace=50
> bottomTab = ttk.Label(slideFrame,
> text=" \u2630 ",
> background='red',
> width=4,
> anchor='center'
> )
> bottomTab.bind("<1>",self._bottomMenu)
> bottomTab.pack(side=RIGHT, ipady=10)
> leftArrow = ttk.Label(slideFrame, text=" \u276E")
> leftArrow.bind("<1>",self._leftSlide)
> leftArrow.pack(side=LEFT)
> rightArrow = ttk.Label(slideFrame, text=" \u276F")
> rightArrow.bind("<1>",self._rightSlide)
> rightArrow.pack(side=LEFT)
> self.notebookContent.bind("<Configure>", self._resetSlide)
> self.notebookTab.bind("<ButtonPress-1>", self.on_tab_close_press, True)
> self.notebookTab.bind("<ButtonRelease-1>", self.on_tab_close_release)
>
> def on_tab_close_press(self, event):
> name = self.identify(event.x, event.y)
> if name == "tab_btn_close":
> index = self.index("@%d,%d" % (event.x, event.y))
> self.state(['pressed'])
> self._active = index
>
> def on_tab_close_release(self, event):
> if not self.instate(['pressed']):
> return None
> name = self.identify(event.x, event.y)
> print(name)
> if name == "tab_btn_close":
> index = self.index("@%d,%d" % (event.x, event.y))
> if index != 0:
> if self._active == index:
> print('id al cerrar : <<{}>>'.format(index))
> self.forget(index)
> self.event_generate("<<NotebookTabClosed>>")
> self.state(["!pressed"])
> self._active = None
>
> def _initialize(self):
> self.style = ttk.Style()
> self.images = (
> tk.PhotoImage("im1", data='''
> R0lGODlhCAAIAMIEAAAAAP/SAP/bNNnZ2f///////////////yH5
> BAEKAAIALAAAAAAIAAgAAAMUCCAsCmO5OBVl8OKhoV3e9jQOkAAAOw==
> '''),
> tk.PhotoImage("im2", data='''
> R0lGODlhCAAIAMIEAAAAAP/SAP/bNNnZ2f///////////////yH5
> BAEKAAMALAAAAAAIAAgAAAMPCDA8+gw+GGlVbWKqmwMJADs=
> ''' ),
> tk.PhotoImage("im3", data='''
> R0lGODlhCAAIAMIEAAAAAP/SAP/bNNnZ2f///////////////yH5B
> AEKAAMALAAAAAAIAAgAAAMPGDE8+gw+GGlVbWKqmwsJADs=
> ''')
> )
> self.style.element_create("tab_btn_close", "image", "im1",
> ("active", "pressed", "!disabled", "im2"),
> ("active", "!disabled", "im3"), border=8, sticky='')
> self.style.layout("ScrollableNotebook", [("ScrollableNotebook.client", {"sticky": "nswe"})])
> self.style.layout("ScrollableNotebook.Tab", [
> ("ScrollableNotebook.tab", {
> "sticky": "nswe",
> "children": [
> ("ScrollableNotebook.padding", {
> "side": "top",
> "sticky": "nswe",
> "children": [
> ("ScrollableNotebook.focus", {
> "side": "top",
> "sticky": "nswe",
> "children": [
> ("ScrollableNotebook.label", {"side": "left", "sticky": ''}),
> ("ScrollableNotebook.tab_btn_close", {"side": "left", "sticky": ''}),
> ]
> })
> ]
> })
> ]
> })
> ])
> self.style.configure('ScrollableNotebook',
> background='#082032'
> )
> self.style.configure("ScrollableNotebook.Tab",
> background='#FDD2BF',
> foreground='#012443',
> padding=[20, 10],
> )
> self.style.map('ScrollableNotebook.Tab', background = [("selected", "#B61919"),
> ("active", "#FF6B6B")],
> foreground = [("selected", "#ffffff"),
> ("active", "#012443")]
> )
>
> def _wheelscroll(self, event):
> if event.delta > 0:
> self._leftSlide(event)
> else:
> self._rightSlide(event)
>
> def _bottomMenu(self,event):
> self.text_font = tkFont.Font(family='Consolas', size=13)
> tabListMenu = Menu(self, tearoff = 0)
> for tab in self.notebookTab.tabs():
> tabListMenu.add_command(label=self.notebookTab.tab(tab, option="text"),
> command= lambda temp=tab: self.select(temp),
> background='#ccffff',
> foreground='black',
> font=self.text_font,
> activebackground='#004c99',
> activeforeground='white')
> try:
> tabListMenu.tk_popup(event.x_root, event.y_root)
> finally:
> tabListMenu.grab_release()
>
> def _tabChanger(self,event):
> try:
> self.notebookContent.select(self.notebookTab.index("current"))
> # tab_id = self.notebookTab.index("current")
> # print('id al cambiar : --> ',self.notebookTab.index(self.notebookTab.select()))
> # global id_tab
> # id_tab = self.notebookTab.index(self.notebookTab.select())
> # return tab_id
> except: pass
>
> def _rightSlide(self,event):
> if self.notebookTab.winfo_width()>self.notebookContent.winfo_width()-self.menuSpace:
> if (self.notebookContent.winfo_width()-(self.notebookTab.winfo_width()+self.notebookTab.winfo_x()))<=self.menuSpace+5:
> 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(self.__ContentTabID(tab_id))
> self.notebookTab.forget(tab_id)
>
> def hide(self,tab_id):
> #self.notebookContent.hide(self.__ContentTabID(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 __ContentTabID(self,tab_id):
> return self.notebookContent.tabs()[self.notebookTab.tabs().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(self.__ContentTabID(tab_id))
> self.notebookTab.select(tab_id)
>
> def tab(self,tab_id, option=None, **kwargs):
> kwargs_Content = kwargs.copy()
> print(kwargs_Content)
> kwargs_Content["text"] = "" # important
> self.notebookContent.tab(self.__ContentTabID(tab_id), option=None, **kwargs_Content)
> return self.notebookTab.tab(tab_id, option=None, **kwargs)
>
> def tabs(self):
> #return self.notebookContent.tabs()
> return self.notebookTab.tabs()
>
> def enable_traversal(self):
> self.notebookContent.enable_traversal()
> self.notebookTab.enable_traversal()
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.