Skip to main content

A flexible and extensible tab manager widget for tkinter applications built on top of ttk.Notebook

Project description

PyPI version Python License

FlexTabs

A powerful and flexible tab management library for Python tkinter applications that extends ttk.Notebook with advanced features like dynamic tab opening/closing, multiple opener styles, keyboard shortcuts, icon support, and state retention.

Table of Contents

Features

Core Features

  • Dynamic Tab Management: Open and close tabs programmatically with state retention
  • Multiple Opener Styles: Toolbar, Sidebar, and Menu-based tab openers
  • Flexible Close Modes: Control how and when tabs can be closed
  • Icon Support: Full icon support for both tab openers and notebook tabs (images + emoji/text fallbacks)
  • Keyboard Shortcuts: Built-in navigation shortcuts plus custom tab shortcuts
  • State Management: Automatic tab state tracking and restoration
  • Event System: Comprehensive callbacks for tab lifecycle events
  • Toast Notifications: Built-in notification system for user feedback
  • Runtime Configuration: Change settings and add/remove tabs at runtime

Advanced Features

  • Smart Refresh: Efficient UI updates that preserve layout and state
  • Multiple Close Confirmation Types: None, Yes/No, Warning, or Info dialogs
  • Unclosable Tabs: Mark tabs as permanent with visual indicators
  • Icon Caching: Automatic icon loading and caching for performance
  • Tooltip Support: Rich tooltips for tab openers
  • Error Handling: Robust error handling with user-friendly notifications

Installation

pip install flextabs

Or clone the repository:

git clone https://github.com/MS-32154/flextabs.git
cd flextabs
pip install -e .

Dependencies:

  • Python 3.8+
  • tkinter (usually included with Python)
  • Pillow (PIL) for image icon support

Running the Demo

python3 -m flextabs

Quick Start

import tkinter as tk
from tkinter import ttk
from flextabs import TabManager, TabConfig, TabContent

# Create your tab content classes
class HomeTabContent(TabContent):
    def setup_content(self):
        ttk.Label(self.frame, text="Welcome to the Home tab!").pack(pady=20)

class SettingsTabContent(TabContent):
    def setup_content(self):
        ttk.Label(self.frame, text="Settings Configuration").pack(pady=20)
        # Add a close button
        self.manager().add_close_button(self.frame, self.tab_id).pack(pady=10)

# Create the main window
root = tk.Tk()
root.title("FlexTabs Demo")
root.geometry("800x600")

# Define tab configurations
tab_configs = [
    TabConfig(
        id="home",
        title="Home",
        content_class=HomeTabContent,
        icon="🏠",  # Emoji icon
        tooltip="Go to home page",
        closable=False  # This tab cannot be closed
    ),
    TabConfig(
        id="settings",
        title="Settings",
        content_class=SettingsTabContent,
        icon="⚙️",
        tooltip="Application settings",
        keyboard_shortcut="<Control-s>"
    )
]

# Create the tab manager
tab_manager = TabManager(
    parent=root,
    tab_configs=tab_configs,
    opener_type="sidebar",  # or "toolbar", "menu"
    opener_config={
        "position": "left",
        "width": 150,
        "title": "Navigation"
    }
)
tab_manager.pack(fill=tk.BOTH, expand=True)

# Open the home tab by default
tab_manager.open_tab("home")

root.mainloop()

Core Components

TabConfig

Defines the configuration for each tab:

TabConfig(
    id="unique_id",              # Required: Unique identifier
    title="Tab Title",           # Required: Display title
    content_class=YourContent,   # Required: TabContent subclass
    icon="🏠",                   # Optional: Icon (emoji, text, or file path)
    tooltip="Helpful text",      # Optional: Tooltip text
    closable=True,               # Optional: Whether tab can be closed
    keyboard_shortcut="<Control-t>",  # Optional: Keyboard shortcut
    data={"key": "value"}        # Optional: Custom data dictionary
)

Icon Support

FlexTabs supports multiple icon types:

# Emoji/text icons (≤4 characters)
icon="🏠"
icon="⚙️"
icon="📊"

# File paths to images (PNG, JPEG, etc.)
icon="/path/to/icon.png"
icon="resources/settings.ico"

# Context-specific icons
icon={
    "opener": "🏠",              # Icon for tab opener
    "tab": "/path/to/home.png",  # Icon for notebook tab
    "default": "📄"              # Fallback
}

TabContent

Base class for all tab content. Inherit from this to create your tabs:

class MyTabContent(TabContent):
    def setup_content(self):
        """Required: Set up your tab's UI here"""
        ttk.Label(self.frame, text="My content").pack()

    def on_tab_focus(self):
        """Optional: Called when tab becomes active"""
        print(f"Tab {self.tab_id} focused")

    def on_tab_blur(self):
        """Optional: Called when tab loses focus"""
        print(f"Tab {self.tab_id} blurred")

    def on_tab_close(self) -> bool:
        """Optional: Called before closing. Return False to prevent."""
        return True  # Allow closing

    def cleanup(self):
        """Optional: Clean up resources"""
        super().cleanup()

TabManager

The main component that orchestrates everything:

TabManager(
    parent=root,                    # Parent widget
    tab_configs=[...],              # List of TabConfig objects
    opener_type="sidebar",          # "toolbar", "sidebar", or "menu"
    opener_config={},               # Opener-specific configuration
    close_button_style="right_click",  # "right_click", "double_click", "both"
    close_confirmation=False,       # Enable close confirmations
    close_confirmation_type="none", # "none", "yesno", "warning", "info"
    close_mode="active_only",       # "active_only", "any_visible", "both"
    enable_keyboard_shortcuts=True, # Enable built-in shortcuts
    show_notebook_icons=True,       # Show icons in notebook tabs
    notebook_icon_size=(16, 16),    # Icon size for notebook tabs
    **kwargs                        # Additional ttk.Frame options
)

Opener Types

Sidebar Opener

Creates a sidebar with navigation buttons:

opener_config = {
    "position": "left",      # "left" or "right"
    "width": 150,           # Sidebar width
    "title": "Navigation",  # Optional title
    "style": {},           # ttk.Frame styling
    "button_style": {},    # Button styling
    "show_icons": True,    # Show icons on buttons
    "icon_size": (16, 16), # Icon size
    "icon_position": "left" # "left", "right", "top", "bottom"
}

Toolbar Opener

Creates a horizontal or vertical toolbar:

opener_config = {
    "position": "top",        # "top", "bottom", "left", "right"
    "layout": "horizontal",   # "horizontal" or "vertical"
    "style": {},             # ttk.Frame styling
    "button_style": {},      # Button styling
    "show_icons": True,      # Show icons on buttons
    "icon_size": (16, 16),   # Icon size
    "icon_position": "left"  # Icon position relative to text
}

Menu Opener

Creates a menu in the application's menu bar:

opener_config = {
    "menu_title": "Tabs",    # Menu title in menu bar
    "show_icons": True,      # Show icons in menu items
    # Note: Icons are limited to emoji/text for menus
}

Close Modes

Control how tabs can be closed:

  • active_only: Only the currently active tab can be closed
  • any_visible: Any visible tab can be closed by clicking
  • both: Active tab closes normally, others require Ctrl+click

Keyboard Shortcuts

Built-in Shortcuts

  • Ctrl+W: Close current tab
  • Ctrl+Tab: Next tab
  • Ctrl+Shift+Tab: Previous tab
  • Ctrl+1 through Ctrl+9: Select tab by index

Custom Shortcuts

Add shortcuts to individual tabs:

TabConfig(
    id="settings",
    title="Settings",
    content_class=SettingsContent,
    keyboard_shortcut="<Control-comma>"  # Ctrl+,
)

Event Callbacks

Set up callbacks to respond to tab events:

def on_tab_opened(tab_id):
    print(f"Tab {tab_id} opened")

def on_tab_closed(tab_id):
    print(f"Tab {tab_id} closed")

def on_tab_switched(new_tab_id, old_tab_id):
    print(f"Switched from {old_tab_id} to {new_tab_id}")

def on_tab_error(tab_id, error):
    print(f"Error in tab {tab_id}: {error}")

tab_manager.on_tab_opened = on_tab_opened
tab_manager.on_tab_closed = on_tab_closed
tab_manager.on_tab_switched = on_tab_switched
tab_manager.on_tab_error = on_tab_error

Runtime Management

Tab Operations

# Open/close tabs
tab_manager.open_tab("settings")
tab_manager.close_tab("settings")

# Check tab status
is_open = tab_manager.is_tab_open("settings")
current_tab = tab_manager.get_current_tab()
open_tabs = tab_manager.get_open_tabs()

# Select tabs
tab_manager.select_tab("home")

# Close all tabs
closed_count = tab_manager.close_all_tabs()

# Get tab content instance
content = tab_manager.get_tab_content("settings")

Dynamic Configuration

# Add new tab at runtime
new_tab = TabConfig(
    id="reports",
    title="Reports",
    content_class=ReportsContent
)
tab_manager.add_tab_config(new_tab)

# Remove tab
tab_manager.remove_tab_config("reports")

