Module TkZero.Frame

Creates a themed frame.

Expand source code
"""
Creates a themed frame.
"""

import tkinter as tk
from tkinter import ttk
from typing import Union

from TkZero import Platform


class Frame(ttk.Frame):
    def __init__(self, parent: Union[tk.Widget, Union[tk.Tk, tk.Toplevel]]):
        """
        Initiate a ttk.Frame.

        :param parent: The parent of the frame.
        """
        if not isinstance(parent, (tk.Widget, tk.Tk, tk.Toplevel)):
            raise TypeError(
                f"parent is not a "
                f"Union[tk.Widget, Union[tk.Tk, tk.Toplevel]]! "
                f"(type passed in: {repr(type(parent))})"
            )
        super().__init__(master=parent)
        self._style_root = "TFrame"
        self._enabled = True

    @property
    def width(self) -> int:
        """
        Get the width of the frame.

        :return: An int which is the width of the frame.
        """
        return self.cget("width")

    @width.setter
    def width(self, new_width: int) -> None:
        """
        Set the width of the frame.

        :param new_width: An int which should be the new width of the frame.
         Cannot be less than 1.
        :return: None.
        """
        if not isinstance(new_width, int):
            raise TypeError(
                f"new_height is not a int! "
                f"(type passed in: {repr(type(new_width))})"
            )
        if new_width < 1:
            raise ValueError(
                f"new_height is less than 1! " f"(value passed in: {repr(new_width)})"
            )
        self.configure(width=new_width)

    @property
    def height(self) -> int:
        """
        Get the height of the frame.

        :return: An int which is the height of the frame.
        """
        return self.cget("height")

    @height.setter
    def height(self, new_height: int) -> None:
        """
        Set the width of the frame.

        :param new_height: An int which should be the new height of the frame.
         Cannot be less than 1.
        :return: None.
        """
        if not isinstance(new_height, int):
            raise TypeError(
                f"new_height is not an int! "
                f"(type passed in: {repr(type(new_height))})"
            )
        if new_height < 1:
            raise ValueError(
                f"new_height is less than 1! " f"(value passed in: {repr(new_height)})"
            )
        self.configure(height=new_height)

    def _enable_children(
        self, parent: Union[tk.Widget, None] = None, enable: bool = True
    ) -> None:
        """
        Enable or disable the children.

        :param parent: A tk.Widget that is our parent. If None then default to
         self.
        :param enable: Whether to enable or disable the children.
        :return: None.
        """
        if parent is None:
            parent = self
        for child in parent.winfo_children():
            if child.winfo_class() not in ("Frame", "LabelFrame"):
                try:
                    child.state(["!disabled" if enable else "disabled"])
                except AttributeError:
                    child.configure(state=tk.NORMAL if enable else tk.DISABLED)
            else:
                self._enable_children(parent, enable)

    @property
    def enabled(self) -> bool:
        """
        Get whether this widget is in normal mode or disabled mode. (grayed
        out and cannot interact with)

        :return: A bool, True if normal otherwise False.
        """
        return self._enabled

    @enabled.setter
    def enabled(self, new_state: bool) -> None:
        """
        Set whether this widget is in normal mode or disabled mode. (grayed
        out and cannot interact with)

        :param new_state: The new state (a bool) True for enabled and False
        for disabled.
        :return: None.
        """
        if not isinstance(new_state, bool):
            raise TypeError(
                f"new_state is not a bool! "
                f"(type passed in: {repr(type(new_state))})"
            )
        self._enabled = new_state
        self._enable_children(enable=self._enabled)

    def apply_style(self, style_name: str) -> None:
        """
        Apply a theme to this frame.

        :param style_name: The name of the theme as a str, ex. "Warning"
        :return: None.
        """
        if not isinstance(style_name, str):
            raise TypeError(
                f"style_name is not a str! "
                f"(type passed in: {repr(type(style_name))})"
            )
        self.configure(style=f"{style_name}.{self._style_root}")


