Skip to content

Style

Bases: Style

A singleton class for creating and managing the application theme and widget styles.

This class is meant to be a drop-in replacement for ttk.Style and inherits all of it's methods and properties. However, in ttkbootstrap, this class is implemented as a singleton. Subclassing is not recommended and may have unintended consequences.

Examples:

```python
# instantiate the style with default theme
style = Style()

# instantiate the style with another theme
style = Style(theme='superhero')

# check all available themes
for theme in style.theme_names():
    print(theme)
```

See the Python documentation on this class for more details.

Source code in src/ttkbootstrap/style.py
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
class Style(ttk.Style):
    """A singleton class for creating and managing the application
    theme and widget styles.

    This class is meant to be a drop-in replacement for `ttk.Style` and
    inherits all of it's methods and properties. However, in
    ttkbootstrap, this class is implemented as a singleton. Subclassing
    is not recommended and may have unintended consequences.

    Examples:

        ```python
        # instantiate the style with default theme
        style = Style()

        # instantiate the style with another theme
        style = Style(theme='superhero')

        # check all available themes
        for theme in style.theme_names():
            print(theme)
        ```

    See the [Python documentation](https://docs.python.org/3/library/tkinter.ttk.html#tkinter.ttk.Style)
    on this class for more details.
    """

    instance = None

    def __new__(cls, theme=None):
        if Style.instance is None:
            return object.__new__(cls)
        else:
            return Style.instance

    def __init__(self, theme=DEFAULT_THEME):
        """
        Parameters:

            theme (str):
                The name of the theme to use when styling the widget.
        """
        if Style.instance is not None:
            if theme != DEFAULT_THEME:
                Style.instance.theme_use(theme)
            return
        self._theme_objects = {}
        self._theme_definitions = {}
        self._style_registry = set()  # all styles used
        self._theme_styles = {}  # styles used in theme
        self._theme_names = set()
        self._load_themes()
        self._dynamic_foreground = False
        super().__init__()

        Style.instance = self
        self.theme_use(theme)

        # apply localization
        from ttkbootstrap import localization
        localization.initialize_localities()

    @property
    def colors(self):
        """An object that contains the colors used for the current
        theme.

        Returns:

            Colors:
                The colors object for the current theme.
        """
        theme = self.theme.name
        if theme in list(self._theme_names):
            definition = self._theme_definitions.get(theme)
            if not definition:
                return []  # TODO refactor this
            else:
                return definition.colors
        else:
            return []  # TODO refactor this

    def configure(self, style, query_opt: Any = None, **kw):
        if query_opt:
            return super().configure(style, query_opt=query_opt, **kw)

        if not self.style_exists_in_theme(style):
            ttkstyle = Bootstyle.update_ttk_widget_style(None, style)
        else:
            ttkstyle = style

        if ttkstyle == style:
            # configure an existing ttkbootrap theme
            return super().configure(style, query_opt=query_opt, **kw)
        else:
            # subclass a ttkbootstrap theme
            result = super().configure(style, query_opt=query_opt, **kw)
            self._register_ttkstyle(style)
            return result

    def theme_names(self):
        """Return a list of all ttkbootstrap themes.

        Returns:

            list[str, ...]:
                A list of theme names.
        """
        return list(self._theme_definitions.keys())

    def register_theme(self, definition):
        """Register a theme definition for use by the `Style`
        object. This makes the definition and name available at
        run-time so that the assets and styles can be created when
        needed.

        Parameters:

            definition (ThemeDefinition):
                A `ThemeDefinition` object.
        """
        theme = definition.name
        self._theme_names.add(theme)
        self._theme_definitions[theme] = definition
        self._theme_styles[theme] = set()

    def theme_use(self, themename=None):
        """Changes the theme used in rendering the application widgets.

        If themename is None, returns the theme in use, otherwise, set
        the current theme to themename, refreshes all widgets and emits
        a ``<<ThemeChanged>>`` event.

        Only use this method if you are changing the theme *during*
        runtime. Otherwise, pass the theme name into the Style
        constructor to instantiate the style with a theme.

        Parameters:

            themename (str):
                The name of the theme to apply when creating new widgets

        Returns:

            Union[str, None]:
                The name of the current theme if `themename` is None
                otherwise, `None`.
        """
        if not themename:
            # return current theme
            return super().theme_use()

        # change to an existing theme
        existing_themes = super().theme_names()
        if themename in existing_themes:
            self.theme = self._theme_definitions.get(themename)
            super().theme_use(themename)
            self._create_ttk_styles_on_theme_change()
            Publisher.publish_message(Channel.STD)
        # setup a new theme
        elif themename in self._theme_names:
            self.theme = self._theme_definitions.get(themename)
            self._theme_objects[themename] = StyleBuilderTTK()
            self._create_ttk_styles_on_theme_change()
            Publisher.publish_message(Channel.STD)
        else:
            raise TclError(themename, "is not a valid theme.")

    def theme_create(self, themename: str, parent: str = None, settings: dict = None) -> None:
        """
        Create a new theme in the Tcl interpreter. If the parent is a registered
        ttkbootstrap theme, the new theme will be registered with a copied
        ThemeDefinition and builder. Duplicate registration is avoided.

        Parameters:

            themename (str):
                The name of the new theme.

            parent (str):
                The name of the parent theme to inherit from.

            settings (dict):
                A dictionary of style settings (Tcl-style).
        """
        from tkinter.ttk import _script_from_settings  # type: ignore[attr-defined]

        script = _script_from_settings(settings) if settings else ''

        # Lazy-load parent if it's a known bootstrap theme
        if parent:
            if parent not in super().theme_names():
                if parent in self._theme_names:
                    self.theme_use(parent)
                else:
                    raise TclError(f"{parent!r} is not a valid theme name or parent theme.")

        # Create the Tcl-level theme
        if parent:
            self.tk.call(
                self._name, "theme", "create", themename,
                "-parent", parent, "-settings", script)
        else:
            self.tk.call(
                self._name, "theme", "create", themename,
                "-settings", script)

        # Register the new theme if copying from a ttkbootstrap theme
        if parent in self._theme_definitions and themename not in self._theme_definitions:
            parent_def = self._theme_definitions[parent]
            copied_def = ThemeDefinition(
                name=themename,
                colors=parent_def.colors,
                themetype=parent_def.type
            )
            self._theme_definitions[themename] = copied_def
            self._theme_names.add(themename)
            self._theme_styles[themename] = set()

            if themename not in self._theme_objects:
                self._theme_objects[themename] = StyleBuilderTTK(build=False)

    def style_exists_in_theme(self, ttkstyle: str):
        """Check if a style exists in the current theme.

        Parameters:

            ttkstyle (str):
                The ttk style to check.

        Returns:

            bool:
                `True` if the style exists, otherwise `False`.
        """
        if self.theme is None:
            return False

        theme_styles = self._theme_styles.get(self.theme.name)
        if theme_styles is None:
            return False

        exists_in_theme = ttkstyle in theme_styles
        exists_in_registry = ttkstyle in self._style_registry
        return exists_in_theme and exists_in_registry

    def use_dynamic_foreground(self, enable: bool = True):
        """Enable or disable dynamic foreground color selection.

        When enabled, the foreground color of widgets will be decided
        between the `fg` and `selectfg` colors based on the
        contrast ratio with the widget's background color.
        At default, this is disabled.

        Parameters:

            enable (bool):
                If `True`, dynamic foreground selection is enabled.
                Otherwise, it is disabled.
        """
        self._dynamic_foreground = enable

    @property
    def dynamic_foreground(self):
        """Returns `True` if dynamic foreground selection is enabled,
        otherwise `False`.
        """
        return self._dynamic_foreground

    @staticmethod
    def get_instance():
        """Returns and instance of the style class"""
        return Style.instance

    @staticmethod
    def _get_builder():
        """Get the object that builds the widget styles for the current
        theme.

        Returns:

            ThemeBuilderTTK:
                The theme builder object that builds the ttk styles for
                the current theme.
        """
        style: Style = Style.get_instance()
        theme_name = style.theme.name
        return style._theme_objects[theme_name]

    @staticmethod
    def _get_builder_tk():
        """Get the object that builds the widget styles for the current
        theme.

        Returns:

            ThemeBuilderTK:
                The theme builder object that builds the ttk styles for
                the current theme.
        """
        builder = Style._get_builder()
        return builder.builder_tk

    def _build_configure(self, style, **kw):
        """Calls configure of superclass; used by style builder classes."""
        super().configure(style, **kw)

    def _load_themes(self, EXTERNAL_THEMES=None):
        """Load all ttkbootstrap defined themes"""
        # create a theme definition object for each theme, this will be
        # used to generate the theme in tkinter along with any assets
        # at run-time
        if USER_THEMES:
            STANDARD_THEMES.update(USER_THEMES)

        if EXTERNAL_THEMES:
            STANDARD_THEMES.update(EXTERNAL_THEMES)

        theme_settings = {"themes": STANDARD_THEMES}
        for name, definition in theme_settings["themes"].items():
            self.register_theme(
                ThemeDefinition(
                    name=name,
                    themetype=definition["type"],
                    colors=definition["colors"],
                )
            )

    def _register_ttkstyle(self, ttkstyle):
        """Register that a ttk style name. This ensures that the
        builder will not attempt to build a style that has already
        been created.

        Parameters:

            ttkstyle (str):
                The name of the ttk style to register.
        """
        self._style_registry.add(ttkstyle)
        theme = self.theme.name
        self._theme_styles[theme].add(ttkstyle)

    def _create_ttk_styles_on_theme_change(self):
        """Create existing styles when the theme changes"""
        for ttkstyle in self._style_registry:
            if not self.style_exists_in_theme(ttkstyle):
                color = Bootstyle.ttkstyle_widget_color(ttkstyle)
                method_name = Bootstyle.ttkstyle_method_name(string=ttkstyle)
                builder: StyleBuilderTTK = self._get_builder()
                method: Callable = builder.name_to_method(method_name)
                method(builder, color)

    def load_user_theme(self, theme: ThemeDefinition):
        """Load a user theme definition"""
        self.register_theme(theme)

    def load_user_themes(self, file):
        """Load user themes saved in json format"""
        with open(file, encoding='utf-8') as f:
            data = json.load(f)
            themes = data['themes']
        for theme in themes:
            for name, definition in theme.items():
                self.register_theme(
                    ThemeDefinition(
                        name=name,
                        themetype=definition["type"],
                        colors=definition["colors"],
                    )
                )

