A flexible and extensible tab manager widget for tkinter applications built on top of ttk.Notebook
Project description
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
- Installation
- Running the Demo
- Quick Start
- Core Components
- Opener Types
- Close Modes
- Keyboard Shortcuts
- Event Callbacks
- Runtime Management
- Notifications
- Advanced Examples
- Error Handling
- Performance Tips
- Best Practices
- Examples
- Styling and Customization
- Requirements
- API Reference
- License
- Contributing
- Support
- Roadmap
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 closedany_visible: Any visible tab can be closed by clickingboth: Active tab closes normally, others require Ctrl+click
Keyboard Shortcuts
Built-in Shortcuts
Ctrl+W: Close current tabCtrl+Tab: Next tabCtrl+Shift+Tab: Previous tabCtrl+1throughCtrl+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
- Icon Preloading: Icons are automatically preloaded and cached
- Smart Refresh: UI updates use smart refresh to avoid recreating widgets unnecessarily
- Lazy Loading: Tabs are only created when first opened
- Memory Management: Proper cleanup prevents memory leaks
Best Practices
- Tab IDs: Use descriptive, unique IDs for all tabs
- Resource Cleanup: Always implement
cleanup()in TabContent subclasses that use resources - Error Handling: Implement robust error handling in your TabContent classes
- Icon Sizes: Use consistent icon sizes for better visual appearance
- 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 informationwarning(yellow) - Warningserror(red) - Error messagessuccess(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, RIGHTCloseMode: ACTIVE_ONLY, ANY_VISIBLE, BOTHCloseConfirmationType: NONE, YESNO, WARNING, INFO
Classes
TabConfig: Tab configuration dataclassTabContent: Base class for tab contentTabOpener: Base class for tab openersTabManager: Main tab management widgetIconManager: Icon loading and cachingTooltipWidget: Tooltip implementationToastNotification: 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.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
Support
If you encounter any issues or have questions, please:
- Check the examples section
- Search existing GitHub Issues
- 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
017ac2d078bb7e5e5403f812d701b2991b7373453f1c6b5402944435ad0bcdf5
|
|
| MD5 |
74b58f6c28849522450a2934ed858db2
|
|
| BLAKE2b-256 |
8efdd3e0c08d41c1f2d7ef1cd9da0921c63eb67b40cd9cce03d054286d3a143d
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
af727c536c3b4cca5ce068bb3924410143d79b4a8c20756736470e2cad89f2e8
|
|
| MD5 |
0a11c82c29e0aca6c4463ca3b8229fd6
|
|
| BLAKE2b-256 |
2c54ea6c4370b4ecf6a7329fe75706e46cd33c0968c447f13a5db2a9042467e4
|