Skip to content

Commit 2c37551

Browse files
authored
make tkinter.Event generic and add missing type hints to bind methods (#4347)
1 parent 51fd8b2 commit 2c37551

File tree

2 files changed

+107
-16
lines changed

2 files changed

+107
-16
lines changed

stdlib/3/tkinter/__init__.pyi

Lines changed: 91 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import sys
22
from enum import Enum
33
from tkinter.constants import * # noqa: F403
44
from types import TracebackType
5-
from typing import Any, Callable, Dict, Optional, Tuple, Type, Union
5+
from typing import Any, Callable, Dict, Generic, Optional, Tuple, Type, TypeVar, Union, overload
6+
from typing_extensions import Literal
67

78
TclError: Any
89
wantobjects: Any
@@ -52,7 +53,10 @@ if sys.version_info >= (3, 6):
5253
VirtualEvent: str = ...
5354
Visibility: str = ...
5455

55-
class Event:
56+
# Events considered covariant because you should never assign to event.widget.
57+
_W = TypeVar("_W", covariant=True, bound="Misc")
58+
59+
class Event(Generic[_W]):
5660
serial: int
5761
num: int
5862
focus: bool
@@ -73,7 +77,7 @@ class Event:
7377
type: EventType
7478
else:
7579
type: str
76-
widget: Misc
80+
widget: _W
7781
delta: int
7882

7983
def NoDefaultRoot(): ...
@@ -119,6 +123,8 @@ getdouble: Any
119123

120124
def getboolean(s): ...
121125

126+
# This class is the base class of all widgets. Don't use BaseWidget or Widget
127+
# for that because Tk doesn't inherit from Widget or BaseWidget.
122128
class Misc:
123129
def destroy(self): ...
124130
def deletecommand(self, name): ...
@@ -222,12 +228,47 @@ class Misc:
222228
def update(self): ...
223229
def update_idletasks(self): ...
224230
def bindtags(self, tagList: Optional[Any] = ...): ...
225-
def bind(self, sequence: Optional[Any] = ..., func: Optional[Any] = ..., add: Optional[Any] = ...): ...
226-
def unbind(self, sequence, funcid: Optional[Any] = ...): ...
227-
def bind_all(self, sequence: Optional[Any] = ..., func: Optional[Any] = ..., add: Optional[Any] = ...): ...
228-
def unbind_all(self, sequence): ...
229-
def bind_class(self, className, sequence: Optional[Any] = ..., func: Optional[Any] = ..., add: Optional[Any] = ...): ...
230-
def unbind_class(self, className, sequence): ...
231+
# bind with isinstance(func, str) doesn't return anything, but all other
232+
# binds do. The default value of func is not str.
233+
@overload
234+
def bind(
235+
self,
236+
sequence: Optional[str] = ...,
237+
func: Optional[Callable[[Event[Misc]], Optional[Literal["break"]]]] = ...,
238+
add: Optional[bool] = ...,
239+
) -> str: ...
240+
@overload
241+
def bind(self, sequence: Optional[str], func: str, add: Optional[bool] = ...) -> None: ...
242+
@overload
243+
def bind(self, *, func: str, add: Optional[bool] = ...) -> None: ...
244+
# There's no way to know what type of widget bind_all and bind_class
245+
# callbacks will get, so those are Misc.
246+
@overload
247+
def bind_all(
248+
self,
249+
sequence: Optional[str] = ...,
250+
func: Optional[Callable[[Event[Misc]], Optional[Literal["break"]]]] = ...,
251+
add: Optional[bool] = ...,
252+
) -> str: ...
253+
@overload
254+
def bind_all(self, sequence: Optional[str], func: str, add: Optional[bool] = ...) -> None: ...
255+
@overload
256+
def bind_all(self, *, func: str, add: Optional[bool] = ...) -> None: ...
257+
@overload
258+
def bind_class(
259+
self,
260+
className: str,
261+
sequence: Optional[str] = ...,
262+
func: Optional[Callable[[Event[Misc]], Optional[Literal["break"]]]] = ...,
263+
add: Optional[bool] = ...,
264+
) -> str: ...
265+
@overload
266+
def bind_class(self, className: str, sequence: Optional[str], func: str, add: Optional[bool] = ...) -> None: ...
267+
@overload
268+
def bind_class(self, className: str, *, func: str, add: Optional[bool] = ...) -> None: ...
269+
def unbind(self, sequence: str, funcid: Optional[str] = ...) -> None: ...
270+
def unbind_all(self, sequence: str) -> None: ...
271+
def unbind_class(self, className: str, sequence: str) -> None: ...
231272
def mainloop(self, n: int = ...): ...
232273
def quit(self): ...
233274
def nametowidget(self, name): ...
@@ -419,7 +460,22 @@ class BaseWidget(Misc):
419460
def __init__(self, master, widgetName, cnf=..., kw=..., extra=...): ...
420461
def destroy(self): ...
421462

422-
class Widget(BaseWidget, Pack, Place, Grid): ...
463+
# This class represents any widget except Toplevel or Tk.
464+
class Widget(BaseWidget, Pack, Place, Grid):
465+
# Allow bind callbacks to take e.g. Event[Label] instead of Event[Misc].
466+
# Tk and Toplevel get notified for their child widgets' events, but other
467+
# widgets don't.
468+
@overload
469+
def bind(
470+
self: _W,
471+
sequence: Optional[str] = ...,
472+
func: Optional[Callable[[Event[_W]], Optional[Literal["break"]]]] = ...,
473+
add: Optional[bool] = ...,
474+
) -> str: ...
475+
@overload
476+
def bind(self, sequence: Optional[str], func: str, add: Optional[bool] = ...) -> None: ...
477+
@overload
478+
def bind(self, *, func: str, add: Optional[bool] = ...) -> None: ...
423479

424480
class Toplevel(BaseWidget, Wm):
425481
def __init__(self, master: Optional[Any] = ..., cnf=..., **kw): ...
@@ -440,8 +496,19 @@ class Canvas(Widget, XView, YView):
440496
def addtag_overlapping(self, newtag, x1, y1, x2, y2): ...
441497
def addtag_withtag(self, newtag, tagOrId): ...
442498
def bbox(self, *args): ...
443-
def tag_unbind(self, tagOrId, sequence, funcid: Optional[Any] = ...): ...
444-
def tag_bind(self, tagOrId, sequence: Optional[Any] = ..., func: Optional[Any] = ..., add: Optional[Any] = ...): ...
499+
@overload
500+
def tag_bind(
501+
self,
502+
tagOrId: Union[str, int],
503+
sequence: Optional[str] = ...,
504+
func: Optional[Callable[[Event[Canvas]], Optional[Literal["break"]]]] = ...,
505+
add: Optional[bool] = ...,
506+
) -> str: ...
507+
@overload
508+
def tag_bind(self, tagOrId: Union[str, int], sequence: Optional[str], func: str, add: Optional[bool] = ...) -> None: ...
509+
@overload
510+
def tag_bind(self, tagOrId: Union[str, int], *, func: str, add: Optional[bool] = ...) -> None: ...
511+
def tag_unbind(self, tagOrId: Union[str, int], sequence: str, funcid: Optional[str] = ...) -> None: ...
445512
def canvasx(self, screenx, gridspacing: Optional[Any] = ...): ...
446513
def canvasy(self, screeny, gridspacing: Optional[Any] = ...): ...
447514
def coords(self, *args): ...
@@ -660,8 +727,18 @@ class Text(Widget, XView, YView):
660727
): ...
661728
def see(self, index): ...
662729
def tag_add(self, tagName, index1, *args): ...
663-
def tag_unbind(self, tagName, sequence, funcid: Optional[Any] = ...): ...
664-
def tag_bind(self, tagName, sequence, func, add: Optional[Any] = ...): ...
730+
# tag_bind stuff is very similar to Canvas
731+
@overload
732+
def tag_bind(
733+
self,
734+
tagName: str,
735+
sequence: Optional[str],
736+
func: Optional[Callable[[Event[Text]], Optional[Literal["break"]]]],
737+
add: Optional[bool] = ...,
738+
) -> str: ...
739+
@overload
740+
def tag_bind(self, tagName: str, sequence: Optional[str], func: str, add: Optional[bool] = ...) -> None: ...
741+
def tag_unbind(self, tagName: str, sequence: str, funcid: Optional[str] = ...) -> None: ...
665742
def tag_cget(self, tagName, option): ...
666743
def tag_configure(self, tagName, cnf: Optional[Any] = ..., **kw): ...
667744
tag_config: Any