colors property

An object that contains the colors used for the current theme.

Returns:

Colors:
    The colors object for the current theme.

dynamic_foreground property

Returns True if dynamic foreground selection is enabled, otherwise False.

__init__(theme=DEFAULT_THEME)

Parameters:

theme (str):
    The name of the theme to use when styling the widget.
Source code in src/ttkbootstrap/style.py
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
def __init__(self, theme=DEFAULT_THEME):
    """
    Parameters:

        theme (str):
            The name of the theme to use when styling the widget.
    """
    if Style.instance is not None:
        if theme != DEFAULT_THEME:
            Style.instance.theme_use(theme)
        return
    self._theme_objects = {}
    self._theme_definitions = {}
    self._style_registry = set()  # all styles used
    self._theme_styles = {}  # styles used in theme
    self._theme_names = set()
    self._load_themes()
    self._dynamic_foreground = False
    super().__init__()

    Style.instance = self
    self.theme_use(theme)

    # apply localization
    from ttkbootstrap import localization
    localization.initialize_localities()

get_instance() staticmethod

Returns and instance of the style class

Source code in src/ttkbootstrap/style.py
820
821
822
823
@staticmethod
def get_instance():
    """Returns and instance of the style class"""
    return Style.instance

load_user_theme(theme)

Load a user theme definition

