Overview

BeepThemesManager is a static class — no instantiation required. At startup it:

  1. Scans all loaded assemblies for types implementing IBeepTheme.
  2. Loads the fixed list of 55+ predefined themes from TheTechIdea.Beep.Vis.Modules2.0.
  3. Ensures a fallback DefaultBeepTheme always exists.
  4. Exposes static events (ThemeChanged, FormStyleChanged, DpiChanged) that every control subscribes to.
Namespace: TheTechIdea.Beep.Winform.Controls.ThemeManagement
Type: Static class — use directly, no new needed.
Key entry points: BeepThemesManager.CurrentThemeName · BeepThemesManager.CurrentStyle · BeepThemesManager.ThemeChanged

Events

EventArgs TypeWhen Fired
ThemeChanged ThemeChangeEventArgs Every time CurrentThemeName is assigned a new value. All controls subscribe here to call ApplyTheme().
FormStyleChanged StyleChangeEventArgs Every time CurrentStyle changes. Automatically triggers ThemeChanged via the corresponding theme name.
DpiChanged EventArgs When DpiScaleFactor changes (monitor-move). Controls should update layout metrics and Invalidate() — not call ApplyTheme().

ThemeChangeEventArgs

Property / MethodTypeDescription
OldThemeNamestringName of the theme before the change.
NewThemeNamestringName of the newly active theme.
OldThemeIBeepThemeThe previous theme object.
NewThemeIBeepThemeThe new theme object (ready to use for painting).
IsThemeChanged()boolTrue when old and new theme names differ.
IsAnyChange()boolTrue when any aspect of the event represents a change.
GetChangeSummary()stringHuman-readable summary of what changed.
ToString()string"Theme changed from X to Y"

StyleChangeEventArgs

PropertyTypeDescription
OldStyleFormStyleThe FormStyle value before the change.
NewStyleFormStyleThe new FormStyle value.

Static Properties

Theme Selection

PropertyTypeDescription
CurrentThemeNamestringGet or set the name of the active theme. Setting fires ThemeChanged.
CurrentThemeIBeepThemeRead-only. Returns the IBeepTheme object for the current theme name.
CurrentStyleFormStyleGet or set the current FormStyle. Setting resolves the corresponding theme and fires both FormStyleChanged and ThemeChanged.
DefaultThemeIBeepThemeThe resolved fallback theme (always non-null after initialization).
DefaultThemeGuidGuidAll-zeros GUID used as the default theme identifier sentinel.

DPI Scaling

PropertyTypeDefaultDescription
DpiScaleFactorfloat1.0Average DPI scale (1.0 = 96 DPI, 1.25 = 120 DPI, 1.5 = 144 DPI). Setting fires DpiChanged.
DpiScaleXfloat1.0Horizontal DPI scale factor. Read-only; updated via UpdateDpiScaleFactors.
DpiScaleYfloat1.0Vertical DPI scale factor. Read-only.
EnableDpiFontScaling bool false Enable manual DPI multiplication on fonts. Keep false. WinForms already auto-scales Point fonts; enabling this causes double-scaling and oversized text on high-DPI monitors. Only enable for pure GDI custom painters that need physical pixel sizes.

Static Methods

Theme Lifecycle

MethodReturnsDescription
InitializeThemes()void Clear and re-discover all themes from loaded assemblies. Called automatically by the static constructor; call again to reload after dynamically loading plugin assemblies.
ResetThemes()void Alias for InitializeThemes(). Useful in tests or plugin-reload scenarios.
AddTheme(theme)void Register a custom IBeepTheme. Assigns a GUID automatically if missing. Duplicate GUIDs are ignored.
RemoveTheme(theme)void Remove a theme by its GUID.

Theme Retrieval

