CTkDataTable 0.1.0__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.
- CTkDataTable/__init__.py +17 -0
- CTkDataTable/_utils.py +58 -0
- CTkDataTable/ctk_data_table.py +1659 -0
- CTkDataTable/examples/__init__.py +1 -0
- CTkDataTable/examples/basic_table.py +208 -0
- CTkDataTable/py.typed +1 -0
- CTkDataTable/table_column.py +446 -0
- CTkDataTable/table_events.py +18 -0
- CTkDataTable/table_model.py +603 -0
- CTkDataTable/table_renderer.py +1239 -0
- CTkDataTable/table_style.py +210 -0
- ctkdatatable-0.1.0.dist-info/METADATA +681 -0
- ctkdatatable-0.1.0.dist-info/RECORD +16 -0
- ctkdatatable-0.1.0.dist-info/WHEEL +5 -0
- ctkdatatable-0.1.0.dist-info/licenses/LICENSE +21 -0
- ctkdatatable-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: CTkDataTable
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A virtualized data table widget for CustomTkinter applications.
|
|
5
|
+
Author: Harry Gomm
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Documentation, https://github.com/Harry-g25/CTkTableData/blob/main/docs/Docs.md
|
|
8
|
+
Project-URL: Homepage, https://github.com/Harry-g25/CTkTableData
|
|
9
|
+
Project-URL: Issues, https://github.com/Harry-g25/CTkTableData/issues
|
|
10
|
+
Project-URL: Source, https://github.com/Harry-g25/CTkTableData
|
|
11
|
+
Keywords: customtkinter,tkinter,table,datagrid,widgets
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: customtkinter>=5.2
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
28
|
+
Requires-Dist: mypy>=1.10; extra == "dev"
|
|
29
|
+
Requires-Dist: ruff>=0.11; extra == "dev"
|
|
30
|
+
Requires-Dist: twine>=5.1; extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# CTkDataTable
|
|
34
|
+
|
|
35
|
+
The `CTkDataTable` package provides a virtualized data table widget for CustomTkinter apps.
|
|
36
|
+
|
|
37
|
+
Use it when you want to show records from Python data, PostgreSQL queries, SQLite queries, or SQLAlchemy results without building a grid of labels by hand.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
From PyPI:
|
|
42
|
+
|
|
43
|
+
```powershell
|
|
44
|
+
pip install CTkDataTable
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
For local development:
|
|
48
|
+
|
|
49
|
+
```powershell
|
|
50
|
+
pip install -e ".[dev]"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Run a Demo
|
|
54
|
+
|
|
55
|
+
```powershell
|
|
56
|
+
python -m CTkDataTable.examples.basic_table
|
|
57
|
+
python -m CTkDataTable.examples.ncr_records
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Development Checks
|
|
61
|
+
|
|
62
|
+
```powershell
|
|
63
|
+
python -m unittest discover -v
|
|
64
|
+
python -m ruff check .
|
|
65
|
+
python -m mypy CTkDataTable
|
|
66
|
+
python -m build
|
|
67
|
+
python -m twine check dist/*
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Release Build
|
|
71
|
+
|
|
72
|
+
```powershell
|
|
73
|
+
Remove-Item -Recurse -Force dist, build -ErrorAction SilentlyContinue
|
|
74
|
+
python -m build
|
|
75
|
+
python -m twine check dist/*
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
MIT License. See [LICENSE](https://github.com/Harry-g25/CTkTableData/blob/main/LICENSE).
|
|
81
|
+
|
|
82
|
+
## Quick Start
|
|
83
|
+
|
|
84
|
+
Using the table has three steps:
|
|
85
|
+
|
|
86
|
+
1. Define the columns.
|
|
87
|
+
2. Pass rows.
|
|
88
|
+
3. Place the table in your CustomTkinter layout.
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
import customtkinter as ctk
|
|
92
|
+
|
|
93
|
+
from CTkDataTable import CTkDataTable
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
app = ctk.CTk()
|
|
97
|
+
app.geometry("700x400")
|
|
98
|
+
app.grid_rowconfigure(0, weight=1)
|
|
99
|
+
app.grid_columnconfigure(0, weight=1)
|
|
100
|
+
|
|
101
|
+
columns = [
|
|
102
|
+
{"key": "id", "title": "ID", "width": 80},
|
|
103
|
+
{"key": "name", "title": "Name", "width": 180},
|
|
104
|
+
{"key": "status", "title": "Status", "width": 140},
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
rows = [
|
|
108
|
+
{"id": 1, "name": "Alice", "status": "Open"},
|
|
109
|
+
{"id": 2, "name": "Bob", "status": "Closed"},
|
|
110
|
+
{"id": 3, "name": "Charlie", "status": "In Review"},
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
table = CTkDataTable(app, columns=columns, data=rows)
|
|
114
|
+
table.grid(row=0, column=0, sticky="nsew", padx=16, pady=16)
|
|
115
|
+
|
|
116
|
+
app.mainloop()
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The important rule is simple:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
column["key"] == row dictionary key
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
For example:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
{"key": "name", "title": "Name", "width": 180}
|
|
129
|
+
{"name": "Alice"}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Loading Data
|
|
133
|
+
|
|
134
|
+
Most users can pass a list of dictionaries:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
table.set_data([
|
|
138
|
+
{"id": 1, "name": "Alice", "status": "Open"},
|
|
139
|
+
{"id": 2, "name": "Bob", "status": "Closed"},
|
|
140
|
+
])
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The table also accepts common database row objects, including:
|
|
144
|
+
|
|
145
|
+
- PostgreSQL rows returned as dictionaries
|
|
146
|
+
- SQLAlchemy mapping rows
|
|
147
|
+
- `sqlite3.Row` objects
|
|
148
|
+
- plain DB-API tuple cursors converted with `rows_from_cursor()`
|
|
149
|
+
|
|
150
|
+
### PostgreSQL with psycopg 3
|
|
151
|
+
|
|
152
|
+
Use `dict_row`, then pass the query result directly to the table.
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
import psycopg
|
|
156
|
+
from psycopg.rows import dict_row
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
with psycopg.connect(DB_URL, row_factory=dict_row) as connection:
|
|
160
|
+
rows = connection.execute("""
|
|
161
|
+
SELECT id, name, status
|
|
162
|
+
FROM customers
|
|
163
|
+
""").fetchall()
|
|
164
|
+
|
|
165
|
+
table.set_data(rows)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### PostgreSQL with psycopg2
|
|
169
|
+
|
|
170
|
+
Use `RealDictCursor`.
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
import psycopg2
|
|
174
|
+
from psycopg2.extras import RealDictCursor
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
connection = psycopg2.connect(DB_URL)
|
|
178
|
+
cursor = connection.cursor(cursor_factory=RealDictCursor)
|
|
179
|
+
|
|
180
|
+
cursor.execute("""
|
|
181
|
+
SELECT id, name, status
|
|
182
|
+
FROM customers
|
|
183
|
+
""")
|
|
184
|
+
|
|
185
|
+
table.set_data(cursor.fetchall())
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Plain DB-API Cursors
|
|
189
|
+
|
|
190
|
+
If your cursor returns tuples, convert them with `rows_from_cursor()`.
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
from CTkDataTable import rows_from_cursor
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
cursor.execute("""
|
|
197
|
+
SELECT id, name, status
|
|
198
|
+
FROM customers
|
|
199
|
+
""")
|
|
200
|
+
|
|
201
|
+
table.set_data(rows_from_cursor(cursor))
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
If database column names do not match your table column keys, use SQL aliases:
|
|
205
|
+
|
|
206
|
+
```sql
|
|
207
|
+
SELECT created_at AS created FROM customers;
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Column Types
|
|
211
|
+
|
|
212
|
+
Every column needs at least a `key`, `title`, and `width`.
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
{"key": "name", "title": "Name", "width": 180}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
You can also set a `type`.
|
|
219
|
+
|
|
220
|
+
### Text
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
{"key": "name", "title": "Name", "width": 180, "type": "text"}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Number
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
{"key": "amount", "title": "Amount", "width": 120, "type": "number"}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Format numbers with `number_format`:
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
{"key": "amount", "title": "Amount", "width": 120, "type": "number", "number_format": "${:,.2f}"}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Percentage
|
|
239
|
+
|
|
240
|
+
Use `percentage` for percent values. It right-aligns by default, sorts numerically, and appends `%`.
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
{"key": "margin", "title": "Margin", "width": 120, "type": "percentage"}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Customize output with `percentage_format`. If your row values are stored as ratios, multiply them before display:
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
{
|
|
250
|
+
"key": "margin",
|
|
251
|
+
"title": "Margin",
|
|
252
|
+
"width": 120,
|
|
253
|
+
"type": "percentage",
|
|
254
|
+
"percentage_format": "{value:.1f}%",
|
|
255
|
+
"percentage_multiplier": 100,
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Currency
|
|
260
|
+
|
|
261
|
+
Use `currency` for money values. It right-aligns by default, sorts numerically, and formats numeric row values.
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
{
|
|
265
|
+
"key": "amount",
|
|
266
|
+
"title": "Amount",
|
|
267
|
+
"width": 120,
|
|
268
|
+
"type": "currency",
|
|
269
|
+
"currency_symbol": "$",
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Customize output with `currency_format` and `currency_negative_format`:
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
{
|
|
277
|
+
"key": "amount",
|
|
278
|
+
"title": "Amount",
|
|
279
|
+
"width": 120,
|
|
280
|
+
"type": "currency",
|
|
281
|
+
"currency_symbol": "GBP ",
|
|
282
|
+
"currency_format": "{symbol}{value:,.2f}",
|
|
283
|
+
"currency_negative_format": "({symbol}{value:,.2f})",
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Date
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
{"key": "created", "title": "Created", "width": 130, "type": "date"}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Date columns accept `datetime.date`, `datetime.datetime`, and ISO date strings.
|
|
294
|
+
|
|
295
|
+
### Badge
|
|
296
|
+
|
|
297
|
+
Use badges for status-like values.
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
{
|
|
301
|
+
"key": "status",
|
|
302
|
+
"title": "Status",
|
|
303
|
+
"width": 130,
|
|
304
|
+
"type": "badge",
|
|
305
|
+
"badge_colors": {
|
|
306
|
+
"Open": "#2ecc71",
|
|
307
|
+
"Closed": "#e74c3c",
|
|
308
|
+
"Overdue": "#e67e22",
|
|
309
|
+
},
|
|
310
|
+
"badge_fallback_color": "#64748b",
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Pill List
|
|
315
|
+
|
|
316
|
+
Use `pill_list` for compact tag lists. Row values can be a list, tuple, set, or a comma-separated string.
|
|
317
|
+
|
|
318
|
+
```python
|
|
319
|
+
{
|
|
320
|
+
"key": "tags",
|
|
321
|
+
"title": "Tags",
|
|
322
|
+
"width": 180,
|
|
323
|
+
"type": "pill_list",
|
|
324
|
+
"pill_colors": {"Urgent": "#ef4444", "Finance": "#0ea5e9"},
|
|
325
|
+
"pill_fallback_color": "#64748b",
|
|
326
|
+
"pill_text_color": "#ffffff",
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Checkbox
|
|
331
|
+
|
|
332
|
+
Checkbox columns display boolean row values and toggle them when clicked. Toggle callbacks receive a
|
|
333
|
+
`TableRowEvent` with the updated row, `column_key` set to the checkbox column, and `action_key` set to
|
|
334
|
+
`"checkbox"`.
|
|
335
|
+
|
|
336
|
+
```python
|
|
337
|
+
from CTkDataTable import TableRowEvent
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def handle_checkbox(event: TableRowEvent) -> None:
|
|
341
|
+
approved = event.row[event.column_key]
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
columns = [
|
|
345
|
+
{"key": "approved", "title": "Approved", "width": 100, "type": "checkbox"},
|
|
346
|
+
]
|
|
347
|
+
|
|
348
|
+
table = CTkDataTable(app, columns=columns, data=rows, on_checkbox_toggle=handle_checkbox)
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Progress
|
|
352
|
+
|
|
353
|
+
Use `progress` for numeric completion values. Values are clamped between `progress_min` and `progress_max`.
|
|
354
|
+
|
|
355
|
+
```python
|
|
356
|
+
{
|
|
357
|
+
"key": "completion",
|
|
358
|
+
"title": "Complete",
|
|
359
|
+
"width": 140,
|
|
360
|
+
"type": "progress",
|
|
361
|
+
"progress_min": 0,
|
|
362
|
+
"progress_max": 100,
|
|
363
|
+
"progress_text_format": "{percent:.0f}%",
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Link
|
|
368
|
+
|
|
369
|
+
Use `link` for clickable text cells. Link clicks fire `on_link_click` with a `TableRowEvent`.
|
|
370
|
+
|
|
371
|
+
```python
|
|
372
|
+
from CTkDataTable import TableRowEvent
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def handle_link(event: TableRowEvent) -> None:
|
|
376
|
+
selected_profile = event.row
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
columns = [
|
|
380
|
+
{"key": "profile", "title": "Profile", "width": 130, "type": "link"},
|
|
381
|
+
]
|
|
382
|
+
|
|
383
|
+
table = CTkDataTable(app, columns=columns, data=rows, on_link_click=handle_link)
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Actions
|
|
387
|
+
|
|
388
|
+
Action columns draw buttons inside each row.
|
|
389
|
+
|
|
390
|
+
```python
|
|
391
|
+
from CTkDataTable import CTkDataTable, TableRowEvent
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def handle_action(event: TableRowEvent) -> None:
|
|
395
|
+
selected_action = event.action_key
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
columns = [
|
|
399
|
+
{"key": "id", "title": "ID", "width": 80},
|
|
400
|
+
{
|
|
401
|
+
"key": "actions",
|
|
402
|
+
"title": "Actions",
|
|
403
|
+
"width": 160,
|
|
404
|
+
"type": "action",
|
|
405
|
+
"sortable": False,
|
|
406
|
+
"actions": [
|
|
407
|
+
{"key": "view", "label": "View"},
|
|
408
|
+
{"key": "delete", "label": "Delete"},
|
|
409
|
+
],
|
|
410
|
+
},
|
|
411
|
+
]
|
|
412
|
+
|
|
413
|
+
table = CTkDataTable(
|
|
414
|
+
app,
|
|
415
|
+
columns=columns,
|
|
416
|
+
data=rows,
|
|
417
|
+
on_action_click=handle_action,
|
|
418
|
+
)
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
## Common Tasks
|
|
422
|
+
|
|
423
|
+
### Replace All Rows
|
|
424
|
+
|
|
425
|
+
```python
|
|
426
|
+
table.set_data(rows)
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Add One Row
|
|
430
|
+
|
|
431
|
+
```python
|
|
432
|
+
table.add_row({"id": 4, "name": "Diana", "status": "Open"})
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### Search from a Search Box
|
|
436
|
+
|
|
437
|
+
```python
|
|
438
|
+
search = ctk.CTkEntry(app, placeholder_text="Search")
|
|
439
|
+
search.grid(row=0, column=0, sticky="ew")
|
|
440
|
+
|
|
441
|
+
table.grid(row=1, column=0, sticky="nsew")
|
|
442
|
+
|
|
443
|
+
search.bind("<KeyRelease>", lambda _event: table.search(search.get()))
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Sort by a Column
|
|
447
|
+
|
|
448
|
+
```python
|
|
449
|
+
table.sort_by("name", ascending=True)
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Users can also click sortable column headers.
|
|
453
|
+
|
|
454
|
+
### Resize Columns
|
|
455
|
+
|
|
456
|
+
```python
|
|
457
|
+
table = CTkDataTable(app, columns=columns, data=rows, resizable_columns=True)
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Users can drag header dividers to resize columns. The table keeps the resize in memory for the current widget instance.
|
|
461
|
+
|
|
462
|
+
You can also change columns from code:
|
|
463
|
+
|
|
464
|
+
```python
|
|
465
|
+
table.set_column_width("name", 220)
|
|
466
|
+
table.set_columns([
|
|
467
|
+
{"key": "id", "title": "ID", "width": 80},
|
|
468
|
+
{"key": "name", "title": "Customer", "width": 220},
|
|
469
|
+
])
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### Style the Table
|
|
473
|
+
|
|
474
|
+
Use `style` to control the table surface, header, rows, selection, dividers, feature cells, padding, and corner radii.
|
|
475
|
+
Pass either a dictionary or a `TableStyle` object.
|
|
476
|
+
|
|
477
|
+
```python
|
|
478
|
+
table = CTkDataTable(
|
|
479
|
+
app,
|
|
480
|
+
columns=columns,
|
|
481
|
+
data=rows,
|
|
482
|
+
style={
|
|
483
|
+
"corner_radius": 12,
|
|
484
|
+
"border_width": 1,
|
|
485
|
+
"border_color": "#d1d5db",
|
|
486
|
+
"header_bg": "#f3f4f6",
|
|
487
|
+
"row_bg": "#ffffff",
|
|
488
|
+
"row_alt_bg": "#f9fafb",
|
|
489
|
+
"hover_bg": "#eef6ff",
|
|
490
|
+
"selected_bg": "#2563eb",
|
|
491
|
+
"selected_text_color": "#ffffff",
|
|
492
|
+
"divider_color": "#e5e7eb",
|
|
493
|
+
"badge_radius": 8,
|
|
494
|
+
"action_radius": 6,
|
|
495
|
+
"cell_padding_x": 14,
|
|
496
|
+
},
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
table.configure_style(header_bg="#111827", header_text_color="#ffffff")
|
|
500
|
+
table.set_style(row_bg="#ffffff", row_alt_bg="#f8fafc")
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
### Style Rows and Cells
|
|
504
|
+
|
|
505
|
+
Style hooks are opt-in. Passing `row_style` or `cell_style` without `enable_style_hooks=True` raises a clear error.
|
|
506
|
+
|
|
507
|
+
```python
|
|
508
|
+
def row_style(row):
|
|
509
|
+
if row["status"] == "Overdue":
|
|
510
|
+
return {"fg_color": "#fff7ed", "text_color": "#9a3412"}
|
|
511
|
+
return None
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def cell_style(row, column_key, value):
|
|
515
|
+
if column_key == "amount" and value < 0:
|
|
516
|
+
return {"text_color": "#dc2626"}
|
|
517
|
+
return None
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
table = CTkDataTable(
|
|
521
|
+
app,
|
|
522
|
+
columns=columns,
|
|
523
|
+
data=rows,
|
|
524
|
+
enable_style_hooks=True,
|
|
525
|
+
row_style=row_style,
|
|
526
|
+
cell_style=cell_style,
|
|
527
|
+
)
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### Filter by Column
|
|
531
|
+
|
|
532
|
+
Column filters combine with global search.
|
|
533
|
+
|
|
534
|
+
```python
|
|
535
|
+
table.set_column_filter("status", {"type": "equals", "value": "Open"})
|
|
536
|
+
table.set_column_filter("amount", {"type": "range", "min": 100, "max": 500})
|
|
537
|
+
table.clear_column_filter("status")
|
|
538
|
+
table.clear_column_filters()
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
Supported filter types are `contains`, `equals`, `not_equals`, `in`, `bool`, `range`, and `date_range`.
|
|
542
|
+
|
|
543
|
+
### Add a Context Menu
|
|
544
|
+
|
|
545
|
+
```python
|
|
546
|
+
def handle_context(event: TableRowEvent) -> None:
|
|
547
|
+
selected_action = event.action_key
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
table = CTkDataTable(
|
|
551
|
+
app,
|
|
552
|
+
columns=columns,
|
|
553
|
+
data=rows,
|
|
554
|
+
context_menu=[
|
|
555
|
+
{"key": "copy_id", "label": "Copy ID"},
|
|
556
|
+
{"key": "delete", "label": "Delete"},
|
|
557
|
+
],
|
|
558
|
+
on_context_action=handle_context,
|
|
559
|
+
)
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### Show a Footer Summary
|
|
563
|
+
|
|
564
|
+
Footer summaries use the current visible rows after search and column filters.
|
|
565
|
+
|
|
566
|
+
```python
|
|
567
|
+
table = CTkDataTable(
|
|
568
|
+
app,
|
|
569
|
+
columns=columns,
|
|
570
|
+
data=rows,
|
|
571
|
+
footer=True,
|
|
572
|
+
summaries={
|
|
573
|
+
"id": "count",
|
|
574
|
+
"amount": "sum",
|
|
575
|
+
"status": lambda rows: f"{len(rows)} visible",
|
|
576
|
+
},
|
|
577
|
+
)
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
Built-in summaries are `count`, `sum`, `avg`, `min`, and `max`.
|
|
581
|
+
|
|
582
|
+
### Load Rows in the Background
|
|
583
|
+
|
|
584
|
+
```python
|
|
585
|
+
def fetch_rows():
|
|
586
|
+
return database.load_customers()
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
table.load_async(
|
|
590
|
+
fetch_rows,
|
|
591
|
+
on_success=lambda rows: None,
|
|
592
|
+
on_error=lambda error: None,
|
|
593
|
+
)
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
`load_async()` shows the loading state, runs your fetch function in a background thread, and updates the table safely on the Tkinter thread.
|
|
597
|
+
|
|
598
|
+
### Get the Selected Row
|
|
599
|
+
|
|
600
|
+
```python
|
|
601
|
+
selected = table.get_selected_row()
|
|
602
|
+
if selected is not None:
|
|
603
|
+
selected_id = selected.get("id")
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
For row identity, use source-data indices or current view indices:
|
|
607
|
+
|
|
608
|
+
```python
|
|
609
|
+
source_indices = table.get_selected_indices()
|
|
610
|
+
view_indices = table.get_selected_view_indices()
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
### Delete Rows
|
|
614
|
+
|
|
615
|
+
```python
|
|
616
|
+
table.delete_row(0)
|
|
617
|
+
table.delete_view_row(0)
|
|
618
|
+
table.delete_row_by_key("id", 4)
|
|
619
|
+
table.delete_selected_rows()
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
`delete_row(index)` uses the original source-data index. `delete_row_by_key()` is usually easier after sorting or filtering.
|
|
623
|
+
Use `delete_view_row(view_index)` when you intentionally want to target the current visible row order.
|
|
624
|
+
|
|
625
|
+
### Detailed Event Payloads
|
|
626
|
+
|
|
627
|
+
Interaction callbacks receive `TableRowEvent` objects when you need the row, source index, visible index, clicked column, or clicked action.
|
|
628
|
+
|
|
629
|
+
```python
|
|
630
|
+
def handle_action(event):
|
|
631
|
+
row_identity = (event.source_index, event.view_index, event.action_key)
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
table = CTkDataTable(
|
|
635
|
+
app,
|
|
636
|
+
columns=columns,
|
|
637
|
+
data=rows,
|
|
638
|
+
on_action_click=handle_action,
|
|
639
|
+
)
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
For link cells, `event.column_key` is the link column and `event.action_key` is `"link"`.
|
|
643
|
+
For checkbox cells, `event.column_key` is the checkbox column, `event.action_key` is `"checkbox"`, and
|
|
644
|
+
`event.row[event.column_key]` is the new boolean value.
|
|
645
|
+
|
|
646
|
+
### Show a Loading State
|
|
647
|
+
|
|
648
|
+
```python
|
|
649
|
+
table.set_loading(True)
|
|
650
|
+
table.set_data(rows)
|
|
651
|
+
table.set_loading(False)
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### Enable Horizontal Scrolling
|
|
655
|
+
|
|
656
|
+
Use this when the total column width is wider than the window.
|
|
657
|
+
|
|
658
|
+
```python
|
|
659
|
+
table = CTkDataTable(app, columns=columns, data=rows, horizontal_scroll=True)
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
### Keyboard Navigation
|
|
663
|
+
|
|
664
|
+
When the table has focus, use Up, Down, Page Up, Page Down, Home, and End to move selection. Press Enter to trigger the double-click row callback. In multi-select mode, Shift extends the selection range.
|
|
665
|
+
|
|
666
|
+
## Notes
|
|
667
|
+
|
|
668
|
+
- The table does not edit text, number, date, or badge cells inline. Checkbox columns can toggle boolean values.
|
|
669
|
+
- The table does not run database queries. Query your database yourself, then call `set_data()`.
|
|
670
|
+
- The table only draws visible rows, so large lists scroll smoothly.
|
|
671
|
+
- `delete_row(index)` deletes from the original data list, not the currently visible filtered or sorted row number.
|
|
672
|
+
- Searching clears selected rows that are no longer visible.
|
|
673
|
+
- Sorting and searching reset the vertical scroll position to the top.
|
|
674
|
+
|
|
675
|
+
## More Detail
|
|
676
|
+
|
|
677
|
+
- Full API reference: [docs/Docs.md](https://github.com/Harry-g25/CTkTableData/blob/main/docs/Docs.md)
|
|
678
|
+
- Publishing guide: [docs/Publishing.md](https://github.com/Harry-g25/CTkTableData/blob/main/docs/Publishing.md)
|
|
679
|
+
- Standalone HTML guide: [docs/Docs.html](https://github.com/Harry-g25/CTkTableData/blob/main/docs/Docs.html)
|
|
680
|
+
- Basic example: [CTkDataTable/examples/basic_table.py](https://github.com/Harry-g25/CTkTableData/blob/main/CTkDataTable/examples/basic_table.py)
|
|
681
|
+
- NCR records example: [CTkDataTable/examples/ncr_records.py](https://github.com/Harry-g25/CTkTableData/blob/main/CTkDataTable/examples/ncr_records.py)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
CTkDataTable/__init__.py,sha256=P11xSuOMlhMsqQIy52wqUM10B1f5xXOKL9EdMexnWgc,394
|
|
2
|
+
CTkDataTable/_utils.py,sha256=8VXDLPN73_087FgPqrm1eZdjysEdu7Q9dvZUhT-VuuM,1813
|
|
3
|
+
CTkDataTable/ctk_data_table.py,sha256=JIf_bEIYjpsZuG_t3Miy-hTAxYEMNJttv4JHuU8n2MY,67887
|
|
4
|
+
CTkDataTable/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
5
|
+
CTkDataTable/table_column.py,sha256=Dy9PjRKTxT59hG56_v8OWVbzwigjq4mPFaVq2IwL0i0,16737
|
|
6
|
+
CTkDataTable/table_events.py,sha256=A2TacxpGLYRelbXWPvmVLWdd-9msN3AHt87xcOYHddI,400
|
|
7
|
+
CTkDataTable/table_model.py,sha256=OC3GXljescOxs9oF9V8rr5co1UbDXKvqP039wiFRSAU,23863
|
|
8
|
+
CTkDataTable/table_renderer.py,sha256=04IRmK_boUBR02wQXK-ksozlAhW2ZZjNwtmmEGLfF9Y,43551
|
|
9
|
+
CTkDataTable/table_style.py,sha256=50o0lqOsjttuKeogj9LqH_ZIPkTYKmP4adUpCDA_eJI,7638
|
|
10
|
+
CTkDataTable/examples/__init__.py,sha256=2mDoEpUzwuLeNzf2GMEwa6lQSVux4eWT7KERXirnoU0,45
|
|
11
|
+
CTkDataTable/examples/basic_table.py,sha256=S4NnIwBNc0gHcnwDttJr3lngfQIgldkG2ZZfIADzlYs,7453
|
|
12
|
+
ctkdatatable-0.1.0.dist-info/licenses/LICENSE,sha256=P6rR8xoQJKDIOADNG6W0oI-axVRS5SWOSC0XlzI7tkA,1067
|
|
13
|
+
ctkdatatable-0.1.0.dist-info/METADATA,sha256=XUrDMDDdbTkKd0IWDiypXhb6DvrnzivU-z9OxyECaZQ,16133
|
|
14
|
+
ctkdatatable-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
15
|
+
ctkdatatable-0.1.0.dist-info/top_level.txt,sha256=qMa16zN3yNS8gjqIWs8KIpo9iSXXFtdAN854irTDUFI,13
|
|
16
|
+
ctkdatatable-0.1.0.dist-info/RECORD,,
|