Source code in src/ttkbootstrap/style.py
903
904
905
def load_user_theme(self, theme: ThemeDefinition):
    """Load a user theme definition"""
    self.register_theme(theme)

load_user_themes(file)

Load user themes saved in json format

Source code in src/ttkbootstrap/style.py
907
908
909
910
911
912
913
914
915
916
917
918
919
920
def load_user_themes(self, file):
    """Load user themes saved in json format"""
    with open(file, encoding='utf-8') as f:
        data = json.load(f)
        themes = data['themes']
    for theme in themes:
        for name, definition in theme.items():
            self.register_theme(
                ThemeDefinition(
                    name=name,
                    themetype=definition["type"],
                    colors=definition["colors"],
                )
            )

register_theme(definition)

Register a theme definition for use by the Style object. This makes the definition and name available at run-time so that the assets and styles can be created when needed.

Parameters:

definition (ThemeDefinition):
    A `ThemeDefinition` object.
Source code in src/ttkbootstrap/style.py
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
def register_theme(self, definition):
    """Register a theme definition for use by the `Style`
    object. This makes the definition and name available at
    run-time so that the assets and styles can be created when
    needed.

    Parameters:

        definition (ThemeDefinition):
            A `ThemeDefinition` object.
    """
    theme = definition.name
    self._theme_names.add(theme)
    self._theme_definitions[theme] = definition
    self._theme_styles[theme] = set()