MethodReturnsDescription
GetTheme(name)IBeepThemeReturn theme by name, or the default theme if not found.
GetDefaultTheme()IBeepThemePriority: "DefaultTheme" → "DefaultBeepTheme" → any registered theme.
GetThemes()List<IBeepTheme>Return a snapshot copy of the full theme collection.
GetThemeNames()List<string>Return all registered theme names.
GetThemeName(theme)stringReturn the ThemeName of a theme object, or "DefaultBeepTheme" if null.
GetGuidFromTheme(theme)stringReturn the GUID string, or the zero-GUID placeholder if null.
ThemeExists(name)boolReturns true if a theme with the given name is registered.

Theme Switching

MethodDescription
SetCurrentTheme(string name)Switch by name if it exists; fires ThemeChanged.
SetCurrentTheme(IBeepTheme theme)Switch by object reference; auto-registers if not already known; fires ThemeChanged.
SetCurrentStyle(FormStyle style)Switch by FormStyle enum; resolves the paired theme; fires FormStyleChanged + ThemeChanged.

FormStyle to Theme Mapping

MethodReturnsDescription
GetThemeForFormStyle(FormStyle)IBeepThemeReturn the theme object paired with a given FormStyle.
GetThemeNameForFormStyle(FormStyle)stringReturn the theme name for a given FormStyle (see mapping table below).

Font Helpers

MethodReturnsDescription
ToFont(TypographyStyle)Font Create a logical Point font. No DPI scaling applied — WinForms auto-scales these. For standard control text.
ToFont(TypographyStyle, bool)Font applyDpiScaling parameter is silently ignored for backward compatibility. Behaves identically to the one-argument overload.
ToFontForControl(TypographyStyle, Control)Font DPI-scaled font for custom GDI painters only. Uses the control's current DPI to calculate physical pixel size. Do not use for standard WinForms text properties.
ToFont(family, size, weight, style)Font Create a font from raw parameters. Falls back to Segoe UI automatically when the requested family is unavailable.
ScaleFontForDpi(Font)Font Multiply an existing font by the global DpiScaleFactor. No-op when EnableDpiFontScaling is false (default).
ScaleFontForControl(Font, Control)Font Multiply an existing font by a specific control's current DPI scale factor.

DPI Management

MethodDescription
UpdateDpiFromControl(Control)Detect and apply the DPI of a specific control. Call from OnDpiChanged in forms.
UpdateDpiFromSystem()Apply the system-wide DPI. Call once at application startup before any form is created.
UpdateDpiScaleFactors(scaleX, scaleY)Set both axes explicitly. Fires DpiChanged only when values change by more than 0.01.

Persistence

MethodDescription
SaveTheme(theme, filePath)XML-serialize a theme to the given file path.
LoadTheme(filePath)XML-deserialize a theme from file. Registers it in the collection automatically.

Misc

MethodDescription
ConfigureDefaultTheme(BeepTheme)Apply sensible dark or light color defaults based on whether the name contains "Dark". Good starting point for new themes.

FormStyle → Theme Name Mapping

Setting CurrentStyle automatically resolves and activates the paired theme via this mapping:

FormStyleTheme NameFormStyleTheme Name
ModernModernThemeNordicNordicTheme
MinimalMinimalThemeiOSiOSTheme
MacOSMacOSThemeUbuntuUbuntuTheme
FluentFluentThemeKDEKDETheme
MaterialMaterialDesignThemeArcLinuxArcLinuxTheme
CartoonCartoonThemeDraculaDraculaTheme
ChatBubbleChatBubbleThemeSolarizedSolarizedTheme
GlassGlassThemeOneDarkOneDarkTheme
MetroMetroThemeGruvBoxGruvBoxTheme
Metro2Metro2ThemeNordNordTheme
GNOMEGNOMEThemeTokyoTokyoTheme
NeoMorphismNeoMorphismThemePaperPaperTheme
GlassmorphismGlassmorphismThemeNeonNeonTheme
BrutalistBrutalistThemeHolographicHolographicTheme
RetroRetroThemeTerminalTerminalTheme
CyberpunkCyberpunkThemeCustomDefaultTheme
 (any other)DefaultTheme

