tkfluent 0.0.2__py3-none-any.whl
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.
- tkflu/__init__.py +26 -0
- tkflu/__main__.py +50 -0
- tkflu/badge.py +169 -0
- tkflu/button.py +286 -0
- tkflu/checkbox.py +0 -0
- tkflu/customwindow.py +134 -0
- tkflu/customwindow2.py +77 -0
- tkflu/entry.py +185 -0
- tkflu/frame.py +230 -0
- tkflu/image.py +0 -0
- tkflu/label.py +65 -0
- tkflu/listbox.py +286 -0
- tkflu/litenav.py +0 -0
- tkflu/scrollbar.py +200 -0
- tkflu/text.py +188 -0
- tkflu/thememanager.py +27 -0
- tkflu/togglebutton.py +341 -0
- tkflu/window.py +197 -0
- tkfluent-0.0.2.dist-info/METADATA +23 -0
- tkfluent-0.0.2.dist-info/RECORD +21 -0
- tkfluent-0.0.2.dist-info/WHEEL +4 -0
tkflu/__init__.py
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
"""、
|
2
|
+
|
3
|
+
Fluent设计的tkinter组件库(模板)
|
4
|
+
|
5
|
+
-------------
|
6
|
+
作者:XiangQinxi
|
7
|
+
-------------
|
8
|
+
"""
|
9
|
+
|
10
|
+
from .badge import FluBadge
|
11
|
+
from .button import FluButton
|
12
|
+
from .entry import FluEntry
|
13
|
+
from .frame import FluFrame
|
14
|
+
from .label import FluLabel
|
15
|
+
from .text import FluText
|
16
|
+
from .thememanager import FluThemeManager
|
17
|
+
from .togglebutton import FluToggleButton
|
18
|
+
from .window import FluWindow
|
19
|
+
|
20
|
+
FluChip = FluBadge
|
21
|
+
FluPushButton = FluButton
|
22
|
+
FluTextInput = FluEntry
|
23
|
+
FluTextBox = FluText
|
24
|
+
FluPanel = FluFrame
|
25
|
+
|
26
|
+
#
|
tkflu/__main__.py
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
from tkflu import *
|
2
|
+
from tkinter import *
|
3
|
+
from tkinter.font import *
|
4
|
+
|
5
|
+
root = FluWindow()
|
6
|
+
root.iconify
|
7
|
+
root.wincustom(way=0)
|
8
|
+
root.wm_geometry("180x360")
|
9
|
+
|
10
|
+
thememanager = FluThemeManager()
|
11
|
+
|
12
|
+
frame = FluFrame()
|
13
|
+
|
14
|
+
badge1 = FluBadge(frame, text="FluBadge", width=60)
|
15
|
+
badge1.pack(padx=5, pady=5)
|
16
|
+
|
17
|
+
badge2 = FluBadge(frame, text="FluBadge (Accent)", width=120, style="accent")
|
18
|
+
badge2.pack(padx=5, pady=5)
|
19
|
+
|
20
|
+
button1 = FluButton(
|
21
|
+
frame, text="FluButton", command=lambda: print("FluButton -> Clicked")
|
22
|
+
)
|
23
|
+
button1.pack(fill="x", padx=5, pady=5)
|
24
|
+
|
25
|
+
button2 = FluButton(
|
26
|
+
frame, text="FluButton (Accent)", command=lambda: print("FluButton (Accent) -> Clicked"), style="accent"
|
27
|
+
)
|
28
|
+
button2.pack(fill="x", padx=5, pady=5)
|
29
|
+
|
30
|
+
def toggle1():
|
31
|
+
print(f"FluToggleButton -> Toggled -> Checked: {togglebutton1.dcget('checked')}")
|
32
|
+
if togglebutton1.dcget('checked'):
|
33
|
+
thememanager.mode("dark")
|
34
|
+
else:
|
35
|
+
thememanager.mode("light")
|
36
|
+
|
37
|
+
togglebutton1 = FluToggleButton(
|
38
|
+
frame, text="FluToggleButton", command=toggle1
|
39
|
+
)
|
40
|
+
togglebutton1.pack(fill="x", padx=5, pady=5)
|
41
|
+
|
42
|
+
entry1 = FluEntry(frame)
|
43
|
+
entry1.pack(fill="x", padx=5, pady=5)
|
44
|
+
|
45
|
+
text1 = FluText(frame)
|
46
|
+
text1.pack(fill="x", padx=5, pady=5)
|
47
|
+
|
48
|
+
frame.pack(fill="both", expand="yes", side="right", padx=5, pady=5)
|
49
|
+
|
50
|
+
root.mainloop()
|
tkflu/badge.py
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
from tkdeft.windows.draw import DSvgDraw
|
2
|
+
from tkdeft.windows.canvas import DCanvas
|
3
|
+
from tkdeft.windows.drawwidget import DDrawWidget
|
4
|
+
|
5
|
+
|
6
|
+
class FluBadgeDraw(DSvgDraw):
|
7
|
+
def create_roundrect(self,
|
8
|
+
x1, y1, x2, y2, temppath=None,
|
9
|
+
fill="transparent", outline="black", width=1
|
10
|
+
):
|
11
|
+
drawing = self.create_drawing(x2 - x1, y2 - y1, temppath=temppath)
|
12
|
+
drawing[1].add(
|
13
|
+
drawing[1].rect(
|
14
|
+
(x1, y1), (x2 - x1, y2 - y1), 20, 25,
|
15
|
+
fill=fill, stroke_width=width,
|
16
|
+
stroke=outline,
|
17
|
+
)
|
18
|
+
)
|
19
|
+
drawing[1].save()
|
20
|
+
return drawing[0]
|
21
|
+
|
22
|
+
|
23
|
+
class FluBadgeCanvas(DCanvas):
|
24
|
+
draw = FluBadgeDraw
|
25
|
+
|
26
|
+
def create_round_rectangle(self,
|
27
|
+
x1, y1, x2, y2, temppath=None,
|
28
|
+
fill="transparent", outline="black", width=1
|
29
|
+
):
|
30
|
+
self._img = self.svgdraw.create_roundrect(
|
31
|
+
x1, y1, x2, y2, temppath=temppath,
|
32
|
+
fill=fill, outline=outline, width=width
|
33
|
+
)
|
34
|
+
self._tkimg = self.svgdraw.create_tksvg_image(self._img)
|
35
|
+
return self.create_image(x1, y1, anchor="nw", image=self._tkimg)
|
36
|
+
|
37
|
+
create_roundrect = create_round_rectangle
|
38
|
+
|
39
|
+
|
40
|
+
class FluBadge(FluBadgeCanvas, DDrawWidget):
|
41
|
+
|
42
|
+
def __init__(self, *args,
|
43
|
+
text="",
|
44
|
+
width=70,
|
45
|
+
height=30,
|
46
|
+
font=None,
|
47
|
+
mode="light",
|
48
|
+
style="standard",
|
49
|
+
**kwargs):
|
50
|
+
|
51
|
+
"""
|
52
|
+
|
53
|
+
初始化类
|
54
|
+
|
55
|
+
:param args: 参照tkinter.Canvas.__init__
|
56
|
+
:param text:
|
57
|
+
:param width:
|
58
|
+
:param height:
|
59
|
+
:param font:
|
60
|
+
:param mode: Fluent主题模式 分为 “light” “dark”
|
61
|
+
:param style:
|
62
|
+
:param kwargs: 参照tkinter.Canvas.__init__
|
63
|
+
"""
|
64
|
+
|
65
|
+
self._init(mode, style)
|
66
|
+
|
67
|
+
super().__init__(*args, width=width, height=height, **kwargs)
|
68
|
+
|
69
|
+
self.dconfigure(
|
70
|
+
text=text,
|
71
|
+
)
|
72
|
+
|
73
|
+
self.bind("<<Clicked>>", lambda event=None: self.focus_set(), add="+")
|
74
|
+
|
75
|
+
if font is None:
|
76
|
+
from tkdeft.utility.fonts import SegoeFont
|
77
|
+
self.attributes.font = SegoeFont()
|
78
|
+
|
79
|
+
def _init(self, mode, style):
|
80
|
+
from easydict import EasyDict
|
81
|
+
|
82
|
+
self.attributes = EasyDict(
|
83
|
+
{
|
84
|
+
"text": "",
|
85
|
+
"command": None,
|
86
|
+
"font": None,
|
87
|
+
|
88
|
+
"back_color": None,
|
89
|
+
"border_color": None,
|
90
|
+
"border_width": None,
|
91
|
+
"text_color": None
|
92
|
+
}
|
93
|
+
)
|
94
|
+
|
95
|
+
self.theme(mode, style)
|
96
|
+
|
97
|
+
def _draw(self, event=None):
|
98
|
+
|
99
|
+
"""
|
100
|
+
重新绘制组件
|
101
|
+
|
102
|
+
:param event:
|
103
|
+
"""
|
104
|
+
|
105
|
+
super()._draw(event)
|
106
|
+
|
107
|
+
self.delete("all")
|
108
|
+
|
109
|
+
_back_color = self.attributes.back_color
|
110
|
+
_border_color = self.attributes.border_color
|
111
|
+
_border_width = self.attributes.border_width
|
112
|
+
_text_color = self.attributes.text_color
|
113
|
+
|
114
|
+
self.element_border = self.create_round_rectangle(
|
115
|
+
0, 0, self.winfo_width(), self.winfo_height(), temppath=self.temppath,
|
116
|
+
fill=_back_color, outline=_border_color, width=_border_width
|
117
|
+
)
|
118
|
+
|
119
|
+
self.element_text = self.create_text(
|
120
|
+
self.winfo_width() / 2, self.winfo_height() / 2, anchor="center",
|
121
|
+
fill=_text_color, text=self.attributes.text, font=self.attributes.font
|
122
|
+
)
|
123
|
+
|
124
|
+
def theme(self, mode, style=None):
|
125
|
+
self.mode = mode
|
126
|
+
if style:
|
127
|
+
self.style = style
|
128
|
+
if mode.lower() == "dark":
|
129
|
+
if self.style.lower() == "accent":
|
130
|
+
self._dark_accent()
|
131
|
+
else:
|
132
|
+
self._dark()
|
133
|
+
else:
|
134
|
+
if self.style.lower() == "accent":
|
135
|
+
self._light_accent()
|
136
|
+
else:
|
137
|
+
self._light()
|
138
|
+
|
139
|
+
def _light(self):
|
140
|
+
self.dconfigure(
|
141
|
+
back_color="#f0f0f0",
|
142
|
+
border_color="#f0f0f0",
|
143
|
+
border_width=1,
|
144
|
+
text_color="#191919",
|
145
|
+
)
|
146
|
+
|
147
|
+
def _light_accent(self):
|
148
|
+
self.dconfigure(
|
149
|
+
back_color="#005fb8",
|
150
|
+
border_color="#005fb8",
|
151
|
+
border_width=1,
|
152
|
+
text_color="#ffffff",
|
153
|
+
)
|
154
|
+
|
155
|
+
def _dark(self):
|
156
|
+
self.dconfigure(
|
157
|
+
back_color="#242424",
|
158
|
+
border_color="#242424",
|
159
|
+
border_width=1,
|
160
|
+
text_color="#ffffff",
|
161
|
+
)
|
162
|
+
|
163
|
+
def _dark_accent(self):
|
164
|
+
self.dconfigure(
|
165
|
+
back_color="#60cdff",
|
166
|
+
border_color="#60cdff",
|
167
|
+
border_width=1,
|
168
|
+
text_color="#000000",
|
169
|
+
)
|
tkflu/button.py
ADDED
@@ -0,0 +1,286 @@
|
|
1
|
+
from tkdeft.windows.draw import DSvgDraw
|
2
|
+
from tkdeft.windows.canvas import DCanvas
|
3
|
+
from tkdeft.windows.drawwidget import DDrawWidget
|
4
|
+
|
5
|
+
|
6
|
+
class FluButtonDraw(DSvgDraw):
|
7
|
+
def create_roundrect(self,
|
8
|
+
x1, y1, x2, y2, radius, radiusy=None, temppath=None,
|
9
|
+
fill="transparent", outline="black", outline2="black", width=1
|
10
|
+
):
|
11
|
+
if radiusy:
|
12
|
+
_rx = radius
|
13
|
+
_ry = radiusy
|
14
|
+
else:
|
15
|
+
_rx, _ry = radius, radius
|
16
|
+
drawing = self.create_drawing(x2 - x1, y2 - y1, temppath=temppath)
|
17
|
+
border = drawing[1].linearGradient(start=(x1, y1), end=(x1, y2), id="DButton.Border")
|
18
|
+
border.add_stop_color("0%", outline)
|
19
|
+
border.add_stop_color("100%", outline2)
|
20
|
+
drawing[1].defs.add(border)
|
21
|
+
drawing[1].add(
|
22
|
+
drawing[1].rect(
|
23
|
+
(x1, y1), (x2 - x1, y2 - y1), _rx, _ry,
|
24
|
+
fill=fill, stroke_width=width,
|
25
|
+
stroke=f"url(#{border.get_id()})",
|
26
|
+
)
|
27
|
+
)
|
28
|
+
drawing[1].save()
|
29
|
+
return drawing[0]
|
30
|
+
|
31
|
+
|
32
|
+
class FluButtonCanvas(DCanvas):
|
33
|
+
draw = FluButtonDraw
|
34
|
+
|
35
|
+
def create_round_rectangle(self,
|
36
|
+
x1, y1, x2, y2, r1, r2=None, temppath=None,
|
37
|
+
fill="transparent", outline="black", outline2="black", width=1
|
38
|
+
):
|
39
|
+
self._img = self.svgdraw.create_roundrect(
|
40
|
+
x1, y1, x2, y2, r1, r2, temppath=temppath,
|
41
|
+
fill=fill, outline=outline, outline2=outline2, width=width
|
42
|
+
)
|
43
|
+
self._tkimg = self.svgdraw.create_tksvg_image(self._img)
|
44
|
+
return self.create_image(x1, y1, anchor="nw", image=self._tkimg)
|
45
|
+
|
46
|
+
create_roundrect = create_round_rectangle
|
47
|
+
|
48
|
+
|
49
|
+
class FluButton(FluButtonCanvas, DDrawWidget):
|
50
|
+
def __init__(self, *args,
|
51
|
+
text="",
|
52
|
+
width=120,
|
53
|
+
height=32,
|
54
|
+
command=None,
|
55
|
+
font=None,
|
56
|
+
mode="light",
|
57
|
+
style="standard",
|
58
|
+
**kwargs):
|
59
|
+
self._init(mode, style)
|
60
|
+
|
61
|
+
super().__init__(*args, width=width, height=height, **kwargs)
|
62
|
+
|
63
|
+
if command is None:
|
64
|
+
def empty(): pass
|
65
|
+
|
66
|
+
command = empty
|
67
|
+
|
68
|
+
self.dconfigure(
|
69
|
+
text=text,
|
70
|
+
command=command
|
71
|
+
)
|
72
|
+
|
73
|
+
self.bind("<<Clicked>>", lambda event=None: self.focus_set(), add="+")
|
74
|
+
self.bind("<<Clicked>>", lambda event=None: self.attributes.command(), add="+")
|
75
|
+
|
76
|
+
self.bind("<Return>", lambda event=None: self.attributes.command(), add="+") # 可以使用回车键模拟点击
|
77
|
+
|
78
|
+
if font is None:
|
79
|
+
from tkdeft.utility.fonts import SegoeFont
|
80
|
+
self.attributes.font = SegoeFont()
|
81
|
+
|
82
|
+
def _init(self, mode, style):
|
83
|
+
|
84
|
+
from easydict import EasyDict
|
85
|
+
|
86
|
+
self.attributes = EasyDict(
|
87
|
+
{
|
88
|
+
"text": "",
|
89
|
+
"command": None,
|
90
|
+
"font": None,
|
91
|
+
|
92
|
+
"rest": {
|
93
|
+
"back_color": "#ffffff",
|
94
|
+
"border_color": "#f0f0f0",
|
95
|
+
"border_color2": "#d6d6d6",
|
96
|
+
"border_width": 1,
|
97
|
+
"radius": 6,
|
98
|
+
"text_color": "#1b1b1b",
|
99
|
+
},
|
100
|
+
"hover": {
|
101
|
+
"back_color": "#fcfcfc",
|
102
|
+
"border_color": "#f0f0f0",
|
103
|
+
"border_color2": "#d6d6d6",
|
104
|
+
"border_width": 1,
|
105
|
+
"radius": 6,
|
106
|
+
"text_color": "#1b1b1b",
|
107
|
+
},
|
108
|
+
"pressed": {
|
109
|
+
"back_color": "#fdfdfd",
|
110
|
+
"border_color": "#f0f0f0",
|
111
|
+
"border_color2": "#f0f0f0",
|
112
|
+
"border_width": 1,
|
113
|
+
"radius": 6,
|
114
|
+
"text_color": "#636363",
|
115
|
+
}
|
116
|
+
}
|
117
|
+
)
|
118
|
+
|
119
|
+
self.theme(mode=mode, style=style)
|
120
|
+
|
121
|
+
def _draw(self, event=None):
|
122
|
+
super()._draw(event)
|
123
|
+
|
124
|
+
self.delete("all")
|
125
|
+
|
126
|
+
if self.enter:
|
127
|
+
if self.button1:
|
128
|
+
_back_color = self.attributes.pressed.back_color
|
129
|
+
_border_color = self.attributes.pressed.border_color
|
130
|
+
_border_color2 = self.attributes.pressed.border_color2
|
131
|
+
_border_width = self.attributes.pressed.border_width
|
132
|
+
_radius = self.attributes.pressed.radius
|
133
|
+
_text_color = self.attributes.pressed.text_color
|
134
|
+
else:
|
135
|
+
_back_color = self.attributes.hover.back_color
|
136
|
+
_border_color = self.attributes.hover.border_color
|
137
|
+
_border_color2 = self.attributes.hover.border_color2
|
138
|
+
_border_width = self.attributes.hover.border_width
|
139
|
+
_radius = self.attributes.hover.radius
|
140
|
+
_text_color = self.attributes.hover.text_color
|
141
|
+
else:
|
142
|
+
_back_color = self.attributes.rest.back_color
|
143
|
+
_border_color = self.attributes.rest.border_color
|
144
|
+
_border_color2 = self.attributes.rest.border_color2
|
145
|
+
_border_width = self.attributes.rest.border_width
|
146
|
+
_radius = self.attributes.rest.radius
|
147
|
+
_text_color = self.attributes.rest.text_color
|
148
|
+
|
149
|
+
self.element_border = self.create_round_rectangle(
|
150
|
+
0, 0, self.winfo_width(), self.winfo_height(), _radius, temppath=self.temppath,
|
151
|
+
fill=_back_color, outline=_border_color, outline2=_border_color2, width=_border_width
|
152
|
+
)
|
153
|
+
self.element_text = self.create_text(
|
154
|
+
self.winfo_width() / 2, self.winfo_height() / 2, anchor="center",
|
155
|
+
fill=_text_color, text=self.attributes.text, font=self.attributes.font
|
156
|
+
)
|
157
|
+
|
158
|
+
def theme(self, mode, style=None):
|
159
|
+
self.mode = mode
|
160
|
+
if style:
|
161
|
+
self.style = style
|
162
|
+
if mode.lower() == "dark":
|
163
|
+
if self.style.lower() == "accent":
|
164
|
+
self._dark_accent()
|
165
|
+
else:
|
166
|
+
self._dark()
|
167
|
+
else:
|
168
|
+
if self.style.lower() == "accent":
|
169
|
+
self._light_accent()
|
170
|
+
else:
|
171
|
+
self._light()
|
172
|
+
|
173
|
+
def _light(self):
|
174
|
+
self.dconfigure(
|
175
|
+
rest={
|
176
|
+
"back_color": "#ffffff",
|
177
|
+
"border_color": "#f0f0f0",
|
178
|
+
"border_color2": "#d6d6d6",
|
179
|
+
"border_width": 1,
|
180
|
+
"radius": 6,
|
181
|
+
"text_color": "#1b1b1b",
|
182
|
+
},
|
183
|
+
hover={
|
184
|
+
"back_color": "#fcfcfc",
|
185
|
+
"border_color": "#f0f0f0",
|
186
|
+
"border_color2": "#d6d6d6",
|
187
|
+
"border_width": 1,
|
188
|
+
"radius": 6,
|
189
|
+
"text_color": "#1b1b1b",
|
190
|
+
},
|
191
|
+
pressed={
|
192
|
+
"back_color": "#fdfdfd",
|
193
|
+
"border_color": "#f0f0f0",
|
194
|
+
"border_color2": "#f0f0f0",
|
195
|
+
"border_width": 1,
|
196
|
+
"radius": 6,
|
197
|
+
"text_color": "#636363",
|
198
|
+
}
|
199
|
+
)
|
200
|
+
|
201
|
+
def _light_accent(self):
|
202
|
+
self.dconfigure(
|
203
|
+
rest={
|
204
|
+
"back_color": "#005fb8",
|
205
|
+
"border_color": "#146cbe",
|
206
|
+
"border_color2": "#00396e",
|
207
|
+
"border_width": 1,
|
208
|
+
"radius": 6,
|
209
|
+
"text_color": "#ffffff",
|
210
|
+
},
|
211
|
+
hover={
|
212
|
+
"back_color": "#0359a9",
|
213
|
+
"border_color": "#1766b0",
|
214
|
+
"border_color2": "#0f4373",
|
215
|
+
"border_width": 1,
|
216
|
+
"radius": 6,
|
217
|
+
"text_color": "#ffffff",
|
218
|
+
},
|
219
|
+
pressed={
|
220
|
+
"back_color": "#005fb8",
|
221
|
+
"border_color": "#4389ca",
|
222
|
+
"border_color2": "#4389ca",
|
223
|
+
"border_width": 1,
|
224
|
+
"radius": 6,
|
225
|
+
"text_color": "#b4cbe0",
|
226
|
+
}
|
227
|
+
)
|
228
|
+
|
229
|
+
def _dark(self):
|
230
|
+
self.dconfigure(
|
231
|
+
rest={
|
232
|
+
"back_color": "#272727",
|
233
|
+
"border_color": "#303030",
|
234
|
+
"border_color2": "#262626",
|
235
|
+
"border_width": 1,
|
236
|
+
"radius": 6,
|
237
|
+
"text_color": "#ffffff",
|
238
|
+
},
|
239
|
+
hover={
|
240
|
+
"back_color": "#2d2d2d",
|
241
|
+
"border_color": "#303030",
|
242
|
+
"border_color2": "#262626",
|
243
|
+
"border_width": 1,
|
244
|
+
"radius": 6,
|
245
|
+
"text_color": "#ffffff",
|
246
|
+
},
|
247
|
+
pressed={
|
248
|
+
"back_color": "#212121",
|
249
|
+
"border_color": "#2a2a2a",
|
250
|
+
"border_color2": "#262626",
|
251
|
+
"border_width": 1,
|
252
|
+
"radius": 6,
|
253
|
+
"text_color": "#cfcfcf",
|
254
|
+
}
|
255
|
+
)
|
256
|
+
|
257
|
+
def _dark_accent(self):
|
258
|
+
self.dconfigure(
|
259
|
+
rest={
|
260
|
+
"back_color": "#60cdff",
|
261
|
+
"border_color": "#6cd1ff",
|
262
|
+
"border_color2": "#56b4df",
|
263
|
+
"border_width": 1,
|
264
|
+
"radius": 6,
|
265
|
+
"text_color": "#000000",
|
266
|
+
},
|
267
|
+
hover={
|
268
|
+
"back_color": "#5abce9",
|
269
|
+
"border_color": "#67c1eb",
|
270
|
+
"border_color2": "#50a5cc",
|
271
|
+
"border_width": 1,
|
272
|
+
"radius": 6,
|
273
|
+
"text_color": "#000000",
|
274
|
+
},
|
275
|
+
pressed={
|
276
|
+
"back_color": "#52a9d1",
|
277
|
+
"border_color": "#60b0d5",
|
278
|
+
"border_color2": "#60b0d5",
|
279
|
+
"border_width": 1,
|
280
|
+
"radius": 6,
|
281
|
+
"text_color": "#295468",
|
282
|
+
}
|
283
|
+
)
|
284
|
+
|
285
|
+
def invoke(self):
|
286
|
+
self.attributes.command()
|
tkflu/checkbox.py
ADDED
File without changes
|
tkflu/customwindow.py
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
from tkinter import Tk
|
2
|
+
|
3
|
+
from ctypes import POINTER, Structure, c_int
|
4
|
+
from ctypes.wintypes import HWND, RECT, UINT
|
5
|
+
|
6
|
+
WM_NCCALCSIZE = 0x0083
|
7
|
+
WS_EX_APPWINDOW = 0x00040000
|
8
|
+
WS_VISIBLE = 0x10000000
|
9
|
+
WS_THICKFRAME = 0x00040000
|
10
|
+
WS_CAPTION = 0x00C00000
|
11
|
+
WM_SYSCOMMAND = 0x0112
|
12
|
+
SC_MINIMIZE = 0xF020
|
13
|
+
|
14
|
+
SWP_NOSIZE = 0x0001
|
15
|
+
SWP_NOREDRAW = 0x0008
|
16
|
+
SWP_FRAMECHANGED = 0x0020
|
17
|
+
|
18
|
+
SW_MAXIMIZE = 3
|
19
|
+
SW_NORMAL = 1
|
20
|
+
|
21
|
+
GWL_EXSTYLE = -20
|
22
|
+
GWL_STYLE = -16
|
23
|
+
GWL_WNDPROC = -4
|
24
|
+
|
25
|
+
|
26
|
+
class PWINDOWPOS(Structure):
|
27
|
+
_fields_ = [
|
28
|
+
("hWnd", HWND),
|
29
|
+
("hwndInsertAfter", HWND),
|
30
|
+
("x", c_int),
|
31
|
+
("y", c_int),
|
32
|
+
("cx", c_int),
|
33
|
+
("cy", c_int),
|
34
|
+
("flags", UINT),
|
35
|
+
]
|
36
|
+
|
37
|
+
|
38
|
+
class NCCALCSIZE_PARAMS(Structure):
|
39
|
+
_fields_ = [("rgrc", RECT * 3), ("lppos", POINTER(PWINDOWPOS))]
|
40
|
+
|
41
|
+
|
42
|
+
from ctypes import WINFUNCTYPE, c_char_p, c_uint64, windll
|
43
|
+
|
44
|
+
|
45
|
+
from tkinter import Event, Widget, Tk, Frame
|
46
|
+
|
47
|
+
|
48
|
+
class WindowDragArea(object):
|
49
|
+
x, y = 0, 0
|
50
|
+
|
51
|
+
def __init__(self, window):
|
52
|
+
self.window = window
|
53
|
+
|
54
|
+
def _click(self, event: Event):
|
55
|
+
self.x, self.y = event.x, event.y
|
56
|
+
|
57
|
+
def _window_move(self, event: Event):
|
58
|
+
new_x = (event.x - self.x) + self.window.winfo_x()
|
59
|
+
new_y = (event.y - self.y) + self.window.winfo_y()
|
60
|
+
if new_y <= 0:
|
61
|
+
new_y = 0
|
62
|
+
self.window.geometry(f"+{new_x}+{new_y}")
|
63
|
+
self.window.update()
|
64
|
+
|
65
|
+
def bind(self, widget: Widget):
|
66
|
+
widget.bind("<Button-1>", self._click)
|
67
|
+
widget.bind("<B1-Motion>", lambda event: self._window_move(event))
|
68
|
+
|
69
|
+
def tag_bind(self, widget: Widget, tag):
|
70
|
+
widget.tag_bind(tag, "<Button-1>", self._click)
|
71
|
+
widget.tag_bind(tag, "<B1-Motion>", lambda event: self._window_move(event))
|
72
|
+
|
73
|
+
|
74
|
+
class CustomWindow(object):
|
75
|
+
def __init__(self, window: Tk = None, wait=100):
|
76
|
+
|
77
|
+
if window is not None:
|
78
|
+
self.window: Tk = window
|
79
|
+
else:
|
80
|
+
from tkinter import _default_root
|
81
|
+
self.window: Tk = _default_root
|
82
|
+
|
83
|
+
self.window.after(wait, self.setup)
|
84
|
+
|
85
|
+
def bind_drag(self, widget):
|
86
|
+
WindowDragArea(self.window).bind(widget)
|
87
|
+
|
88
|
+
def setup(self):
|
89
|
+
def handle(hwnd: any, msg: any, wp: any, lp: any) -> any:
|
90
|
+
if msg == WM_NCCALCSIZE and wp:
|
91
|
+
sz = NCCALCSIZE_PARAMS.from_address(lp)
|
92
|
+
sz.rgrc[0].top -= 6
|
93
|
+
|
94
|
+
return windll.user32.CallWindowProcW(*map(c_uint64, (globals()[old], hwnd, msg, wp, lp)))
|
95
|
+
|
96
|
+
self.hwnd = windll.user32.GetParent(self.window.winfo_id())
|
97
|
+
|
98
|
+
windll.user32.SetWindowLongA(self.hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW)
|
99
|
+
windll.user32.SetWindowLongA(self.hwnd, GWL_STYLE, WS_VISIBLE | WS_THICKFRAME)
|
100
|
+
|
101
|
+
old, new = "old", "new"
|
102
|
+
prototype = WINFUNCTYPE(c_uint64, c_uint64, c_uint64, c_uint64, c_uint64)
|
103
|
+
globals()[old] = None
|
104
|
+
globals()[new] = prototype(handle)
|
105
|
+
globals()[old] = windll.user32.GetWindowLongPtrA(self.hwnd, GWL_WNDPROC)
|
106
|
+
windll.user32.SetWindowLongPtrA(self.hwnd, GWL_WNDPROC, globals()[new])
|
107
|
+
|
108
|
+
self.window.wm_iconify()
|
109
|
+
self.window.wm_deiconify()
|
110
|
+
self.window.focus_force()
|
111
|
+
self.window.update()
|
112
|
+
|
113
|
+
|
114
|
+
from tkinter import Tk
|
115
|
+
|
116
|
+
|
117
|
+
class CustomTk(Tk):
|
118
|
+
def __init__(self, *args, **kwargs):
|
119
|
+
super().__init__(*args, **kwargs)
|
120
|
+
self.customwindow = CustomWindow(self, *args, **kwargs)
|
121
|
+
|
122
|
+
|
123
|
+
if __name__ == '__main__':
|
124
|
+
from tkinter import Tk, Frame
|
125
|
+
root = Tk()
|
126
|
+
root.title("Test")
|
127
|
+
|
128
|
+
frame = Frame(root, width=100, height=25, background="grey")
|
129
|
+
frame.pack(fill="x", side="top")
|
130
|
+
|
131
|
+
customwindow = CustomWindow(root, wait=100)
|
132
|
+
customwindow.bind_drag(frame)
|
133
|
+
|
134
|
+
root.mainloop()
|