class ScrollableFrame(Frame):
    def __init__(
        self,
        parent: Union[tk.Widget, Union[tk.Tk, tk.Toplevel]],
        x_scrolling: bool = False,
        y_scrolling: bool = True,
    ):
        """
        Create a scrollable frame.
        MAKE SURE WHEN GRIDING WIDGETS TO THIS FRAME TO USE THE FRAME
        ATTRIBUTE INSTEAD OF THIS FRAME DIRECTLY.

        :param parent: The parent of this frame.
        :param x_scrolling: A bool on whether to add a scrollbar in the x
         direction.
        :param y_scrolling: A bool on whether to add a scrollbar in the y
         direction.
        """
        # https://blog.teclado.com/tkinter-scrollable-frames/
        super().__init__(parent)
        self.canvas = tk.Canvas(self)
        if x_scrolling:
            x_scrollbar = ttk.Scrollbar(
                self, orient=tk.HORIZONTAL, command=self.canvas.xview
            )
        if y_scrolling:
            y_scrollbar = ttk.Scrollbar(
                self, orient=tk.VERTICAL, command=self.canvas.yview
            )
        self.frame = Frame(self.canvas)
        self.frame.bind(
            "<Configure>",
            lambda _: self.canvas.configure(scrollregion=self.canvas.bbox(tk.ALL)),
        )
        self.canvas.create_window((0, 0), window=self.frame, anchor=tk.NW)
        if x_scrolling:
            self.canvas.configure(xscrollcommand=x_scrollbar.set)
        if y_scrolling:
            self.canvas.configure(yscrollcommand=y_scrollbar.set)
        self.canvas.grid(row=0, column=0)
        if x_scrolling:
            x_scrollbar.grid(row=1, column=0, sticky=tk.NSEW)
        if y_scrolling:
            y_scrollbar.grid(row=0, column=1, sticky=tk.NSEW)
        # https://stackoverflow.com/a/37858368/10291933
        self.frame.bind("<Enter>", self._bind_to_mousewheel)
        self.frame.bind("<Leave>", self._unbind_to_mousewheel)
        self._shift_pressed = False
        # https://stackoverflow.com/a/8089241/10291933
        # http://www.tcl.tk/man/tcl8.4/TkCmd/keysyms.htm
        self.frame.bind_all("<Shift_L>", lambda _: self._set_shift_pressed(True))
        self.frame.bind_all("<Shift_R>", lambda _: self._set_shift_pressed(True))
        self.frame.bind_all(
            "<KeyRelease-Shift_L>", lambda _: self._set_shift_pressed(False)
        )
        self.frame.bind_all(
            "<KeyRelease-Shift_R>", lambda _: self._set_shift_pressed(False)
        )

    def _set_shift_pressed(self, is_pressed: bool) -> None:
        """
        Set whether shift is pressed or not.

        :param is_pressed: A bool.
        :return: None.
        """
        self._shift_pressed = is_pressed

    def _bind_to_mousewheel(self, event) -> None:
        """
        Bind to the mousewheel.

        :param event: An event that Tkinter passes in.
        :return: None
        """
        self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)

    def _unbind_to_mousewheel(self, event) -> None:
        """
        Unbind to the mousewheel.

        :param event: An event that Tkinter passes in.
        :return: None
        """
        self.canvas.unbind_all("<MouseWheel>")

    def _on_mousewheel(self, event) -> None:
        """
        A callback for the mousewheel to scroll.

        :param event: An event that Tkinter passes in.
        :return: None.
        """
        if Platform.on_aqua(self):
            scroll = str(int(-1 * event.delta))
        else:
            scroll = str(int(-1 * (event.delta / 120)))
        if self._shift_pressed:
            self.canvas.xview_scroll(scroll, tk.UNITS)
        else:
            self.canvas.yview_scroll(scroll, tk.UNITS)

    def _enable_children(
        self, parent: Union[tk.Widget, None] = None, enable: bool = True
    ) -> None:
        """
        Enable or disable the children.

        :param parent: A tk.Widget that is our parent. If None then default to
         self.
        :param enable: Whether to enable or disable the children.
        :return: None.
        """
        if parent is None:
            parent = self.frame
        for child in parent.winfo_children():
            if child.winfo_class() not in ("Frame", "LabelFrame"):
                try:
                    child.state(["!disabled" if enable else "disabled"])
                except AttributeError:
                    child.configure(state=tk.NORMAL if enable else tk.DISABLED)
            else:
                self._enable_children(parent, enable)

    def apply_style(self, style_name: str) -> None:
        """
        Apply a theme to this frame.

        :param style_name: The name of the theme as a str, ex. "Warning"
        :return: None.
        """
        if not isinstance(style_name, str):
            raise TypeError(
                f"style_name is not a str! "
                f"(type passed in: {repr(type(style_name))})"
            )
        self.frame.configure(style=f"{style_name}.{self._style_root}")

