Skip to content

Latest commit

 

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation


What is CTkDataTable?

CTkDataTable is a Python module for building cleaner, more practical data tables inside customtkinter desktop applications.

It was created to solve a common problem: CustomTkinter is great for modern desktop interfaces, but displaying structured table data can still be awkward. Standard Tkinter options such as ttk.Treeview often feel dated, difficult to style, or out of place in a modern UI.

CTkDataTable provides a configurable table widget designed for internal tools, dashboards, admin panels, database applications and workflow software.


Why Use It?

CustomTkinter Friendly

Designed to fit naturally into modern CustomTkinter applications.

Dictionary Based

Define columns and rows using simple Python dictionaries.

Practical

Built for dashboards, admin tools, database viewers and internal systems.


Features

  • Built for customtkinter
  • Simple column configuration
  • Row data passed as dictionaries
  • Configurable column titles
  • Configurable column widths
  • Text columns
  • Number columns
  • Badge columns
  • Contrast-aware badge and pill labels
  • Table-style combobox columns with scrollable, searchable popups
  • Stable row IDs and structured edit events
  • Per-column and table-wide validation
  • Read-only tables and columns
  • Cleaner alternative to ttk.Treeview
  • Resizable columns
  • Fill-width layouts
  • Horizontal scrolling
  • Compact, comfortable and spacious density presets
  • Hover, pressed, keyboard-focus and truncation-tooltip feedback
  • Useful for desktop dashboards and database-driven apps

Installation

pip install CTkDataTable

CTkDataTable requires Python 3.11 or newer. Pip also installs the compatible customtkinter and dropdown dependencies.


Quick Start

import customtkinter as ctk
from CTkDataTable import CTkDataTable

app = ctk.CTk()
app.title("CTkDataTable Example")
app.geometry("900x500")

columns = [
    {
        "key": "id",
        "title": "ID",
        "width": 50,
        "type": "number"
    },
    {
        "key": "first_name",
        "title": "First Name",
        "width": 140,
        "type": "text"
    },
    {
        "key": "last_name",
        "title": "Last Name",
        "width": 140,
        "type": "text"
    },
    {
        "key": "position",
        "title": "Position",
        "width": 180,
        "type": "text"
    },
    {
        "key": "permission",
        "title": "Permission",
        "width": 140,
        "type": "badge",
        "badge_colors": {
            "Admin": ("#fecaca", "#7f1d1d"),
            "Manager": ("#bfdbfe", "#1e3a8a"),
            "Standard": ("#e5e7eb", "#374151")
        }
    }
]

rows = [
    {
        "id": 1,
        "first_name": "Harry",
        "last_name": "Gomm",
        "position": "Manager",
        "permission": "Manager"
    },
    {
        "id": 2,
        "first_name": "Ben",
        "last_name": "Jones",
        "position": "Engineer",
        "permission": "Standard"
    },
    {
        "id": 3,
        "first_name": "Charlie",
        "last_name": "Smith",
        "position": "Admin",
        "permission": "Admin"
    }
]

table = CTkDataTable(
    master=app,
    columns=columns,
    data=rows,
    row_key="id",
    column_width_mode="fill",
    resizable_columns=True
)

table.pack(fill="both", expand=True, padx=20, pady=20)

app.mainloop()

How It Works

flowchart LR
    A[Define Columns] --> B[Create Row Data]
    B --> C[Pass Data to CTkDataTable]
    C --> D[Render Table]
    D --> E[Display Structured Data]
Loading

Column Configuration

Columns are defined using dictionaries.

columns = [
    {
        "key": "first_name",
        "title": "First Name",
        "width": 140,
        "type": "text"
    }
]
Property Description
key The key used to match data from each row
title The text displayed in the table header
width The width of the column
type The column display type

Supported Column Types

Text

For names, labels, descriptions and general values.

Number

For IDs, counts, quantities and numeric data.

Badge

For statuses, permissions, categories and priority labels.

Additional built-in types include percentage, currency, date, datetime, progress, link, pill-list, checkbox, combobox and action columns.

Text Column

{
    "key": "name",
    "title": "Name",
    "width": 160,
    "type": "text"
}

Number Column

{
    "key": "id",
    "title": "ID",
    "width": 60,
    "type": "number"
}

Badge Column

{
    "key": "permission",
    "title": "Permission",
    "width": 140,
    "type": "badge",
    "badge_colors": {
        "Admin": ("#fecaca", "#7f1d1d"),
        "Manager": ("#bfdbfe", "#1e3a8a"),
        "Standard": ("#e5e7eb", "#374151")
    }
}