stdlib/3/tkinter/ttk.pyi

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import sys
22
import tkinter
3-
from typing import Any, List, Optional
3+
from tkinter import Event
4+
from typing import Any, Callable, Dict, List, Optional, Tuple, Type, TypeVar, Union, overload
5+
from typing_extensions import Literal
46

57
def tclobjs_to_py(adict): ...
68
def setup_master(master: Optional[Any] = ...): ...
@@ -145,7 +147,19 @@ class Treeview(Widget, tkinter.XView, tkinter.YView):
145147
def selection_remove(self, items): ...
146148
def selection_toggle(self, items): ...
147149
def set(self, item, column: Optional[Any] = ..., value: Optional[Any] = ...): ...
148-
def tag_bind(self, tagname, sequence: Optional[Any] = ..., callback: Optional[Any] = ...): ...
150+
# There's no tag_unbind() or 'add' argument for whatever reason.
151+
# Also, it's 'callback' instead of 'func' here.
152+
@overload
153+
def tag_bind(
154+
self,
155+
tagname: str,
156+
sequence: Optional[str] = ...,
157+
callback: Optional[Callable[[Event[Treeview]], Optional[Literal["break"]]]] = ...,
158+
) -> str: ...
159+
@overload
160+
def tag_bind(self, tagname: str, sequence: Optional[str], callback: str) -> None: ...
161+
@overload
162+
def tag_bind(self, tagname: str, *, callback: str) -> None: ...
149163
def tag_configure(self, tagname, option: Optional[Any] = ..., **kw): ...
150164
def tag_has(self, tagname, item: Optional[Any] = ...): ...
151165

0 commit comments

Comments
 (0)