Classes

class Frame (parent: Union[tkinter.Widget, tkinter.Tk, tkinter.Toplevel])

Ttk Frame widget is a container, used to group other widgets together.

Initiate a ttk.Frame.

:param parent: The parent of the frame.

Expand source code
class Frame(ttk.Frame):
    def __init__(self, parent: Union[tk.Widget, Union[tk.Tk, tk.Toplevel]]):
        """
        Initiate a ttk.Frame.

        :param parent: The parent of the frame.
        """
        if not isinstance(parent, (tk.Widget, tk.Tk, tk.Toplevel)):
            raise TypeError(
                f"parent is not a "
                f"Union[tk.Widget, Union[tk.Tk, tk.Toplevel]]! "
                f"(type passed in: {repr(type(parent))})"
            )
        super().__init__(master=parent)
        self._style_root = "TFrame"
        self._enabled = True

    @property
    def width(self) -> int:
        """
        Get the width of the frame.

        :return: An int which is the width of the frame.
        """
        return self.cget("width")

    @width.setter
    def width(self, new_width: int) -> None:
        """
        Set the width of the frame.

        :param new_width: An int which should be the new width of the frame.
         Cannot be less than 1.
        :return: None.
        """
        if not isinstance(new_width, int):
            raise TypeError(
                f"new_height is not a int! "
                f"(type passed in: {repr(type(new_width))})"
            )
        if new_width < 1:
            raise ValueError(
                f"new_height is less than 1! " f"(value passed in: {repr(new_width)})"
            )
        self.configure(width=new_width)

    @property
    def height(self) -> int:
        """
        Get the height of the frame.

        :return: An int which is the height of the frame.
        """
        return self.cget("height")

    @height.setter
    def height(self, new_height: int) -> None:
        """
        Set the width of the frame.

        :param new_height: An int which should be the new height of the frame.
         Cannot be less than 1.
        :return: None.
        """
        if not isinstance(new_height, int):
            raise TypeError(
                f"new_height is not an int! "
                f"(type passed in: {repr(type(new_height))})"
            )
        if new_height < 1:
            raise ValueError(
                f"new_height is less than 1! " f"(value passed in: {repr(new_height)})"
            )
        self.configure(height=new_height)

    def _enable_children(
        self, parent: Union[tk.Widget, None] = None, enable: bool = True
    ) -> None:
        """
        Enable or disable the children.

        :param parent: A tk.Widget that is our parent. If None then default to
         self.
        :param enable: Whether to enable or disable the children.
        :return: None.
        """
        if parent is None:
            parent = self
        for child in parent.winfo_children():
            if child.winfo_class() not in ("Frame", "LabelFrame"):
                try:
                    child.state(["!disabled" if enable else "disabled"])
                except AttributeError:
                    child.configure(state=tk.NORMAL if enable else tk.DISABLED)
            else:
                self._enable_children(parent, enable)

    @property
    def enabled(self) -> bool:
        """
        Get whether this widget is in normal mode or disabled mode. (grayed
        out and cannot interact with)

        :return: A bool, True if normal otherwise False.
        """
        return self._enabled

    @enabled.setter
    def enabled(self, new_state: bool) -> None:
        """
        Set whether this widget is in normal mode or disabled mode. (grayed
        out and cannot interact with)

        :param new_state: The new state (a bool) True for enabled and False
        for disabled.
        :return: None.
        """
        if not isinstance(new_state, bool):
            raise TypeError(
                f"new_state is not a bool! "
                f"(type passed in: {repr(type(new_state))})"
            )
        self._enabled = new_state
        self._enable_children(enable=self._enabled)

    def apply_style(self, style_name: str) -> None:
        """
        Apply a theme to this frame.

        :param style_name: The name of the theme as a str, ex. "Warning"
        :return: None.
        """
        if not isinstance(style_name, str):
            raise TypeError(
                f"style_name is not a str! "
                f"(type passed in: {repr(type(style_name))})"
            )
        self.configure(style=f"{style_name}.{self._style_root}")

