3

I'm trying to create new tabs in my navigation bar notebook by clicking on the last tab. To further complicate the task my application is written with classes. My current and less elegant solution requires an Entry with a Button to create the new tab with the title entered in the Entry widget. Does anyone know a more elegant solution to my problem?

Heres my code:

import tkinter as tk
from tkinter import ttk

class MainApp(tk.Tk):
    """Main window class"""
    def __init__(self):
        super(MainApp, self).__init__()
        self.geometry("1000x1000")
        self.main_window = tk.Frame(self)
        self.main_window.pack(side="top", fill="both", expand=True)
        # saving all tabs & Frames to a dictionary to be able to access them later
        self.frames = {}
        self.tabs = {}
        # create a tab bar
        self.navbar = Navbar(self.main_window)
        # set the layout
        self.set_layout()

    def set_layout(self):
        """creates the default app layout"""
        self.add_tab("Settings") # first page
        self.add_tab("Case 1")

    def add_tab(self, title):
        """adds a new tab with name title to the navbar"""
        tab = ttk.Frame(self)
        self.navbar.add(tab, text=title)
        self.tabs[title] = tab
        # check if Settings or Case type Tab, to create MainInput frame with correct buttons
        if title.lower().find("setting") != -1:
            self.frames[title] = MainInput(self, tab, self.navbar, settings=True)
        else:
            self.frames[title] = MainInput(self, tab, self.navbar, case=True)
        self.navbar.pack(fill=tk.BOTH, expand=tk.YES)
        for tab in self.tabs:
            self.frames[tab].grid(sticky="nesw")

    def add_frame(self, title, frame):
        """adds the frame to frames dict with key title"""
        self.frames[title] = frame

class Navbar(ttk.Notebook):
    """returns a Notebook"""
    def __init__(self, parent):
        ttk.Notebook.__init__(self, parent)

    @staticmethod
    def delete(tab):
        """delete current tab"""
        tab.forget(tab.select())

class MainInput(tk.Frame):
    """The base frame of every tab"""
    def __init__(self, root, parent, notebook, settings=False, case=False):
        tk.Frame.__init__(self, parent)
        # Either build a settings or testcase tab
        if settings is True:
            SettingsField(root, parent)
        if case is True:
            CaseGeneral(parent, notebook)

class SettingsField(tk.Frame):
    """Creates a settings tab"""
    def __init__(self, root, parent):
        tk.Frame.__init__(self, parent)
        # add the "new tab" name entry and button
        tk.Label(parent, text="Add new Testcase:").grid(row=0, column=0, columnspan=2, sticky="w")
        tk.Label(parent, text="Name:").grid(row=1, column=0, sticky="w", padx=20)
        new_tab_title = tk.Entry(parent, textvariable=tk.StringVar(), width=30)
        new_tab_title.grid(row=1, column=1, columnspan=2, sticky="w")
        add_button = tk.Button(parent, text="add", command=lambda: [root.add_tab(new_tab_title.get())])
        add_button.grid(row=1, column=3, sticky="w", padx=5)

class CaseGeneral(tk.Frame):
    def __init__(self, parent, notebook):
        tk.Frame.__init__(self, parent)
        # create "delete current tab" buton
        tk.Label(parent, text="delete Testcase").grid(row=0, column=0)
        delete_button = tk.Button(parent, text="delete", command=lambda: [Navbar.delete(notebook)])
        delete_button.grid(row=0, column=1, sticky="w")

Images of the running code: Settings Tab Cases

Note: Please don't roast me since this is my first post here :)

KaaraT
  • 90
  • 6
  • If they get a new tab when clicking on the last tab, how can they switch to the last tab without creating a new tab? That sounds very confusing. – Bryan Oakley Apr 13 '22 at 15:01
  • My idea ist to have an extra tab with name/title "+" or "new tab" which will create a new tab before itself when clicked, just like in your browser. So the last tab is just a button to create a new tab. Hope this clarifies it and sorry for the confusion! – KaaraT Apr 13 '22 at 15:43
  • So, why don't you do exactly that? Always have one more tab at the end that is blank until the user selects it? – Bryan Oakley Apr 13 '22 at 16:01
  • Hmm that might actually be a bit more elegant than my current solution. Haven't thought of that, so thank you :) – KaaraT Apr 13 '22 at 16:18
  • You might want to take a look at [this answer](https://stackoverflow.com/questions/39458337/is-there-a-way-to-add-close-buttons-to-tabs-in-tkinter-ttk-notebook) which shows how to add a button to every tab. You can probably use a similar technique to add a button to the right of all the tabs. I think there may even be an answer that shows how to do that but I don't have a link for that. – Bryan Oakley Apr 13 '22 at 16:33
  • Thanks for the tip, I will definitely implement this for closing tabs! I will post an answer, if I can make adding a tab work this way. – KaaraT Apr 13 '22 at 16:56
  • You can do a lot of customization via the ttk styling engine. Unfortunately it is under-documented so it requires a bit of trial and error. – Bryan Oakley Apr 13 '22 at 17:05

1 Answers1

6

A really simple solution would be to create a binding on <<NotebookTabChanged>> that creates a new tab whenever the last tab on the row is selected. You can then create a tab with a "+" as the label and make sure it's the last tab.

Here's a simple example of the technique. It gives the new tab a label of <untitled>. Every time you click on the last tab it creates a new tab immediately before the last tab and then selects it.

import tkinter as tk
from tkinter import ttk

def handleTabChange(event):
    if notebook.select() == notebook.tabs()[-1]:
        index = len(notebook.tabs())-1
        frame = tk.Frame(notebook)
        notebook.insert(index, frame, text="<untitled>")
        notebook.select(index)

root = tk.Tk()
notebook = ttk.Notebook(root)
notebook.bind("<<NotebookTabChanged>>", handleTabChange)

notebook.pack(fill="both", expand=True)

# add a tab that creates new tabs when selected
frame = tk.Frame()
notebook.add(frame, text="+")

root.mainloop()

screenshot

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685