BeepTabs
Fully custom tab control with themed headers, closeable tabs, drag-and-drop reordering, and SVG close buttons
📑 Table of Contents
Overview
BeepTabs is a fully custom tab control that extends the standard TabControl with advanced features including themed headers, closeable tabs, drag-and-drop tab reordering, and flexible header positioning. Unlike traditional tab controls, BeepTabs provides complete custom rendering with owner-drawn headers and smooth anti-aliased graphics.
Built as an enhanced TabControl, BeepTabs features SVG close buttons, rounded corners, custom color schemes, and supports header positioning on all four sides (Top, Bottom, Left, Right). It includes comprehensive drag-and-drop functionality for tab reordering and integrates seamlessly with the Beep theming system.
Key Features
🎨 Custom Owner-Drawn Headers
Fully custom-rendered tab headers with anti-aliasing and smooth graphics
❌ Closeable Tabs
SVG close buttons on each tab with TabRemoved event notification
🔄 Drag-and-Drop Reordering
Full drag-and-drop support for tab reordering with visual feedback
📐 Flexible Header Positioning
Headers can be positioned on Top, Bottom, Left, or Right sides
🔘 Rounded Corner Design
Modern rounded corners on tab headers for professional appearance
🎭 Complete Theme Integration
Full integration with Beep theming system for consistent styling
📏 Dynamic Tab Sizing
Automatic tab sizing based on content with configurable min/max limits
♿ Accessibility Support
Built-in accessibility features for screen readers and keyboard navigation
Basic Usage
Simple Tab Control
// Create a basic BeepTabs control
var tabControl = new BeepTabs
{
Size = new Size(500, 400),
Location = new Point(10, 10),
HeaderPosition = TabHeaderPosition.Top,
HeaderHeight = 30
};
// Create and add tab pages
var tabPage1 = new TabPage("General")
{
BackColor = Color.White
};
var tabPage2 = new TabPage("Advanced")
{
BackColor = Color.White
};
var tabPage3 = new TabPage("Settings")
{
BackColor = Color.White
};
// Add content to tabs
var label1 = new Label { Text = "General Settings", Dock = DockStyle.Fill };
var label2 = new Label { Text = "Advanced Options", Dock = DockStyle.Fill };
var label3 = new Label { Text = "Configuration", Dock = DockStyle.Fill };
tabPage1.Controls.Add(label1);
tabPage2.Controls.Add(label2);
tabPage3.Controls.Add(label3);
// Add tabs to control
tabControl.TabPages.Add(tabPage1);
tabControl.TabPages.Add(tabPage2);
tabControl.TabPages.Add(tabPage3);
// Add to form
this.Controls.Add(tabControl);
Themed Tab Control with Event Handling
var tabControl = new BeepTabs
{
Theme = "DarkTheme",
HeaderPosition = TabHeaderPosition.Top,
HeaderHeight = 35,
Size = new Size(600, 450)
};
// Handle tab removal
tabControl.TabRemoved += (sender, e) => {
// Console.WriteLine($"Tab '{e.TabText}' was removed");
// Perform cleanup if needed
CleanupTabResources(e.TabText);
};
// Handle selection changes
tabControl.SelectedIndexChanged += (sender, e) => {
var selectedTab = tabControl.SelectedTab;
if (selectedTab != null)
{
// Console.WriteLine($"Selected tab: {selectedTab.Text}");
LoadTabContent(selectedTab);
}
};
// Add tabs dynamically
for (int i = 1; i <= 5; i++)
{
var tabPage = new TabPage($"Tab {i}");
var content = new Panel { Dock = DockStyle.Fill, BackColor = Color.LightBlue };
content.Controls.Add(new Label
{
Text = $"Content for Tab {i}",
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleCenter
});
tabPage.Controls.Add(content);
tabControl.TabPages.Add(tabPage);
}
Tab Control with Different Header Positions
// Create tabs with different header positions
var topTabs = new BeepTabs
{
HeaderPosition = TabHeaderPosition.Top,
HeaderHeight = 30,
Size = new Size(400, 300),
Location = new Point(10, 10)
};
var leftTabs = new BeepTabs
{
HeaderPosition = TabHeaderPosition.Left,
HeaderHeight = 150, // Width when vertical
Size = new Size(400, 300),
Location = new Point(420, 10)
};
var bottomTabs = new BeepTabs
{
HeaderPosition = TabHeaderPosition.Bottom,
HeaderHeight = 30,
Size = new Size(400, 300),
Location = new Point(10, 320)
};
var rightTabs = new BeepTabs
{
HeaderPosition = TabHeaderPosition.Right,
HeaderHeight = 150, // Width when vertical
Size = new Size(400, 300),
Location = new Point(420, 320)
};
// Add sample tabs to each
foreach (var tabs in new[] { topTabs, leftTabs, bottomTabs, rightTabs })
{
for (int i = 1; i <= 3; i++)
{
var tabPage = new TabPage($"Tab {i}");
var label = new Label
{
Text = $"Content {i}\nPosition: {tabs.HeaderPosition}",
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleCenter
};
tabPage.Controls.Add(label);
tabs.TabPages.Add(tabPage);
}
}
// Add all to form
this.Controls.AddRange(new Control[] { topTabs, leftTabs, bottomTabs, rightTabs });
BeepTabs automatically handles layout adjustments when switching header positions, making it easy to create flexible interfaces.
Header Positions
BeepTabs supports four different header positions for maximum layout flexibility:
📄 Top Headers
Traditional tab layout with headers above content
📋 Bottom Headers
Headers positioned below content area
📑 Left Headers
Vertical tab layout with headers on the left side
📄 Right Headers
Vertical tab layout with headers on the right side
Header Position Configuration
// Top headers (default)
var topHeaderTabs = new BeepTabs
{
HeaderPosition = TabHeaderPosition.Top,
HeaderHeight = 30 // Height of header area
};
// Bottom headers
var bottomHeaderTabs = new BeepTabs
{
HeaderPosition = TabHeaderPosition.Bottom,
HeaderHeight = 30 // Height of header area
};
// Left headers (vertical)
var leftHeaderTabs = new BeepTabs
{
HeaderPosition = TabHeaderPosition.Left,
HeaderHeight = 120 // Width of header area when vertical
};
// Right headers (vertical)
var rightHeaderTabs = new BeepTabs
{
HeaderPosition = TabHeaderPosition.Right,
HeaderHeight = 120 // Width of header area when vertical
};
// Dynamic header position change
private void ChangeHeaderPosition()
{
switch (currentPosition)
{
case TabHeaderPosition.Top:
tabControl.HeaderPosition = TabHeaderPosition.Right;
tabControl.HeaderHeight = 150;
break;
case TabHeaderPosition.Right:
tabControl.HeaderPosition = TabHeaderPosition.Bottom;
tabControl.HeaderHeight = 30;
break;
case TabHeaderPosition.Bottom:
tabControl.HeaderPosition = TabHeaderPosition.Left;
tabControl.HeaderHeight = 150;
break;
case TabHeaderPosition.Left:
tabControl.HeaderPosition = TabHeaderPosition.Top;
tabControl.HeaderHeight = 30;
break;
}
}
Properties
Core Properties
| Property | Type | Default | Description |
|---|---|---|---|
HeaderPosition |
TabHeaderPosition | Top | Position of tab headers (Top, Bottom, Left, Right) |
HeaderHeight |
int | 30 | Height of header area (width for vertical positions) |
Theme |
string | Default | Theme name for styling |
SelectTab |
TabPage | null | Currently selected tab page |
SelectTabByIndex |
int | -1 | Select tab by index (write-only) |
Inherited TabControl Properties
| Property | Type | Description |
|---|---|---|
TabPages |
TabPageCollection | Collection of tab pages |
SelectedTab |
TabPage | Currently selected tab page |
SelectedIndex |
int | Index of currently selected tab |
TabCount |
int | Number of tabs in the control |
TabHeaderPosition Enumeration
Top
Headers at the top
HeaderHeight = height of header area
Bottom
Headers at the bottom
HeaderHeight = height of header area
Left
Headers on the left (vertical)
HeaderHeight = width of header area
Right
Headers on the right (vertical)
HeaderHeight = width of header area
Methods
| Method | Parameters | Returns | Description |
|---|---|---|---|
ApplyTheme |
None | void | Apply current theme to all visual elements |
SuspendFormLayout |
None | void | Suspend layout updates for performance |
ResumeFormLayout |
None | void | Resume layout updates and refresh display |
ReceiveMouseClick |
Point clientLocation | void | Handle mouse click from parent control |
ReceiveMouseMove |
Point clientLocation | void | Handle mouse move from parent control |
Method Usage Examples
var tabControl = new BeepTabs();
// Apply theme changes
tabControl.Theme = "DarkTheme";
tabControl.ApplyTheme();
// Performance optimization during bulk updates
tabControl.SuspendFormLayout();
try
{
// Add multiple tabs
for (int i = 0; i < 10; i++)
{
var tabPage = new TabPage($"Tab {i}");
// Add content...
tabControl.TabPages.Add(tabPage);
}
}
finally
{
tabControl.ResumeFormLayout();
}
// Handle mouse events from parent container
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
// Forward click to tab control if within bounds
if (tabControl.Bounds.Contains(e.Location))
{
Point relativeLocation = new Point(
e.Location.X - tabControl.Left,
e.Location.Y - tabControl.Top
);
tabControl.ReceiveMouseClick(relativeLocation);
}
}
Events
| Event | Type | Description |
|---|---|---|
TabRemoved |
EventHandler<TabRemovedEventArgs> | Fired when a tab is closed via close button |
SelectedIndexChanged |
EventHandler | Fired when the selected tab changes |
DrawItem |
DrawItemEventHandler | Fired for custom tab drawing (internal use) |
Event Handling Examples
var tabControl = new BeepTabs();
// Handle tab removal
tabControl.TabRemoved += (sender, e) => {
// Console.WriteLine($"Tab '{e.TabText}' was removed");
// Perform cleanup
CleanupTabResources(e.TabText);
// Save state
SaveTabConfiguration();
// Check if no tabs remain
if (tabControl.TabCount == 0)
{
ShowWelcomeScreen();
}
};
// Handle tab selection changes
tabControl.SelectedIndexChanged += (sender, e) => {
var selectedTab = tabControl.SelectedTab;
if (selectedTab != null)
{
// Console.WriteLine($"Selected tab: {selectedTab.Text}");
// Update status bar
UpdateStatusBar($"Viewing: {selectedTab.Text}");
// Load tab-specific data
LoadTabContent(selectedTab);
// Update menu state
UpdateMenuForTab(selectedTab);
}
};
// Advanced tab management
private void CleanupTabResources(string tabText)
{
// Dispose of any resources associated with the tab
if (tabResources.ContainsKey(tabText))
{
var resources = tabResources[tabText];
resources.Dispose();
tabResources.Remove(tabText);
}
}
private void LoadTabContent(TabPage tabPage)
{
// Lazy load content when tab is selected
if (tabPage.Controls.Count == 0)
{
// Create and add content
var content = CreateContentForTab(tabPage.Text);
if (content != null)
{
tabPage.Controls.Add(content);
}
}
}
Examples
Document Editor with Closeable Tabs
public class DocumentEditor : Form
{
private BeepTabs documentTabs;
private Dictionary openDocuments;
public DocumentEditor()
{
InitializeDocumentTabs();
openDocuments = new Dictionary();
}
private void InitializeDocumentTabs()
{
documentTabs = new BeepTabs
{
Dock = DockStyle.Fill,
HeaderPosition = TabHeaderPosition.Top,
HeaderHeight = 32,
Theme = "DefaultTheme"
};
// Handle tab removal
documentTabs.TabRemoved += DocumentTabs_TabRemoved;
documentTabs.SelectedIndexChanged += DocumentTabs_SelectedIndexChanged;
this.Controls.Add(documentTabs);
}
public void OpenDocument(string filePath)
{
string fileName = Path.GetFileName(filePath);
// Check if document is already open
foreach (TabPage existingTab in documentTabs.TabPages)
{
if (existingTab.Text == fileName)
{
documentTabs.SelectedTab = existingTab;
return;
}
}
// Create new tab for document
var tabPage = new TabPage(fileName);
var textEditor = new RichTextBox
{
Dock = DockStyle.Fill,
Font = new Font("Consolas", 11f),
Text = File.ReadAllText(filePath)
};
// Track document state
var docData = new DocumentData
{
FilePath = filePath,
Editor = textEditor,
IsModified = false
};
textEditor.TextChanged += (s, e) => {
docData.IsModified = true;
UpdateTabTitle(tabPage, fileName);
};
tabPage.Controls.Add(textEditor);
documentTabs.TabPages.Add(tabPage);
documentTabs.SelectedTab = tabPage;
openDocuments[fileName] = docData;
}
private void DocumentTabs_TabRemoved(object sender, TabRemovedEventArgs e)
{
string fileName = e.TabText.TrimEnd('*'); // Remove modified indicator
if (openDocuments.ContainsKey(fileName))
{
var docData = openDocuments[fileName];
// Check for unsaved changes
if (docData.IsModified)
{
var result = MessageBox.Show(
$"Save changes to {fileName}?",
"Unsaved Changes",
MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Question);
switch (result)
{
case DialogResult.Yes:
SaveDocument(docData);
break;
case DialogResult.Cancel:
// Re-add the tab (user cancelled)
var tabPage = new TabPage(e.TabText);
tabPage.Controls.Add(docData.Editor);
documentTabs.TabPages.Add(tabPage);
return;
}
}
openDocuments.Remove(fileName);
}
// Show welcome screen if no documents open
if (documentTabs.TabCount == 0)
{
ShowWelcomeTab();
}
}
private class DocumentData
{
public string FilePath { get; set; }
public RichTextBox Editor { get; set; }
public bool IsModified { get; set; }
}
}
Settings Dialog with Multiple Categories
public class AdvancedSettingsDialog : Form
{
private BeepTabs settingsTabs;
public AdvancedSettingsDialog()
{
InitializeComponents();
CreateSettingsTabs();
}
private void InitializeComponents()
{
this.Text = "Advanced Settings";
this.Size = new Size(800, 600);
this.StartPosition = FormStartPosition.CenterParent;
}
private void CreateSettingsTabs()
{
settingsTabs = new BeepTabs
{
HeaderPosition = TabHeaderPosition.Left,
HeaderHeight = 180,
Theme = "DefaultTheme",
Dock = DockStyle.Fill
};
// Create setting categories
CreateGeneralTab();
CreateAdvancedTab();
CreateAppearanceTab();
CreateKeyboardTab();
CreatePluginsTab();
this.Controls.Add(settingsTabs);
}
private void CreateGeneralTab()
{
var tabPage = new TabPage("General Settings");
var panel = new Panel { Dock = DockStyle.Fill, Padding = new Padding(20) };
// Add general settings controls
var languageLabel = new Label { Text = "Language:", Location = new Point(0, 10), AutoSize = true };
var languageCombo = new ComboBox
{
Location = new Point(100, 7),
Width = 150,
Items = { "English", "Spanish", "French", "German" }
};
var startupCheckBox = new CheckBox
{
Text = "Start with Windows",
Location = new Point(0, 40),
AutoSize = true
};
var autoSaveCheckBox = new CheckBox
{
Text = "Auto-save documents",
Location = new Point(0, 70),
AutoSize = true
};
panel.Controls.AddRange(new Control[]
{
languageLabel, languageCombo,
startupCheckBox, autoSaveCheckBox
});
tabPage.Controls.Add(panel);
settingsTabs.TabPages.Add(tabPage);
}
private void CreateAdvancedTab()
{
var tabPage = new TabPage("Advanced Options");
var panel = new Panel { Dock = DockStyle.Fill, Padding = new Padding(20) };
// Add advanced settings
var debugCheckBox = new CheckBox
{
Text = "Enable debug mode",
Location = new Point(0, 10),
AutoSize = true
};
var cacheLabel = new Label { Text = "Cache size (MB):", Location = new Point(0, 40), AutoSize = true };
var cacheUpDown = new NumericUpDown
{
Location = new Point(120, 37),
Width = 80,
Minimum = 50,
Maximum = 1000,
Value = 200
};
panel.Controls.AddRange(new Control[] { debugCheckBox, cacheLabel, cacheUpDown });
tabPage.Controls.Add(panel);
settingsTabs.TabPages.Add(tabPage);
}
private void CreateAppearanceTab()
{
var tabPage = new TabPage("Appearance");
var panel = new Panel { Dock = DockStyle.Fill, Padding = new Padding(20) };
// Theme selection
var themeLabel = new Label { Text = "Theme:", Location = new Point(0, 10), AutoSize = true };
var themeCombo = new ComboBox
{
Location = new Point(60, 7),
Width = 150,
DropDownStyle = ComboBoxStyle.DropDownList,
Items = { "Light", "Dark", "Auto" }
};
panel.Controls.AddRange(new Control[] { themeLabel, themeCombo });
tabPage.Controls.Add(panel);
settingsTabs.TabPages.Add(tabPage);
}
private void CreateKeyboardTab()
{
var tabPage = new TabPage("Keyboard");
var panel = new Panel { Dock = DockStyle.Fill, Padding = new Padding(20) };
var shortcutsListView = new ListView
{
Dock = DockStyle.Fill,
View = View.Details,
FullRowSelect = true,
GridLines = true
};
shortcutsListView.Columns.Add("Action", 200);
shortcutsListView.Columns.Add("Shortcut", 150);
// Add sample shortcuts
shortcutsListView.Items.Add(new ListViewItem(new[] { "New Document", "Ctrl+N" }));
shortcutsListView.Items.Add(new ListViewItem(new[] { "Open Document", "Ctrl+O" }));
shortcutsListView.Items.Add(new ListViewItem(new[] { "Save Document", "Ctrl+S" }));
panel.Controls.Add(shortcutsListView);
tabPage.Controls.Add(panel);
settingsTabs.TabPages.Add(tabPage);
}
private void CreatePluginsTab()
{
var tabPage = new TabPage("Plugins");
var panel = new Panel { Dock = DockStyle.Fill, Padding = new Padding(20) };
var pluginsCheckListBox = new CheckedListBox
{
Dock = DockStyle.Fill
};
// Add sample plugins
pluginsCheckListBox.Items.Add("Spell Checker", true);
pluginsCheckListBox.Items.Add("Code Highlighter", true);
pluginsCheckListBox.Items.Add("Auto Complete", false);
pluginsCheckListBox.Items.Add("Git Integration", true);
panel.Controls.Add(pluginsCheckListBox);
tabPage.Controls.Add(panel);
settingsTabs.TabPages.Add(tabPage);
}
}
Theming
BeepTabs provides comprehensive theming support for all visual elements:
Apply Theme
var tabControl = new BeepTabs
{
Theme = "DarkTheme"
};
// Theme automatically applies:
// - TabBackColor (inactive tab background)
// - TabForeColor (inactive tab text)
// - TabSelectedBackColor (active tab background)
// - TabSelectedForeColor (active tab text)
// Apply theme manually if needed
tabControl.ApplyTheme();
Custom Theme Integration
// Apply theme to nested controls
tabControl.ApplyTheme();
// Theme is automatically applied to:
// - All TabPage backgrounds
// - Child controls implementing IBeepUIComponent
// - Addin controls and their children
// Custom theme application for specific scenarios
foreach (TabPage tabPage in tabControl.TabPages)
{
tabPage.BackColor = currentTheme.TabBackColor;
tabPage.ForeColor = currentTheme.TabForeColor;
foreach (Control control in tabPage.Controls)
{
if (control is IBeepUIComponent beepControl)
{
beepControl.Theme = tabControl.Theme;
}
}
}
BeepTabs automatically applies theme colors to tab headers, close buttons, and content areas, ensuring consistent visual appearance across your application.
Best Practices
🎨 User Experience
- Tab Limits: Consider limiting the number of tabs to prevent overcrowding
- Close Confirmation: Implement confirmation for closing modified content
- Tab Ordering: Provide logical tab ordering and allow user customization
- Visual Feedback: Use modified indicators (*) for unsaved content
- Keyboard Navigation: Support Ctrl+Tab for tab switching
⚡ Performance
- Lazy Loading: Load tab content only when selected
- Resource Management: Dispose of tab resources when tabs are closed
- Layout Suspension: Use SuspendFormLayout during bulk operations
- Memory Optimization: Clear unused tab references
🎨 Visual Design
- Header Position: Choose appropriate header position for your layout
- Header Sizing: Set appropriate HeaderHeight for readability
- Theme Consistency: Apply consistent theming across all tabs
- Icon Usage: Consider adding icons to tab headers for better identification
♿ Accessibility
- Keyboard Support: Ensure full keyboard navigation
- Screen Readers: Use meaningful tab text and descriptions
- Focus Indicators: Provide clear focus indication
- High Contrast: Test with high contrast themes
Be careful with tab limits and resource management. Too many tabs can impact performance and user experience.
Use BeepTabs for document interfaces, settings dialogs, and any scenario requiring organized content sections with closeable tabs.