Ancestors

  • tkinter.ttk.Frame
  • tkinter.ttk.Widget
  • tkinter.Widget
  • tkinter.BaseWidget
  • tkinter.Misc
  • tkinter.Pack
  • tkinter.Place
  • tkinter.Grid

Subclasses

Instance variables

var enabled : bool

Get whether this widget is in normal mode or disabled mode. (grayed out and cannot interact with)

:return: A bool, True if normal otherwise False.

Expand source code
@property
def enabled(self) -> bool:
    """
    Get whether this widget is in normal mode or disabled mode. (grayed
    out and cannot interact with)

    :return: A bool, True if normal otherwise False.
    """
    return self._enabled
var height : int

Get the height of the frame.

:return: An int which is the height of the frame.

Expand source code
@property
def height(self) -> int:
    """
    Get the height of the frame.

    :return: An int which is the height of the frame.
    """
    return self.cget("height")
var width : int

Get the width of the frame.

:return: An int which is the width of the frame.

Expand source code
@property
def width(self) -> int:
    """
    Get the width of the frame.

    :return: An int which is the width of the frame.
    """
    return self.cget("width")

Methods

def apply_style(self, style_name: str) ‑> NoneType

Apply a theme to this frame.

:param style_name: The name of the theme as a str, ex. "Warning" :return: None.

Expand source code
def apply_style(self, style_name: str) -> None:
    """
    Apply a theme to this frame.

    :param style_name: The name of the theme as a str, ex. "Warning"
    :return: None.
    """
    if not isinstance(style_name, str):
        raise TypeError(
            f"style_name is not a str! "
            f"(type passed in: {repr(type(style_name))})"
        )
    self.configure(style=f"{style_name}.{self._style_root}")
class ScrollableFrame (parent: Union[tkinter.Widget, tkinter.Tk, tkinter.Toplevel], x_scrolling: bool = False, y_scrolling: bool = True)

Ttk Frame widget is a container, used to group other widgets together.

Create a scrollable frame. MAKE SURE WHEN GRIDING WIDGETS TO THIS FRAME TO USE THE FRAME ATTRIBUTE INSTEAD OF THIS FRAME DIRECTLY.

:param parent: The parent of this frame. :param x_scrolling: A bool on whether to add a scrollbar in the x direction. :param y_scrolling: A bool on whether to add a scrollbar in the y direction.