# Change close mode
tab_manager.set_close_mode("any_visible")

Icon Management

# Refresh all icons (useful after changing icon files)
tab_manager.refresh_tab_icons()

# Update icon settings
tab_manager.set_notebook_icon_settings(
    show_icons=True,
    icon_size=(20, 20),
    fallback_icon_key="default"
)

tab_manager.set_opener_icon_settings(
    show_icons=True,
    icon_size=(18, 18),
    icon_position="top"
)

# Add custom fallback icons
tab_manager.add_fallback_icon("custom", "🔧")
available_icons = tab_manager.get_available_fallback_icons()

Notifications

Show toast notifications to users:

# Basic notification
tab_manager.show_notification("Tab opened successfully")

# Styled notifications
tab_manager.show_notification(
    "Settings saved!",
    toast_type="success",  # "info", "warning", "error", "success"
    duration=3000  # milliseconds
)

Advanced Examples

Custom Tab with Complex UI

class DataAnalysisTab(TabContent):
    def setup_content(self):
        # Create a complex interface
        main_frame = ttk.Frame(self.frame)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # Toolbar
        toolbar = ttk.Frame(main_frame)
        toolbar.pack(fill=tk.X, pady=(0, 10))

        ttk.Button(toolbar, text="Load Data").pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(toolbar, text="Export").pack(side=tk.LEFT, padx=(0, 5))

        # Content area with notebook
        content_nb = ttk.Notebook(main_frame)
        content_nb.pack(fill=tk.BOTH, expand=True)

        # Data tab
        data_frame = ttk.Frame(content_nb)
        content_nb.add(data_frame, text="Raw Data")

        # Charts tab
        chart_frame = ttk.Frame(content_nb)
        content_nb.add(chart_frame, text="Charts")

    def on_tab_focus(self):
        # Refresh data when tab becomes active
        self.refresh_data()

    def on_tab_close(self):
        # Ask user to save unsaved changes
        if self.has_unsaved_changes():
            from tkinter import messagebox
            result = messagebox.askyesnocancel(
                "Unsaved Changes",
                "Save changes before closing?",
                parent=self.frame
            )
            if result is None:  # Cancel
                return False
            elif result:  # Yes
                self.save_changes()
        return True

    def refresh_data(self):
        # Implementation here
        pass

    def has_unsaved_changes(self):
        # Check for unsaved changes
        return False

    def save_changes(self):
        # Save changes
        pass

Multi-Window Application

class MultiWindowApp:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("Multi-Window App")

        # Create main tab manager
        self.main_tabs = TabManager(
            parent=self.root,
            tab_configs=self.get_main_tab_configs(),
            opener_type="toolbar",
            opener_config={"position": "top"}
        )
        self.main_tabs.pack(fill=tk.BOTH, expand=True)

        # Set up callbacks
        self.main_tabs.on_tab_opened = self.on_main_tab_opened

    def get_main_tab_configs(self):
        return [
            TabConfig("home", "Home", HomeTab, icon="🏠", closable=False),
            TabConfig("projects", "Projects", ProjectsTab, icon="📁"),
            TabConfig("settings", "Settings", SettingsTab, icon="⚙️")
        ]

    def on_main_tab_opened(self, tab_id):
        if tab_id == "projects":
            # When projects tab opens, populate with recent projects
            content = self.main_tabs.get_tab_content(tab_id)
            if content:
                content.load_recent_projects()

    def run(self):
        self.main_tabs.open_tab("home")
        self.root.mainloop()

Notebook Styling

FlexTabs uses ttk.Notebook internally, so all standard ttk styling applies:

import tkinter.ttk as ttk

# Create custom notebook style
style = ttk.Style()
style.configure("Custom.TNotebook",
               background="lightgray",
               tabmargins=[0, 5, 0, 0])
style.configure("Custom.TNotebook.Tab",
               padding=[20, 10],
               background="white")

tab_manager = TabManager(
    parent,
    tab_configs=tabs,
    notebook_config={
        "style": "Custom.TNotebook",  # Apply custom ttk style
        "padding": 5
    }
)

You can also access the underlying ttk.Notebook directly:

# Access the internal ttk.Notebook for advanced customization
internal_notebook = tab_manager.notebook
internal_notebook.configure(width=500, height=300)

Error Handling

FlexTabs includes comprehensive error handling:

# Errors are automatically caught and can be handled via callback
def handle_tab_error(tab_id, error):
    print(f"Error in tab {tab_id}: {error}")
    # Log to file, show user message, etc.

tab_manager.on_tab_error = handle_tab_error