Combobox Column

Combobox cells keep a dropdown arrow visible inside the table and open a themed, scrollable CTkScrollableDropdownPP popup. Lists with more than ten choices get search automatically. Options may store the same string they display, or use ComboOption to separate a friendly label from the value saved to your database.

from CTkDataTable import CellChangeEvent, ComboOption


def status_changed(event: CellChangeEvent) -> None:
    print(event.row_id, event.old_value, "->", event.new_value)


{
    "key": "status",
    "title": "Status",
    "width": 180,
    "type": "combobox",
    "options": [
        ComboOption("Pending", "pending"),
        ComboOption("Active", "active"),
        ComboOption("Complete", "complete"),
    ],
    "allow_custom": False,
    "allow_empty": True,
    "empty_value": None,
    "empty_label": "No status",
    "dropdown_height": 300,
    "dropdown_width": 240,
    "searchable": True,
    "on_change": status_changed
}

Set searchable=True or False to override automatic search, and use items_per_page for very long lists. Set allow_custom=True to accept typed values. Pressing Enter or clicking elsewhere commits custom text, while Escape cancels it. With allow_empty=True, the labelled empty choice and Delete/Backspace use empty_value; leaving its default as None maps naturally to SQL NULL. Existing values outside the configured choices remain visible until the user changes them. Installing CTkDataTable installs the dropdown dependency automatically.

Editing and Saving

Give database-backed tables a stable identity with row_key="id". Every row must then contain a unique, hashable ID. CellChangeEvent.row_id remains the record identity even when sorting or filtering changes the row's visible position.

get_data(), get_cell(), and the other getters are pure reads: they never commit an editor as a side effect. Put the edit boundary in your Save button:

def save_to_database() -> None:
    if not table.commit_edit():
        print("Cannot save:", table.edit_validation_error)
        return

    rows = table.get_data()
    # Run parameterized INSERT/UPDATE statements, then commit your transaction.

Use cancel_edit() to discard the active typed edit. For targeted access, call get_cell(source_index, "status"), get_cell_by_id(row_id, "status"), update_cell(...), or update_cell_by_id(...). Programmatic updates validate but are quiet by default; pass notify=True to emit a CellChangeEvent with origin="api". Stable-ID row helpers include get_row_by_id(), update_row_by_id(), and delete_row_by_id().

Editable checkbox and combobox columns accept editable=False, validator=..., and on_change=.... The table accepts read_only=True, cell_validator=..., and on_cell_change=.... Validators receive a CellEditRequest and return None to accept or an error message to reject. A rejected typed edit stays open so the user can correct it.

For each successful change, the model and view update first, then the column's on_change, then the table's on_cell_change. CellChangeEvent includes widget, row_id, a read-only row snapshot, source_index, view_index_before, view_index_after, column_key, old_value, new_value, and origin. on_selection_change similarly receives a SelectionChangeEvent with current, added, and removed rows/IDs/indices. Selection can also be controlled with select_row(), select_row_by_id(), and clear_selection(). The existing on_checkbox_toggle(TableRowEvent) callback remains supported for compatibility and runs after the new cell-change callbacks.

The widget intentionally does not own a database connection or transaction. If a SQL save fails, keep your original rows or reload them with set_data() after rolling back.


Row Data

Rows are passed as a list of dictionaries.

rows = [
    {
        "id": 1,
        "first_name": "Harry",
        "last_name": "Gomm",
        "position": "Manager",
        "permission": "Manager"
    }
]

Each row key should match the key value defined in the column configuration.


Use Cases

CTkDataTable can be used for:

  • Admin panels
  • User management screens
  • Database viewers
  • Desktop dashboards
  • CRUD applications
  • Job management tools
  • Stock or asset registers
  • Reporting interfaces

Project Status

CTkDataTable is available on PyPI for production use and actively maintained through practical use in real CustomTkinter desktop applications. Review the changelog when upgrading. Feedback, issues and suggestions are welcome.


Contributing

Contributions are welcome.

If you find a bug, have an idea for a feature, or want to improve the documentation, feel free to open an issue or submit a pull request.


Licence

This project is released under the MIT Licence.


Links


Built for clean, practical CustomTkinter applications.

About

A modern and configurable data table widget for CustomTkinter, designed for building cleaner desktop apps, dashboards and database-driven tools.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Contributors

Languages