style_exists_in_theme(ttkstyle)

Check if a style exists in the current theme.

Parameters:

ttkstyle (str):
    The ttk style to check.

Returns:

bool:
    `True` if the style exists, otherwise `False`.
Source code in src/ttkbootstrap/style.py
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
def style_exists_in_theme(self, ttkstyle: str):
    """Check if a style exists in the current theme.

    Parameters:

        ttkstyle (str):
            The ttk style to check.

    Returns:

        bool:
            `True` if the style exists, otherwise `False`.
    """
    if self.theme is None:
        return False

    theme_styles = self._theme_styles.get(self.theme.name)
    if theme_styles is None:
        return False

    exists_in_theme = ttkstyle in theme_styles
    exists_in_registry = ttkstyle in self._style_registry
    return exists_in_theme and exists_in_registry

theme_create(themename, parent=None, settings=None)

Create a new theme in the Tcl interpreter. If the parent is a registered ttkbootstrap theme, the new theme will be registered with a copied ThemeDefinition and builder. Duplicate registration is avoided.

Parameters:

themename (str):
    The name of the new theme.

parent (str):
    The name of the parent theme to inherit from.

settings (dict):
    A dictionary of style settings (Tcl-style).
Source code in src/ttkbootstrap/style.py
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
def theme_create(self, themename: str, parent: str = None, settings: dict = None) -> None:
    """
    Create a new theme in the Tcl interpreter. If the parent is a registered
    ttkbootstrap theme, the new theme will be registered with a copied
    ThemeDefinition and builder. Duplicate registration is avoided.

    Parameters:

        themename (str):
            The name of the new theme.

        parent (str):
            The name of the parent theme to inherit from.

        settings (dict):
            A dictionary of style settings (Tcl-style).
    """
    from tkinter.ttk import _script_from_settings  # type: ignore[attr-defined]

    script = _script_from_settings(settings) if settings else ''

    # Lazy-load parent if it's a known bootstrap theme
    if parent:
        if parent not in super().theme_names():
            if parent in self._theme_names:
                self.theme_use(parent)
            else:
                raise TclError(f"{parent!r} is not a valid theme name or parent theme.")

    # Create the Tcl-level theme
    if parent:
        self.tk.call(
            self._name, "theme", "create", themename,
            "-parent", parent, "-settings", script)
    else:
        self.tk.call(
            self._name, "theme", "create", themename,
            "-settings", script)

    # Register the new theme if copying from a ttkbootstrap theme
    if parent in self._theme_definitions and themename not in self._theme_definitions:
        parent_def = self._theme_definitions[parent]
        copied_def = ThemeDefinition(
            name=themename,
            colors=parent_def.colors,
            themetype=parent_def.type
        )
        self._theme_definitions[themename] = copied_def
        self._theme_names.add(themename)
        self._theme_styles[themename] = set()

        if themename not in self._theme_objects:
            self._theme_objects[themename] = StyleBuilderTTK(build=False)