Performance Tips

  1. Icon Preloading: Icons are automatically preloaded and cached
  2. Smart Refresh: UI updates use smart refresh to avoid recreating widgets unnecessarily
  3. Lazy Loading: Tabs are only created when first opened
  4. Memory Management: Proper cleanup prevents memory leaks

Best Practices

  1. Tab IDs: Use descriptive, unique IDs for all tabs
  2. Resource Cleanup: Always implement cleanup() in TabContent subclasses that use resources
  3. Error Handling: Implement robust error handling in your TabContent classes
  4. Icon Sizes: Use consistent icon sizes for better visual appearance
  5. Keyboard Shortcuts: Use standard shortcuts when possible (Ctrl+S for settings, etc.)

Examples

Complete Application Example

import tkinter as tk
from tkinter import ttk, messagebox
from flextabs import TabManager, TabConfig, TabContent

class HomeTab(TabContent):
    def setup_content(self):
        ttk.Label(self.frame, text="Welcome to the Home Tab!",
                 font=("TkDefaultFont", 16)).pack(pady=20)

        ttk.Button(self.frame, text="Open Settings",
                  command=lambda: self.get_manager().open_tab("settings")).pack(pady=5)

class SettingsTab(TabContent):
    def setup_content(self):
        ttk.Label(self.frame, text="Settings",
                 font=("TkDefaultFont", 14, "bold")).pack(pady=10)

        # Some settings widgets
        ttk.Checkbutton(self.frame, text="Enable notifications").pack(pady=2)
        ttk.Checkbutton(self.frame, text="Auto-save").pack(pady=2)

        # Add close button
        close_btn = self.get_manager().add_close_button(self.frame, self.tab_id)
        close_btn.pack(pady=10)

class DataTab(TabContent):
    def setup_content(self):
        self.data_modified = False

        ttk.Label(self.frame, text="Data Editor").pack(pady=10)

        self.text_area = tk.Text(self.frame, height=10, width=50)
        self.text_area.pack(pady=10, padx=20, fill=tk.BOTH, expand=True)
        self.text_area.bind('<KeyPress>', self.on_data_change)

    def on_data_change(self, event):
        self.data_modified = True

    def on_tab_close(self) -> bool:
        if self.data_modified:
            result = messagebox.askyesnocancel(
                "Unsaved Changes",
                "You have unsaved changes. Save before closing?",
                parent=self.frame
            )
            if result is None:  # Cancel
                return False
            elif result:  # Yes - save
                # Simulate saving
                self.get_manager().show_notification("Data saved!", "success")
        return True

def main():
    root = tk.Tk()
    root.title("FlexTabs Demo Application")
    root.geometry("900x600")

    # Define tabs
    tabs = [
        TabConfig("home", "Home", HomeTab,
                 tooltip="Application home page",
                 keyboard_shortcut="<Control-h>"),
        TabConfig("settings", "Settings", SettingsTab,
                 tooltip="Application settings",
                 closable=False),  # Can't be closed
        TabConfig("data", "Data Editor", DataTab,
                 tooltip="Edit your data here",
                 keyboard_shortcut="<Control-d>"),
    ]

    # Create tab manager with sidebar
    tab_manager = TabManager(
        root,
        tab_configs=tabs,
        opener_type="sidebar",
        opener_config={
            "position": "left",
            "width": 180,
            "title": "Navigation",
            "style": {"bg": "#f8f9fa"}
        },
        close_confirmation=True,
        close_confirmation_type="yesno",
        enable_keyboard_shortcuts=True
    )

    # Set up event handlers
    def on_tab_opened(tab_id):
        tab_manager.show_notification(f"Opened {tabs[0].title}", "info")

    tab_manager.on_tab_opened = on_tab_opened
    tab_manager.pack(fill=tk.BOTH, expand=True)

    # Open home tab by default
    tab_manager.open_tab("home")

    root.mainloop()

if __name__ == "__main__":
    main()

Dynamic Tab Management

import tkinter as tk
from flextabs import TabManager, TabConfig, TabContent

class DynamicContent(TabContent):
    def setup_content(self):
        data = self.config.data
        tk.Label(self.frame, text=f"Dynamic tab: {data.get('content', 'No content')}").pack()

def create_dynamic_tab(tab_manager, counter):
    """Create a new tab dynamically"""
    tab_id = f"dynamic_{counter}"
    config = TabConfig(
        id=tab_id,
        title=f"Dynamic {counter}",
        content_class=DynamicContent,
        data={"content": f"This is dynamic tab #{counter}"}
    )

    tab_manager.add_tab_config(config)
    tab_manager.open_tab(tab_id)

