TomSchimansky / CustomTkinter

A modern and customizable python UI-library based on Tkinter
MIT License
11.58k stars 1.08k forks source link

Using strict type hinting with CustomTkinter #2181

Open ghost opened 10 months ago

ghost commented 10 months ago

I recently started developing with strict type hinting switched on. This has thrown up lots of issues with calls to CustomTkinter methods. Is this a known issue or have I done something wrong? thanks

dipeshSam commented 10 months ago

@Barrowcroft Could you please elaborate a bit what mechanism are you using for type hinting and what error are you facing? Code snippets, error screenshot may help.

ghost commented 10 months ago

Thanks for your reply.

I am using type checking by putting " "python.analysis.typeCheckingMode": "strict", " into my settings file.

When I include CustomTkinter I get the message "Stub file not found for "customtkinter"

I also get issues any time I access the 'grid' or 'configure' methods. Messages "Type of "configure" is partially unknown" and "Type of "grid" is partially unknown" respectively.

I get similar problems with '.mainloop',

It may well be simply a matter of my misunderstanding type hinting. I should add that everything functions correctly, but the type checker complains a lot.

thanks

dipeshSam commented 10 months ago

@Barrowcroft, In the customtkinter library, type hints are currently provided to the CTk-specific widgets and methods. Notably, the hints do not extend to encompass classes or functions that remain identical in both the Tkinter and customtkinter frameworks. This facility will be addressed in the forthcoming version of the customtkinter.

For users desiring comprehensive type hints, a recommended approach involves leveraging Python Interfaces. To implement type hints consistently across all scenarios, one can create a customtkinter.pyi file within the current working directory of the user. This file should contain all requisite type hints, ensuring uniformity and clarity in various projects.

Guide and demonstration:

customtkinter.pyi file:

from typing import Union, Tuple, Callable, Optional, Literal
import PIL 

class CTk:
    def __init__(self, fg_color: str = None) -> None:
        ...

    def geometry(self, width_x_height: str) -> None:
        """Sets geometry of the window in width x height form."""
        ...

class CTkImage:
    def __init__(self, light_image: PIL.Image | None, dark_image: PIL.Image | None, size: Tuple[int, int]) -> None: ...
    ...

class CTkFont:
    def __init__(self, family: str, weight: str, size: int) -> None:
        ...

class Variable:
    ...

class CTkButton:
    def __init__(self,
                 master: any,
                 width: int = 140,
                 height: int = 28,
                 corner_radius: Optional[int] = None,
                 border_width: Optional[int] = None,
                 border_spacing: int = 2,

                 bg_color: Union[str, Tuple[str, str]] = "transparent",
                 fg_color: Optional[Union[str, Tuple[str, str]]] = None,
                 hover_color: Optional[Union[str, Tuple[str, str]]] = None,
                 border_color: Optional[Union[str, Tuple[str, str]]] = None,
                 text_color: Optional[Union[str, Tuple[str, str]]] = None,
                 text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,

                 background_corner_colors: Union[Tuple[Union[str, Tuple[str, str]]], None] = None,
                 round_width_to_even_floats: bool = True,
                 round_height_to_even_floats: bool = True,

                 text: str = "CTkButton",
                 font: Optional[Union[tuple, CTkFont]] = None,
                 textvariable: Union[Variable, None] = None,
                 image: Union[CTkImage, CTkImage, None] = None,
                 state: str = "normal",
                 hover: bool = True,
                 command: Union[Callable[[], None], None] = None,
                 compound: str = "left",
                 anchor: str = "center",
                 **kwargs):

        ...

    def destroy(self):
        """Deletes the object completely and frees up space."""
        ...

    def configure(self,
            corner_radius: float,
            border_width: float,
            border_spacing: float, 
            fg_color: str | tuple[str, str],
            hover_color: str | tuple[str, str],
            border_color: str | tuple[str, str],
            text_color: str | tuple[str, str],
            text_color_disabled: str | tuple[str, str],
            background_corner_colors: Tuple[str, str, str, str],
            text: str,
            font: CTkFont | Tuple[str, int, str],
            textvariable: Variable,
            image: CTkImage | None,
            state: str,
            hover: bool,
            command: Callable,
            compound: Literal["left", "right", "top", "bottom"],
            anchor: str,
            kwargs
        ):

        ...

    def cget(self,
            corner_radius: float,
            border_width: float,
            border_spacing: float, 
            fg_color: str | tuple[str, str],
            hover_color: str | tuple[str, str],
            border_color: str | tuple[str, str],
            text_color: str | tuple[str, str],
            text_color_disabled: str | tuple[str, str],
            background_corner_colors: Tuple[str, str, str, str],
            text: str,
            font: CTkFont | Tuple[str, int, str],
            textvariable: Variable,
            image: CTkImage | None,
            state: str,
            hover: bool,
            command: Callable,
            compound: Literal["left", "right", "top", "bottom"],
            anchor: str,
            kwargs
        ) -> any:
     ...

    def invoke(self):
        """ calls command function if button is not disabled """
        ...

    def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
        """ called on the tkinter.Canvas """
        ...

    def unbind(self, sequence: str = None, funcid: str = None):
        """ called on the tkinter.Label and tkinter.Canvas """
        ...

    def focus(self):
        """Takes the button in focus."""
        ...
    def focus_set(self):
        """Takes the button in focus."""
        ...

    def focus_force(self):
        """Takes the button in focus forced."""
        ...

    def grid(self,
            column: float,
            columnspan: float,
            in_: CTk,
            ipadx: float,
            ipady: float,
            padx: float,
            pady: float,
            row: float,
            rowspan: float,
            sticky: Literal["n", "s", "e", "w", "nw", "ne", "sw", "se", "ew", "ns", "nsew"]
        ):

        ...

Output Demonstration: configure hover box configure_hover_box

configure typing list configure_typing_box

grid typing box grid_typing_box

grid typing list grid_typing_list

grid typing suggestion grid_typing_suggestion

You can add more methods, classes in the customtkinter.pyi file for type hints in your projects as per your requirements. Hope, it will be helpful for you.

ghost commented 10 months ago

Thank you so much. That is indeed really helpful. I will try and implement it that way, and see how it goes.

dipeshSam commented 10 months ago

Thank you so much. That is indeed really helpful. I will try and implement it that way, and see how it goes.

@Barrowcroft You are most welcome :)