theme_names()

Return a list of all ttkbootstrap themes.

Returns:

list[str, ...]:
    A list of theme names.
Source code in src/ttkbootstrap/style.py
651
652
653
654
655
656
657
658
659
def theme_names(self):
    """Return a list of all ttkbootstrap themes.

    Returns:

        list[str, ...]:
            A list of theme names.
    """
    return list(self._theme_definitions.keys())

theme_use(themename=None)

Changes the theme used in rendering the application widgets.

If themename is None, returns the theme in use, otherwise, set the current theme to themename, refreshes all widgets and emits a <<ThemeChanged>> event.

Only use this method if you are changing the theme during runtime. Otherwise, pass the theme name into the Style constructor to instantiate the style with a theme.

Parameters:

themename (str):
    The name of the theme to apply when creating new widgets

Returns:

Union[str, None]:
    The name of the current theme if `themename` is None
    otherwise, `None`.
Source code in src/ttkbootstrap/style.py
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
def theme_use(self, themename=None):
    """Changes the theme used in rendering the application widgets.

    If themename is None, returns the theme in use, otherwise, set
    the current theme to themename, refreshes all widgets and emits
    a ``<<ThemeChanged>>`` event.

    Only use this method if you are changing the theme *during*
    runtime. Otherwise, pass the theme name into the Style
    constructor to instantiate the style with a theme.

    Parameters:

        themename (str):
            The name of the theme to apply when creating new widgets

    Returns:

        Union[str, None]:
            The name of the current theme if `themename` is None
            otherwise, `None`.
    """
    if not themename:
        # return current theme
        return super().theme_use()

    # change to an existing theme
    existing_themes = super().theme_names()
    if themename in existing_themes:
        self.theme = self._theme_definitions.get(themename)
        super().theme_use(themename)
        self._create_ttk_styles_on_theme_change()
        Publisher.publish_message(Channel.STD)
    # setup a new theme
    elif themename in self._theme_names:
        self.theme = self._theme_definitions.get(themename)
        self._theme_objects[themename] = StyleBuilderTTK()
        self._create_ttk_styles_on_theme_change()
        Publisher.publish_message(Channel.STD)
    else:
        raise TclError(themename, "is not a valid theme.")

use_dynamic_foreground(enable=True)

Enable or disable dynamic foreground color selection.

When enabled, the foreground color of widgets will be decided between the fg and selectfg colors based on the contrast ratio with the widget's background color. At default, this is disabled.

Parameters:

enable (bool):
    If `True`, dynamic foreground selection is enabled.
    Otherwise, it is disabled.
Source code in src/ttkbootstrap/style.py
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
def use_dynamic_foreground(self, enable: bool = True):
    """Enable or disable dynamic foreground color selection.

    When enabled, the foreground color of widgets will be decided
    between the `fg` and `selectfg` colors based on the
    contrast ratio with the widget's background color.
    At default, this is disabled.

    Parameters:

        enable (bool):
            If `True`, dynamic foreground selection is enabled.
            Otherwise, it is disabled.
    """
    self._dynamic_foreground = enable