Built-in Predefined Themes

All themes below are auto-discovered and registered at startup from TheTechIdea.Beep.Vis.Modules2.0. They are also dynamically loaded by AddPredefinedThemes() via reflection:

  • DefaultTheme
  • WinterTheme
  • CandyTheme
  • ZenTheme
  • RetroTheme
  • RoyalTheme
  • HighlightTheme
  • DarkTheme
  • OceanTheme
  • LightTheme
  • PastelTheme
  • MidnightTheme
  • SpringTheme
  • ForestTheme
  • NeonTheme
  • RusticTheme
  • GalaxyTheme
  • DesertTheme
  • VintageTheme
  • ModernDarkTheme
  • MaterialDesignTheme
  • NeumorphismTheme
  • GlassmorphismTheme
  • FlatDesignTheme
  • CyberpunkNeonTheme
  • GradientBurstTheme
  • HighContrastTheme
  • MonochromeTheme
  • LuxuryGoldTheme
  • SunsetTheme
  • AutumnTheme
  • EarthyTheme
  • ArcLinuxTheme
  • BrutalistTheme
  • CartoonTheme
  • ChatBubbleTheme
  • CyberpunkTheme
  • DraculaTheme
  • FluentTheme
  • GlassTheme
  • GNOMETheme
  • GruvBoxTheme
  • HolographicTheme
  • iOSTheme
  • KDETheme
  • MacOSTheme
  • Metro2Theme
  • MetroTheme
  • MinimalTheme
  • NeoMorphismTheme
  • NordicTheme
  • NordTheme
  • OneDarkTheme
  • PaperTheme
  • SolarizedTheme
  • TerminalTheme
  • TokyoTheme
  • UbuntuTheme

Creating a Custom Theme

Implement IBeepTheme (or inherit from BeepTheme) and register it once at startup:

public class MyBrandTheme : BeepTheme
{
    public MyBrandTheme()
    {
        ThemeName      = "MyBrandTheme";
        IsDarkTheme    = false;
        BackColor      = Color.FromArgb(245, 248, 255);
        ForeColor      = Color.FromArgb(20, 20, 40);
        PrimaryColor   = Color.FromArgb(0, 85, 200);    // brand blue
        SecondaryColor = Color.FromArgb(0, 150, 130);   // brand teal
        AccentColor    = Color.FromArgb(255, 120, 30);  // brand orange
        BorderRadius   = 8;
        PaddingSmall   = 4;
        PaddingMedium  = 8;
        PaddingLarge   = 16;
    }
}
// Register once at application startup (before Application.Run)
BeepThemesManager.AddTheme(new MyBrandTheme());

// Activate it - all subscribed controls repaint automatically
BeepThemesManager.SetCurrentTheme("MyBrandTheme");

Subscribing to Theme Events in Custom Controls

public class MyCustomControl : BeepControl
{
    public MyCustomControl()
    {
        BeepThemesManager.ThemeChanged     += OnThemeChanged;
        BeepThemesManager.DpiChanged       += OnDpiChanged;
        BeepThemesManager.FormStyleChanged += OnFormStyleChanged;
    }

    private void OnThemeChanged(object sender, ThemeChangeEventArgs e)
    {
        // e.NewTheme is the ready-to-use IBeepTheme
        ApplyTheme(e.NewTheme);
        Invalidate();
    }

    private void OnDpiChanged(object sender, EventArgs e)
    {
        // DPI changed - update layout metrics but do NOT call ApplyTheme()
        UpdateLayout();
        Invalidate();
    }

