tklive 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tklive-0.1.0/LICENSE +21 -0
- tklive-0.1.0/PKG-INFO +58 -0
- tklive-0.1.0/README.md +46 -0
- tklive-0.1.0/pyproject.toml +15 -0
- tklive-0.1.0/setup.cfg +4 -0
- tklive-0.1.0/tklive/__init__.py +4 -0
- tklive-0.1.0/tklive/core.py +151 -0
- tklive-0.1.0/tklive/state.py +257 -0
- tklive-0.1.0/tklive.egg-info/PKG-INFO +58 -0
- tklive-0.1.0/tklive.egg-info/SOURCES.txt +11 -0
- tklive-0.1.0/tklive.egg-info/dependency_links.txt +1 -0
- tklive-0.1.0/tklive.egg-info/requires.txt +1 -0
- tklive-0.1.0/tklive.egg-info/top_level.txt +1 -0
tklive-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tklive-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tklive
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Live hot-reload for tkinter and customtkinter apps
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/yourusername/tklive
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: watchdog>=3.0.0
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
13
|
+
# tklive
|
|
14
|
+
|
|
15
|
+
Live hot-reload für tkinter und customtkinter Apps.
|
|
16
|
+
Speichere deine Datei → UI updated sich sofort ohne das Fenster zu schließen.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install tklive
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Verwendung
|
|
25
|
+
|
|
26
|
+
Füge **zwei Zeilen** zu deinem Projekt hinzu:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import tkinter as tk
|
|
30
|
+
from tklive import live
|
|
31
|
+
|
|
32
|
+
root = tk.Tk()
|
|
33
|
+
|
|
34
|
+
# ... dein gesamter normaler Code bleibt unverändert ...
|
|
35
|
+
|
|
36
|
+
live(root, __file__) # ← eine Zeile ans Ende
|
|
37
|
+
root.mainloop()
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Speichere die Datei → UI reloaded live. Oder **Ctrl+R** für manuellen Reload.
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- ✅ Fenster bleibt offen – kein Neustart
|
|
45
|
+
- ✅ State-Erhalt (Entry, Text, Tabs, Scrollposition, Fenstergröße)
|
|
46
|
+
- ✅ customtkinter Support (CTkEntry, CTkTextbox, CTkSlider, CTkTabview ...)
|
|
47
|
+
- ✅ ttk Support (Notebook, Treeview, Combobox ...)
|
|
48
|
+
- ✅ Multi-File Support
|
|
49
|
+
|
|
50
|
+
## Mehrere Dateien beobachten
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
live(root, __file__, watch_files=["ui.py", "components.py"])
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Lizenz
|
|
57
|
+
|
|
58
|
+
MIT
|
tklive-0.1.0/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# tklive
|
|
2
|
+
|
|
3
|
+
Live hot-reload für tkinter und customtkinter Apps.
|
|
4
|
+
Speichere deine Datei → UI updated sich sofort ohne das Fenster zu schließen.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install tklive
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Verwendung
|
|
13
|
+
|
|
14
|
+
Füge **zwei Zeilen** zu deinem Projekt hinzu:
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
import tkinter as tk
|
|
18
|
+
from tklive import live
|
|
19
|
+
|
|
20
|
+
root = tk.Tk()
|
|
21
|
+
|
|
22
|
+
# ... dein gesamter normaler Code bleibt unverändert ...
|
|
23
|
+
|
|
24
|
+
live(root, __file__) # ← eine Zeile ans Ende
|
|
25
|
+
root.mainloop()
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Speichere die Datei → UI reloaded live. Oder **Ctrl+R** für manuellen Reload.
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- ✅ Fenster bleibt offen – kein Neustart
|
|
33
|
+
- ✅ State-Erhalt (Entry, Text, Tabs, Scrollposition, Fenstergröße)
|
|
34
|
+
- ✅ customtkinter Support (CTkEntry, CTkTextbox, CTkSlider, CTkTabview ...)
|
|
35
|
+
- ✅ ttk Support (Notebook, Treeview, Combobox ...)
|
|
36
|
+
- ✅ Multi-File Support
|
|
37
|
+
|
|
38
|
+
## Mehrere Dateien beobachten
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
live(root, __file__, watch_files=["ui.py", "components.py"])
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Lizenz
|
|
45
|
+
|
|
46
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tklive"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Live hot-reload for tkinter and customtkinter apps"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.8"
|
|
12
|
+
dependencies = ["watchdog>=3.0.0"]
|
|
13
|
+
|
|
14
|
+
[project.urls]
|
|
15
|
+
Homepage = "https://github.com/yourusername/tklive"
|
tklive-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
import traceback
|
|
5
|
+
import tkinter as tk
|
|
6
|
+
|
|
7
|
+
from .state import save_state, restore_state
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
from watchdog.observers import Observer
|
|
11
|
+
from watchdog.events import FileSystemEventHandler
|
|
12
|
+
HAS_WATCHDOG = True
|
|
13
|
+
except ImportError:
|
|
14
|
+
HAS_WATCHDOG = False
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _FileHandler(FileSystemEventHandler):
|
|
18
|
+
def __init__(self, paths, callback):
|
|
19
|
+
self._paths = [os.path.abspath(p) for p in paths]
|
|
20
|
+
self._callback = callback
|
|
21
|
+
self._last = 0
|
|
22
|
+
|
|
23
|
+
def on_modified(self, event):
|
|
24
|
+
if not event.is_directory and event.src_path.endswith(".py"):
|
|
25
|
+
if os.path.abspath(event.src_path) in self._paths or not self._paths:
|
|
26
|
+
now = time.time()
|
|
27
|
+
if now - self._last > 0.5:
|
|
28
|
+
self._last = now
|
|
29
|
+
self._callback()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _clear_widgets(root):
|
|
33
|
+
for w in root.winfo_children():
|
|
34
|
+
w.destroy()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def live(root: tk.Tk, script_path: str, watch_files: list = None, auto: bool = True):
|
|
38
|
+
"""
|
|
39
|
+
Aktiviert live-reload für ein tkinter Script.
|
|
40
|
+
|
|
41
|
+
Verwendung:
|
|
42
|
+
live(root, __file__)
|
|
43
|
+
|
|
44
|
+
Mehrere Dateien beobachten:
|
|
45
|
+
live(root, __file__, watch_files=["ui.py", "components.py"])
|
|
46
|
+
|
|
47
|
+
Parameter:
|
|
48
|
+
root tkinter Root-Fenster
|
|
49
|
+
script_path Pfad zum Hauptscript (__file__ übergeben)
|
|
50
|
+
watch_files Weitere Dateien die beobachtet werden sollen (optional)
|
|
51
|
+
auto Automatisch neu laden wenn Datei gespeichert wird
|
|
52
|
+
"""
|
|
53
|
+
abs_path = os.path.abspath(script_path)
|
|
54
|
+
folder = os.path.dirname(abs_path)
|
|
55
|
+
|
|
56
|
+
# Alle zu beobachtenden Dateien sammeln
|
|
57
|
+
all_files = [abs_path]
|
|
58
|
+
if watch_files:
|
|
59
|
+
for f in watch_files:
|
|
60
|
+
p = os.path.abspath(f)
|
|
61
|
+
if p not in all_files:
|
|
62
|
+
all_files.append(p)
|
|
63
|
+
|
|
64
|
+
def do_reload():
|
|
65
|
+
try:
|
|
66
|
+
state = save_state(root)
|
|
67
|
+
root.after(0, lambda: _do_rebuild(state))
|
|
68
|
+
except Exception:
|
|
69
|
+
traceback.print_exc()
|
|
70
|
+
|
|
71
|
+
def _do_rebuild(state):
|
|
72
|
+
try:
|
|
73
|
+
_clear_widgets(root)
|
|
74
|
+
|
|
75
|
+
script_globals = {
|
|
76
|
+
"__file__": abs_path,
|
|
77
|
+
"__name__": "__tkinter_reload__",
|
|
78
|
+
"__builtins__": __builtins__,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
import tkinter as _tk
|
|
82
|
+
original_tk = _tk.Tk
|
|
83
|
+
|
|
84
|
+
class _FakeTk:
|
|
85
|
+
def __new__(cls, *args, **kwargs):
|
|
86
|
+
return root
|
|
87
|
+
|
|
88
|
+
_tk.Tk = _FakeTk
|
|
89
|
+
|
|
90
|
+
# customtkinter CTk ebenfalls patchen
|
|
91
|
+
try:
|
|
92
|
+
import customtkinter as _ctk
|
|
93
|
+
original_ctk = _ctk.CTk
|
|
94
|
+
class _FakeCTk:
|
|
95
|
+
def __new__(cls, *args, **kwargs):
|
|
96
|
+
return root
|
|
97
|
+
_ctk.CTk = _FakeCTk
|
|
98
|
+
except ImportError:
|
|
99
|
+
original_ctk = None
|
|
100
|
+
_ctk = None
|
|
101
|
+
|
|
102
|
+
original_mainloop = root.mainloop
|
|
103
|
+
root.mainloop = lambda: None
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
with open(abs_path, "r", encoding="utf-8") as f:
|
|
107
|
+
source = f.read()
|
|
108
|
+
exec(compile(source, abs_path, "exec"), script_globals)
|
|
109
|
+
finally:
|
|
110
|
+
_tk.Tk = original_tk
|
|
111
|
+
root.mainloop = original_mainloop
|
|
112
|
+
if _ctk and original_ctk:
|
|
113
|
+
_ctk.CTk = original_ctk
|
|
114
|
+
|
|
115
|
+
restore_state(root, state)
|
|
116
|
+
|
|
117
|
+
except Exception:
|
|
118
|
+
_clear_widgets(root)
|
|
119
|
+
tb = traceback.format_exc()
|
|
120
|
+
tk.Label(root, text="Fehler beim Reload", fg="red",
|
|
121
|
+
font=("Arial", 14, "bold")).pack(pady=20)
|
|
122
|
+
t = tk.Text(root, width=80, height=20, bg="#1e1e1e", fg="#ff6b6b",
|
|
123
|
+
font=("Courier", 9))
|
|
124
|
+
t.pack(padx=10)
|
|
125
|
+
t.insert("1.0", tb)
|
|
126
|
+
|
|
127
|
+
root.bind_all("<Control-r>", lambda e: do_reload())
|
|
128
|
+
|
|
129
|
+
if auto:
|
|
130
|
+
if not HAS_WATCHDOG:
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
handler = _FileHandler(all_files, do_reload)
|
|
134
|
+
observer = Observer()
|
|
135
|
+
|
|
136
|
+
# Alle relevanten Ordner beobachten
|
|
137
|
+
watched_folders = set()
|
|
138
|
+
for f in all_files:
|
|
139
|
+
watched_folders.add(os.path.dirname(f))
|
|
140
|
+
|
|
141
|
+
for watched_folder in watched_folders:
|
|
142
|
+
observer.schedule(handler, watched_folder, recursive=False)
|
|
143
|
+
|
|
144
|
+
observer.daemon = True
|
|
145
|
+
observer.start()
|
|
146
|
+
|
|
147
|
+
def on_close():
|
|
148
|
+
observer.stop()
|
|
149
|
+
root.destroy()
|
|
150
|
+
|
|
151
|
+
root.protocol("WM_DELETE_WINDOW", on_close)
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Speichert und stellt Widget-Zustände wieder her.
|
|
3
|
+
Unterstützt: tkinter, ttk, customtkinter, ttkbootstrap
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import tkinter as tk
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from tkinter import ttk
|
|
10
|
+
HAS_TTK = True
|
|
11
|
+
except ImportError:
|
|
12
|
+
HAS_TTK = False
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
import customtkinter as ctk
|
|
16
|
+
HAS_CTK = True
|
|
17
|
+
except ImportError:
|
|
18
|
+
HAS_CTK = False
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def save_state(root: tk.Tk) -> dict:
|
|
22
|
+
state = {
|
|
23
|
+
"widgets": {},
|
|
24
|
+
"window": {
|
|
25
|
+
"geometry": root.geometry(),
|
|
26
|
+
"title": root.title(),
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
_collect(root, state["widgets"])
|
|
30
|
+
return state
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def restore_state(root: tk.Tk, state: dict):
|
|
34
|
+
if not state:
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
# Fenstergröße und Position wiederherstellen
|
|
38
|
+
if "window" in state:
|
|
39
|
+
try:
|
|
40
|
+
root.geometry(state["window"]["geometry"])
|
|
41
|
+
root.title(state["window"]["title"])
|
|
42
|
+
except Exception:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
if "widgets" in state:
|
|
46
|
+
_apply(root, state["widgets"])
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _widget_key(widget) -> str:
|
|
50
|
+
name = str(widget).split(".")[-1]
|
|
51
|
+
return str(widget) if not name else f"{type(widget).__name__}:{str(widget)}"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _collect(widget, state: dict):
|
|
55
|
+
key = _widget_key(widget)
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
# ── tkinter Standard ──────────────────────────────
|
|
59
|
+
if isinstance(widget, tk.Entry):
|
|
60
|
+
state[key] = ("entry", widget.get())
|
|
61
|
+
|
|
62
|
+
elif isinstance(widget, tk.Text):
|
|
63
|
+
state[key] = ("text", widget.get("1.0", tk.END), widget.yview()[0])
|
|
64
|
+
|
|
65
|
+
elif isinstance(widget, tk.Scale):
|
|
66
|
+
state[key] = ("scale", widget.get())
|
|
67
|
+
|
|
68
|
+
elif isinstance(widget, tk.Spinbox):
|
|
69
|
+
state[key] = ("spinbox", widget.get())
|
|
70
|
+
|
|
71
|
+
elif isinstance(widget, tk.Listbox):
|
|
72
|
+
selected = list(widget.curselection())
|
|
73
|
+
scroll = widget.yview()[0]
|
|
74
|
+
state[key] = ("listbox", selected, scroll)
|
|
75
|
+
|
|
76
|
+
elif isinstance(widget, tk.BooleanVar):
|
|
77
|
+
state[key] = ("boolvar", widget.get())
|
|
78
|
+
|
|
79
|
+
elif isinstance(widget, (tk.Checkbutton, tk.Radiobutton)):
|
|
80
|
+
try:
|
|
81
|
+
var = widget.cget("variable")
|
|
82
|
+
if var:
|
|
83
|
+
state[key] = ("varname", str(var))
|
|
84
|
+
except Exception:
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
# ── ttk ──────────────────────────────────────────
|
|
88
|
+
if HAS_TTK:
|
|
89
|
+
if isinstance(widget, ttk.Combobox):
|
|
90
|
+
state[key] = ("combobox", widget.get())
|
|
91
|
+
|
|
92
|
+
elif isinstance(widget, ttk.Entry):
|
|
93
|
+
state[key] = ("entry", widget.get())
|
|
94
|
+
|
|
95
|
+
elif isinstance(widget, ttk.Notebook):
|
|
96
|
+
state[key] = ("notebook", widget.index("current"))
|
|
97
|
+
|
|
98
|
+
elif isinstance(widget, ttk.Treeview):
|
|
99
|
+
selected = widget.selection()
|
|
100
|
+
state[key] = ("treeview", selected)
|
|
101
|
+
|
|
102
|
+
elif isinstance(widget, ttk.Scale):
|
|
103
|
+
state[key] = ("scale", widget.get())
|
|
104
|
+
|
|
105
|
+
elif isinstance(widget, ttk.Spinbox):
|
|
106
|
+
state[key] = ("spinbox", widget.get())
|
|
107
|
+
|
|
108
|
+
# ── customtkinter ─────────────────────────────────
|
|
109
|
+
if HAS_CTK:
|
|
110
|
+
if isinstance(widget, ctk.CTkEntry):
|
|
111
|
+
state[key] = ("ctk_entry", widget.get())
|
|
112
|
+
|
|
113
|
+
elif isinstance(widget, ctk.CTkTextbox):
|
|
114
|
+
state[key] = ("ctk_text", widget.get("1.0", tk.END))
|
|
115
|
+
|
|
116
|
+
elif isinstance(widget, ctk.CTkComboBox):
|
|
117
|
+
state[key] = ("ctk_combo", widget.get())
|
|
118
|
+
|
|
119
|
+
elif isinstance(widget, ctk.CTkSlider):
|
|
120
|
+
state[key] = ("ctk_slider", widget.get())
|
|
121
|
+
|
|
122
|
+
elif isinstance(widget, ctk.CTkCheckBox):
|
|
123
|
+
state[key] = ("ctk_check", widget.get())
|
|
124
|
+
|
|
125
|
+
elif isinstance(widget, ctk.CTkSwitch):
|
|
126
|
+
state[key] = ("ctk_switch", widget.get())
|
|
127
|
+
|
|
128
|
+
elif isinstance(widget, ctk.CTkSegmentedButton):
|
|
129
|
+
state[key] = ("ctk_segmented", widget.get())
|
|
130
|
+
|
|
131
|
+
elif isinstance(widget, ctk.CTkTabview):
|
|
132
|
+
try:
|
|
133
|
+
state[key] = ("ctk_tab", widget.get())
|
|
134
|
+
except Exception:
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
except Exception:
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
# Rekursiv durch alle Kinder
|
|
141
|
+
try:
|
|
142
|
+
children = widget.winfo_children()
|
|
143
|
+
except Exception:
|
|
144
|
+
children = []
|
|
145
|
+
|
|
146
|
+
for child in children:
|
|
147
|
+
_collect(child, state)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _apply(widget, state: dict):
|
|
151
|
+
key = _widget_key(widget)
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
if key in state:
|
|
155
|
+
entry = state[key]
|
|
156
|
+
kind = entry[0]
|
|
157
|
+
|
|
158
|
+
# ── tkinter Standard ──────────────────────────
|
|
159
|
+
if kind == "entry" and isinstance(widget, tk.Entry):
|
|
160
|
+
widget.delete(0, tk.END)
|
|
161
|
+
widget.insert(0, entry[1])
|
|
162
|
+
|
|
163
|
+
elif kind == "text" and isinstance(widget, tk.Text):
|
|
164
|
+
widget.delete("1.0", tk.END)
|
|
165
|
+
widget.insert("1.0", entry[1].rstrip("\n"))
|
|
166
|
+
widget.yview_moveto(entry[2])
|
|
167
|
+
|
|
168
|
+
elif kind == "scale" and isinstance(widget, tk.Scale):
|
|
169
|
+
widget.set(entry[1])
|
|
170
|
+
|
|
171
|
+
elif kind == "spinbox" and isinstance(widget, tk.Spinbox):
|
|
172
|
+
widget.delete(0, tk.END)
|
|
173
|
+
widget.insert(0, entry[1])
|
|
174
|
+
|
|
175
|
+
elif kind == "listbox" and isinstance(widget, tk.Listbox):
|
|
176
|
+
widget.selection_clear(0, tk.END)
|
|
177
|
+
for i in entry[1]:
|
|
178
|
+
widget.selection_set(i)
|
|
179
|
+
widget.yview_moveto(entry[2])
|
|
180
|
+
|
|
181
|
+
# ── ttk ───────────────────────────────────────
|
|
182
|
+
elif kind == "combobox" and HAS_TTK and isinstance(widget, ttk.Combobox):
|
|
183
|
+
widget.set(entry[1])
|
|
184
|
+
|
|
185
|
+
elif kind == "entry" and HAS_TTK and isinstance(widget, ttk.Entry):
|
|
186
|
+
widget.delete(0, tk.END)
|
|
187
|
+
widget.insert(0, entry[1])
|
|
188
|
+
|
|
189
|
+
elif kind == "notebook" and HAS_TTK and isinstance(widget, ttk.Notebook):
|
|
190
|
+
try:
|
|
191
|
+
widget.select(entry[1])
|
|
192
|
+
except Exception:
|
|
193
|
+
pass
|
|
194
|
+
|
|
195
|
+
elif kind == "treeview" and HAS_TTK and isinstance(widget, ttk.Treeview):
|
|
196
|
+
try:
|
|
197
|
+
widget.selection_set(entry[1])
|
|
198
|
+
except Exception:
|
|
199
|
+
pass
|
|
200
|
+
|
|
201
|
+
elif kind == "scale" and HAS_TTK and isinstance(widget, ttk.Scale):
|
|
202
|
+
widget.set(entry[1])
|
|
203
|
+
|
|
204
|
+
elif kind == "spinbox" and HAS_TTK and isinstance(widget, ttk.Spinbox):
|
|
205
|
+
widget.delete(0, tk.END)
|
|
206
|
+
widget.insert(0, entry[1])
|
|
207
|
+
|
|
208
|
+
# ── customtkinter ─────────────────────────────
|
|
209
|
+
elif kind == "ctk_entry" and HAS_CTK and isinstance(widget, ctk.CTkEntry):
|
|
210
|
+
widget.delete(0, tk.END)
|
|
211
|
+
widget.insert(0, entry[1])
|
|
212
|
+
|
|
213
|
+
elif kind == "ctk_text" and HAS_CTK and isinstance(widget, ctk.CTkTextbox):
|
|
214
|
+
widget.delete("1.0", tk.END)
|
|
215
|
+
widget.insert("1.0", entry[1].rstrip("\n"))
|
|
216
|
+
|
|
217
|
+
elif kind == "ctk_combo" and HAS_CTK and isinstance(widget, ctk.CTkComboBox):
|
|
218
|
+
widget.set(entry[1])
|
|
219
|
+
|
|
220
|
+
elif kind == "ctk_slider" and HAS_CTK and isinstance(widget, ctk.CTkSlider):
|
|
221
|
+
widget.set(entry[1])
|
|
222
|
+
|
|
223
|
+
elif kind == "ctk_check" and HAS_CTK and isinstance(widget, ctk.CTkCheckBox):
|
|
224
|
+
if entry[1]:
|
|
225
|
+
widget.select()
|
|
226
|
+
else:
|
|
227
|
+
widget.deselect()
|
|
228
|
+
|
|
229
|
+
elif kind == "ctk_switch" and HAS_CTK and isinstance(widget, ctk.CTkSwitch):
|
|
230
|
+
if entry[1]:
|
|
231
|
+
widget.select()
|
|
232
|
+
else:
|
|
233
|
+
widget.deselect()
|
|
234
|
+
|
|
235
|
+
elif kind == "ctk_segmented" and HAS_CTK and isinstance(widget, ctk.CTkSegmentedButton):
|
|
236
|
+
try:
|
|
237
|
+
widget.set(entry[1])
|
|
238
|
+
except Exception:
|
|
239
|
+
pass
|
|
240
|
+
|
|
241
|
+
elif kind == "ctk_tab" and HAS_CTK and isinstance(widget, ctk.CTkTabview):
|
|
242
|
+
try:
|
|
243
|
+
widget.set(entry[1])
|
|
244
|
+
except Exception:
|
|
245
|
+
pass
|
|
246
|
+
|
|
247
|
+
except Exception:
|
|
248
|
+
pass
|
|
249
|
+
|
|
250
|
+
# Rekursiv durch alle Kinder
|
|
251
|
+
try:
|
|
252
|
+
children = widget.winfo_children()
|
|
253
|
+
except Exception:
|
|
254
|
+
children = []
|
|
255
|
+
|
|
256
|
+
for child in children:
|
|
257
|
+
_apply(child, state)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tklive
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Live hot-reload for tkinter and customtkinter apps
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/yourusername/tklive
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: watchdog>=3.0.0
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
13
|
+
# tklive
|
|
14
|
+
|
|
15
|
+
Live hot-reload für tkinter und customtkinter Apps.
|
|
16
|
+
Speichere deine Datei → UI updated sich sofort ohne das Fenster zu schließen.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install tklive
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Verwendung
|
|
25
|
+
|
|
26
|
+
Füge **zwei Zeilen** zu deinem Projekt hinzu:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import tkinter as tk
|
|
30
|
+
from tklive import live
|
|
31
|
+
|
|
32
|
+
root = tk.Tk()
|
|
33
|
+
|
|
34
|
+
# ... dein gesamter normaler Code bleibt unverändert ...
|
|
35
|
+
|
|
36
|
+
live(root, __file__) # ← eine Zeile ans Ende
|
|
37
|
+
root.mainloop()
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Speichere die Datei → UI reloaded live. Oder **Ctrl+R** für manuellen Reload.
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- ✅ Fenster bleibt offen – kein Neustart
|
|
45
|
+
- ✅ State-Erhalt (Entry, Text, Tabs, Scrollposition, Fenstergröße)
|
|
46
|
+
- ✅ customtkinter Support (CTkEntry, CTkTextbox, CTkSlider, CTkTabview ...)
|
|
47
|
+
- ✅ ttk Support (Notebook, Treeview, Combobox ...)
|
|
48
|
+
- ✅ Multi-File Support
|
|
49
|
+
|
|
50
|
+
## Mehrere Dateien beobachten
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
live(root, __file__, watch_files=["ui.py", "components.py"])
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Lizenz
|
|
57
|
+
|
|
58
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
watchdog>=3.0.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tklive
|