Expand source code
class ScrollableFrame(Frame):
    def __init__(
        self,
        parent: Union[tk.Widget, Union[tk.Tk, tk.Toplevel]],
        x_scrolling: bool = False,
        y_scrolling: bool = True,
    ):
        """
        Create a scrollable frame.
        MAKE SURE WHEN GRIDING WIDGETS TO THIS FRAME TO USE THE FRAME
        ATTRIBUTE INSTEAD OF THIS FRAME DIRECTLY.

        :param parent: The parent of this frame.
        :param x_scrolling: A bool on whether to add a scrollbar in the x
         direction.
        :param y_scrolling: A bool on whether to add a scrollbar in the y
         direction.
        """
        # https://blog.teclado.com/tkinter-scrollable-frames/
        super().__init__(parent)
        self.canvas = tk.Canvas(self)
        if x_scrolling:
            x_scrollbar = ttk.Scrollbar(
                self, orient=tk.HORIZONTAL, command=self.canvas.xview
            )
        if y_scrolling:
            y_scrollbar = ttk.Scrollbar(
                self, orient=tk.VERTICAL, command=self.canvas.yview
            )
        self.frame = Frame(self.canvas)
        self.frame.bind(
            "<Configure>",
            lambda _: self.canvas.configure(scrollregion=self.canvas.bbox(tk.ALL)),
        )
        self.canvas.create_window((0, 0), window=self.frame, anchor=tk.NW)
        if x_scrolling:
            self.canvas.configure(xscrollcommand=x_scrollbar.set)
        if y_scrolling:
            self.canvas.configure(yscrollcommand=y_scrollbar.set)
        self.canvas.grid(row=0, column=0)
        if x_scrolling:
            x_scrollbar.grid(row=1, column=0, sticky=tk.NSEW)
        if y_scrolling:
            y_scrollbar.grid(row=0, column=1, sticky=tk.NSEW)
        # https://stackoverflow.com/a/37858368/10291933
        self.frame.bind("<Enter>", self._bind_to_mousewheel)
        self.frame.bind("<Leave>", self._unbind_to_mousewheel)
        self._shift_pressed = False
        # https://stackoverflow.com/a/8089241/10291933
        # http://www.tcl.tk/man/tcl8.4/TkCmd/keysyms.htm
        self.frame.bind_all("<Shift_L>", lambda _: self._set_shift_pressed(True))
        self.frame.bind_all("<Shift_R>", lambda _: self._set_shift_pressed(True))
        self.frame.bind_all(
            "<KeyRelease-Shift_L>", lambda _: self._set_shift_pressed(False)
        )
        self.frame.bind_all(
            "<KeyRelease-Shift_R>", lambda _: self._set_shift_pressed(False)
        )

    def _set_shift_pressed(self, is_pressed: bool) -> None:
        """
        Set whether shift is pressed or not.

        :param is_pressed: A bool.
        :return: None.
        """
        self._shift_pressed = is_pressed

    def _bind_to_mousewheel(self, event) -> None:
        """
        Bind to the mousewheel.

        :param event: An event that Tkinter passes in.
        :return: None
        """
        self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)

    def _unbind_to_mousewheel(self, event) -> None:
        """
        Unbind to the mousewheel.

        :param event: An event that Tkinter passes in.
        :return: None
        """
        self.canvas.unbind_all("<MouseWheel>")

    def _on_mousewheel(self, event) -> None:
        """
        A callback for the mousewheel to scroll.

        :param event: An event that Tkinter passes in.
        :return: None.
        """
        if Platform.on_aqua(self):
            scroll = str(int(-1 * event.delta))
        else:
            scroll = str(int(-1 * (event.delta / 120)))
        if self._shift_pressed:
            self.canvas.xview_scroll(scroll, tk.UNITS)
        else:
            self.canvas.yview_scroll(scroll, tk.UNITS)

    def _enable_children(
        self, parent: Union[tk.Widget, None] = None, enable: bool = True
    ) -> None:
        """
        Enable or disable the children.

        :param parent: A tk.Widget that is our parent. If None then default to
         self.
        :param enable: Whether to enable or disable the children.
        :return: None.
        """
        if parent is None:
            parent = self.frame
        for child in parent.winfo_children():
            if child.winfo_class() not in ("Frame", "LabelFrame"):
                try:
                    child.state(["!disabled" if enable else "disabled"])
                except AttributeError:
                    child.configure(state=tk.NORMAL if enable else tk.DISABLED)
            else:
                self._enable_children(parent, enable)

    def apply_style(self, style_name: str) -> None:
        """
        Apply a theme to this frame.

        :param style_name: The name of the theme as a str, ex. "Warning"
        :return: None.
        """
        if not isinstance(style_name, str):
            raise TypeError(
                f"style_name is not a str! "
                f"(type passed in: {repr(type(style_name))})"
            )
        self.frame.configure(style=f"{style_name}.{self._style_root}")

Ancestors

  • Frame
  • tkinter.ttk.Frame
  • tkinter.ttk.Widget
  • tkinter.Widget
  • tkinter.BaseWidget
  • tkinter.Misc
  • tkinter.Pack
  • tkinter.Place
  • tkinter.Grid

Inherited members