    private void OnFormStyleChanged(object sender, StyleChangeEventArgs e)
    {
        // Optional: adjust chrome for the new style
        UpdateBorderForStyle(e.NewStyle);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            // CRITICAL: always unsubscribe - static events hold strong references
            BeepThemesManager.ThemeChanged     -= OnThemeChanged;
            BeepThemesManager.DpiChanged       -= OnDpiChanged;
            BeepThemesManager.FormStyleChanged -= OnFormStyleChanged;
        }
        base.Dispose(disposing);
    }
}
Memory leak warning: Static events hold strong references to all subscribers. Always unsubscribe in Dispose(), otherwise controls are kept alive until the application exits.

DPI Handling

Beep uses WinForms automatic Point-font DPI scaling for all standard text. Custom GDI painters that need absolute pixel measurements must call ToFontForControl instead of ToFont.

Application startup

// Program.cs - before Application.Run()
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
BeepThemesManager.UpdateDpiFromSystem();

Forms that can move between monitors

protected override void OnDpiChanged(DpiChangedEventArgs e)
{
    base.OnDpiChanged(e);
    // Updates DpiScaleX/Y and fires BeepThemesManager.DpiChanged automatically
    BeepThemesManager.UpdateDpiFromControl(this);
}

Font in a custom GDI painter

protected override void OnPaint(PaintEventArgs e)
{
    // Use ToFontForControl for physical pixel accuracy in custom GDI drawing
    using var bodyFont = BeepThemesManager.ToFontForControl(
        BeepThemesManager.CurrentTheme.Body1Typography, this);

    e.Graphics.DrawString("Hello", bodyFont, Brushes.Black, ClientRectangle);
}

Switching Themes at Runtime

// By name
BeepThemesManager.SetCurrentTheme("DraculaTheme");

// By FormStyle - sets both form chrome and theme palette in one call
BeepThemesManager.SetCurrentStyle(FormStyle.Dracula);

// By object reference
IBeepTheme night = BeepThemesManager.GetTheme("MidnightTheme");
BeepThemesManager.SetCurrentTheme(night);

// Populate a ComboBox with all available themes
var names = BeepThemesManager.GetThemeNames();
cmbTheme.DataSource = names;
cmbTheme.SelectedIndexChanged += (s, e) =>
    BeepThemesManager.SetCurrentTheme((string)cmbTheme.SelectedItem);

Theme Persistence

// Save a custom theme to an XML file
IBeepTheme custom = BeepThemesManager.GetTheme("MyBrandTheme");
BeepThemesManager.SaveTheme(custom, @"themes\mybrand.xml");

// Load it back (also registers it automatically)
IBeepTheme loaded = BeepThemesManager.LoadTheme(@"themes\mybrand.xml");
BeepThemesManager.SetCurrentTheme(loaded);

Best Practices

  • Never call ApplyTheme() inside the DpiChanged handler. DPI change and theme change are intentionally separate events. Calling ApplyTheme() on DPI change causes unnecessary font re-creation, multiple repaints, and potential double-scaling.
  • Always unsubscribe in Dispose(). Static event subscriptions prevent controls from being garbage-collected.
  • Use ToFont() for normal WinForms text, ToFontForControl() for custom GDI painters. ToFont() returns a logical Point font that WinForms auto-scales. ToFontForControl() returns a physical pixel font for raw GDI measurements.
  • Leave EnableDpiFontScaling = false (the default). Enabling it on a WinForms app causes double-scaling (WinForms + manual) resulting in oversized text on high-DPI monitors.
  • Call UpdateDpiFromSystem() once at startup. For forms that can move between monitors also call UpdateDpiFromControl(this) inside OnDpiChanged.
  • Register custom themes before calling Application.Run(). Controls are created first and may miss a ThemeChanged event if the theme is added after the window is shown.
  • Use ConfigureDefaultTheme() as a quick baseline. It sets sensible dark/light colors and padding so you only override the properties your brand needs.
  • Prefer SetCurrentStyle(FormStyle) over SetCurrentTheme(name) when you want the form chrome and control palette to stay visually consistent — both change together in one call.