# Usage in your application
root = tk.Tk()
tab_manager = TabManager(root, tab_configs=[], opener_type="toolbar")

# Add button to create new tabs
counter = 1
def add_tab():
    global counter
    create_dynamic_tab(tab_manager, counter)
    counter += 1

tk.Button(root, text="Add Tab", command=add_tab).pack()
tab_manager.pack(fill=tk.BOTH, expand=True)

Styling and Customization

Custom Tooltip Styling

The library includes built-in tooltips that can be customized by modifying the TooltipWidget class or by styling the underlying tkinter widgets.

Toast Notifications

Built-in toast notification system with four types:

  • info (blue) - General information
  • warning (yellow) - Warnings
  • error (red) - Error messages
  • success (green) - Success messages

TTK Styling and Compatibility

FlexTabs preserves full compatibility with ttk.Notebook styling and behavior:

import tkinter.ttk as ttk

# All standard ttk.Notebook features work
style = ttk.Style()
style.configure("Custom.TNotebook", background="lightgray")
style.configure("Custom.TNotebook.Tab", padding=[20, 10])

# FlexTabs respects ttk themes
style.theme_use('clam')  # or 'alt', 'default', 'classic'

tab_manager = TabManager(
    parent,
    tab_configs=tabs,
    notebook_config={"style": "Custom.TNotebook"}
)

# Access underlying ttk.Notebook for direct manipulation
notebook = tab_manager.notebook
print(f"Current tab: {notebook.select()}")
print(f"All tabs: {notebook.tabs()}")

Migrating from ttk.Notebook

If you have existing ttk.Notebook code, migration is straightforward:

Before (ttk.Notebook):

notebook = ttk.Notebook(parent)
frame = ttk.Frame(notebook)
notebook.add(frame, text="My Tab")
# Content goes directly in frame

After (FlexTabs):

class MyTab(TabContent):
    def setup_content(self):
        # Content goes in self.frame
        pass

config = TabConfig("my_tab", "My Tab", MyTab)
tab_manager = TabManager(parent, [config], opener_type="toolbar")

Requirements

  • Python 3.8+
  • tkinter (usually included with Python)
  • Pillow (PIL) for image icon support

API Reference

For the complete API reference got to FlexTabs API Documentation or see the source code.

Enums

  • TabPosition: TOP, BOTTOM, LEFT, RIGHT
  • CloseMode: ACTIVE_ONLY, ANY_VISIBLE, BOTH
  • CloseConfirmationType: NONE, YESNO, WARNING, INFO

Classes

  • TabConfig: Tab configuration dataclass
  • TabContent: Base class for tab content
  • TabOpener: Base class for tab openers
  • TabManager: Main tab management widget
  • IconManager: Icon loading and caching
  • TooltipWidget: Tooltip implementation
  • ToastNotification: Notification system

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

Support

If you encounter any issues or have questions, please:

  1. Check the examples section
  2. Search existing GitHub Issues
  3. Create a new issue with:
    • Python version
    • Operating system
    • Minimal code example reproducing the issue
    • Full error traceback (if applicable)

Roadmap

  • Icon support for tabs and buttons
  • Drag and drop tab reordering
  • Tab groups and separators
  • Persistent tab state between sessions
  • Theme system
  • Animation effects
  • Tab overflow handling

FlexTabs – Extending ttk.Notebook for powerful and flexible tab management in Tkinter.

© 2025 MS-32154. All rights reserved.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

flextabs-0.2.0.tar.gz (39.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

flextabs-0.2.0-py3-none-any.whl (35.6 kB view details)

Uploaded Python 3

File details

Details for the file flextabs-0.2.0.tar.gz.

File metadata

  • Download URL: flextabs-0.2.0.tar.gz
  • Upload date:
  • Size: 39.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.5

File hashes

Hashes for flextabs-0.2.0.tar.gz
Algorithm Hash digest
SHA256 017ac2d078bb7e5e5403f812d701b2991b7373453f1c6b5402944435ad0bcdf5
MD5 74b58f6c28849522450a2934ed858db2
BLAKE2b-256 8efdd3e0c08d41c1f2d7ef1cd9da0921c63eb67b40cd9cce03d054286d3a143d

See more details on using hashes here.

File details

Details for the file flextabs-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: flextabs-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 35.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.5

File hashes

Hashes for flextabs-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 af727c536c3b4cca5ce068bb3924410143d79b4a8c20756736470e2cad89f2e8
MD5 0a11c82c29e0aca6c4463ca3b8229fd6
BLAKE2b-256 2c54ea6c4370b4ecf6a7329fe75706e46cd33c0968c447f13a5db2a9042467e4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page