Skip to content

[tkinter.ttk] Added type annotations for Style, fix some other incomplete parts #14348

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
168 changes: 153 additions & 15 deletions stdlib/tkinter/ttk.pyi
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import _tkinter
import sys
import tkinter
from _typeshed import Incomplete, MaybeNone
from collections.abc import Callable
from _typeshed import MaybeNone
from collections.abc import Callable, Iterable
from tkinter.font import _FontDescription
from typing import Any, Literal, TypedDict, overload
from typing_extensions import TypeAlias
from typing_extensions import Never, TypeAlias, Unpack

__all__ = [
"Button",
Expand Down Expand Up @@ -35,7 +36,7 @@ __all__ = [
]

def tclobjs_to_py(adict: dict[Any, Any]) -> dict[Any, Any]: ...
def setup_master(master=None): ...
def setup_master(master: tkinter.Misc | None = None): ...

_Padding: TypeAlias = (
tkinter._ScreenUnits
Expand All @@ -48,19 +49,156 @@ _Padding: TypeAlias = (
# from ttk_widget (aka ttk::widget) manual page, differs from tkinter._Compound
_TtkCompound: TypeAlias = Literal["", "text", "image", tkinter._Compound]

# Last item (option value to apply) varies between different options so use Any.
# It could also be any iterable with items matching the tuple, but that case
# hasn't been added here for consistency with _Padding above.
_Statespec: TypeAlias = tuple[Unpack[tuple[str, ...]], Any]
_ImageStatespec: TypeAlias = tuple[Unpack[tuple[str, ...]], tkinter._ImageSpec]
_VsapiStatespec: TypeAlias = tuple[Unpack[tuple[str, ...]], int]

class _Layout(TypedDict, total=False):
side: Literal["left", "right", "top", "bottom"]
sticky: str # consists of letters 'n', 's', 'w', 'e', may contain repeats, may be empty
unit: Literal[0, 1] | bool
children: _LayoutSpec
# Note: there seem to be some other undocumented keys sometimes

# This could be any sequence when passed as a parameter but will always be a list when returned.
_LayoutSpec: TypeAlias = list[tuple[str, _Layout | None]]

# Keep these in sync with the appropriate methods in Style
class _ElementCreateImageKwargs(TypedDict, total=False):
border: _Padding
height: tkinter._ScreenUnits
padding: _Padding
sticky: str
width: tkinter._ScreenUnits

_ElementCreateArgsCrossPlatform: TypeAlias = (
# Could be any sequence here but types are not homogenous so just type it as tuple
tuple[Literal["image"], tkinter._ImageSpec, Unpack[tuple[_ImageStatespec, ...]], _ElementCreateImageKwargs]
| tuple[Literal["from"], str, str]
| tuple[Literal["from"], str] # (fromelement is optional)
)
if sys.platform == "win32" and sys.version_info >= (3, 13):
class _ElementCreateVsapiKwargsPadding(TypedDict, total=False):
padding: _Padding

class _ElementCreateVsapiKwargsMargin(TypedDict, total=False):
padding: _Padding

class _ElementCreateVsapiKwargsSize(TypedDict):
width: tkinter._ScreenUnits
height: tkinter._ScreenUnits

_ElementCreateVsapiKwargsDict: TypeAlias = (
_ElementCreateVsapiKwargsPadding | _ElementCreateVsapiKwargsMargin | _ElementCreateVsapiKwargsSize
)
_ElementCreateArgs: TypeAlias = (
_ElementCreateArgsCrossPlatform
| tuple[Literal["vsapi"], str, int, _ElementCreateVsapiKwargsDict]
| tuple[Literal["vsapi"], str, int, _VsapiStatespec, _ElementCreateVsapiKwargsDict]
)
else:
_ElementCreateArgs: TypeAlias = _ElementCreateArgsCrossPlatform
_ThemeSettingsValue = TypedDict(
"_ThemeSettingsValue",
{
"configure": dict[str, Any],
"map": dict[str, Iterable[_Statespec]],
"layout": _LayoutSpec,
"element create": _ElementCreateArgs,
},
total=False,
)
_ThemeSettings: TypeAlias = dict[str, _ThemeSettingsValue]

class Style:
master: Incomplete
master: tkinter.Misc
tk: _tkinter.TkappType
def __init__(self, master: tkinter.Misc | None = None) -> None: ...
def configure(self, style, query_opt=None, **kw): ...
def map(self, style, query_opt=None, **kw): ...
def lookup(self, style, option, state=None, default=None): ...
def layout(self, style, layoutspec=None): ...
def element_create(self, elementname, etype, *args, **kw) -> None: ...
def element_names(self): ...
def element_options(self, elementname): ...
def theme_create(self, themename, parent=None, settings=None) -> None: ...
def theme_settings(self, themename, settings) -> None: ...
# For these methods, values given vary between options. Returned values
# seem to be str, but this might not always be the case.
@overload
def configure(self, style: str) -> dict[str, Any] | None: ... # Returns None if no configuration.
@overload
def configure(self, style: str, query_opt: str, **kw: Any) -> Any: ...
@overload
def configure(self, style: str, query_opt: None = None, **kw: Any) -> None: ...
@overload
def map(self, style: str, query_opt: str) -> _Statespec: ...
@overload
def map(self, style: str, query_opt: None = None, **kw: Iterable[_Statespec]) -> dict[str, _Statespec]: ...
def lookup(self, style: str, option: str, state: Iterable[str] | None = None, default: Any | None = None) -> Any: ...
@overload
def layout(self, style: str, layoutspec: _LayoutSpec) -> list[Never]: ... # Always seems to return an empty list
@overload
def layout(self, style: str, layoutspec: None = None) -> _LayoutSpec: ...
@overload
def element_create(
self,
elementname: str,
etype: Literal["image"],
# This is technically positional-only and others above are positional-or-keyword.
# But as this isn't optional, the ones above must also be positional-only.
# However, this is inconsistent with the implementation (as it technically has the ones above as positional-or-keyword
# and this one in *args and then it checks that this is present but to get this into the *args, the previous 2 must
# also be given positionally so are in practise positional-only). Therefore, stubtest complains.
# (I'm not sure if this is the best way to handle this - ignoring the
# stubtest error would result in stubs that actually match the code)
_default_image: tkinter._ImageSpec,
*imagespec: _ImageStatespec,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda awkward but you could do *imagespec: *tuple[tkinter._ImageSpec, *tuple[_ImageStatespec, ...]]. Not sure if this would be problematic to use though for code completion though.

border: _Padding = ...,
height: tkinter._ScreenUnits = ...,
padding: _Padding = ...,
sticky: str = ...,
width: tkinter._ScreenUnits = ...,
) -> None: ...
@overload
def element_create(self, elementname: str, etype: Literal["from"], themename: str, fromelement: str = ..., /) -> None: ...
if sys.platform == "win32" and sys.version_info >= (3, 13): # and tk version >= 8.6
# margin, padding, and (width + height) are mutually exclusive. width
# and height must either both be present or not present at all. Note:
# There are other undocumented options if you look at ttk's source code.
@overload
def element_create(
self,
elementname: str,
etype: Literal["vsapi"],
_class: str,
_part: int,
_vs_statespec: _VsapiStatespec = ...,
*,
padding: _Padding = ...,
) -> None: ...
@overload
def element_create(
self,
elementname: str,
etype: Literal["vsapi"],
_class: str,
_part: int,
_vs_statespec: _VsapiStatespec = ...,
*,
margin: _Padding = ...,
) -> None: ...
@overload
def element_create(
self,
elementname: str,
etype: Literal["vsapi"],
_class: str,
_part: int,
_vs_statespec: _VsapiStatespec = ...,
*,
width: tkinter._ScreenUnits,
height: tkinter._ScreenUnits,
) -> None: ...

def element_names(self) -> tuple[str, ...]: ...
def element_options(self, elementname: str) -> tuple[str, ...]: ...
def theme_create(self, themename: str, parent: str | None = None, settings: _ThemeSettings | None = None) -> None: ...
def theme_settings(self, themename: str, settings: _ThemeSettings) -> None: ...
def theme_names(self) -> tuple[str, ...]: ...
@overload
def theme_use(self, themename: str) -> None: ...
Expand Down Expand Up @@ -615,7 +753,7 @@ class Panedwindow(Widget, tkinter.PanedWindow):
) -> dict[str, tuple[str, str, str, Any, Any]] | None: ...
@overload
def config(self, cnf: str) -> tuple[str, str, str, Any, Any]: ...
forget: Incomplete
forget = tkinter.PanedWindow.forget
def insert(self, pos, child, **kw) -> None: ...
def pane(self, pane, option=None, **kw): ...
def sashpos(self, index, newpos=None): ...
Expand Down
Loading