Back to Top

About tksheet

Limitations

Some examples of things that are not possible with tksheet:


Installation and Requirements

tksheet is available through PyPi (Python package index) and can be installed by using Pip through the command line pip install tksheet

#To install using pip
pip install tksheet

#To update using pip
pip install tksheet --upgrade

Alternatively you can download the source code and inside the tksheet directory where the pyproject.toml file is located use the command line pip install -e .


Basic Initialization

Like other tkinter widgets you need only the Sheet()s parent as an argument to initialize a Sheet() e.g.

sheet = Sheet(my_frame_widget)

As an example, this is a tkinter program involving a Sheet() widget

from tksheet import Sheet
import tkinter as tk


class demo(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.grid_columnconfigure(0, weight = 1)
        self.grid_rowconfigure(0, weight = 1)
        self.frame = tk.Frame(self)
        self.frame.grid_columnconfigure(0, weight = 1)
        self.frame.grid_rowconfigure(0, weight = 1)
        self.sheet = Sheet(self.frame,
                           data = [[f"Row {r}, Column {c}\nnewline1\nnewline2" for c in range(50)] for r in range(500)])
        self.sheet.enable_bindings()
        self.frame.grid(row = 0, column = 0, sticky = "nswe")
        self.sheet.grid(row = 0, column = 0, sticky = "nswe")


app = demo()
app.mainloop()

Usage Examples

This is to demonstrate some of tksheets functionality:

from tksheet import Sheet, num2alpha
import tkinter as tk


class demo(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)
        self.frame = tk.Frame(self)
        self.frame.grid_columnconfigure(0, weight=1)
        self.frame.grid_rowconfigure(0, weight=1)

        # create an instance of Sheet()
        self.sheet = Sheet(
            # set the Sheets parent widget
            self.frame,
            # optional: set the Sheets data at initialization
            data=[[f"Row {r}, Column {c}\nnewline1\nnewline2" for c in range(20)] for r in range(100)],
            theme="light green",
            height=520,
            width=1000,
        )
        # enable various bindings
        self.sheet.enable_bindings("all", "edit_index", "edit_header")

        # set a user edit validation function
        # AND bind all sheet modification events to a function
        # chained as two functions
        # more information at:
        # #validate-user-cell-edits
        self.sheet.edit_validation(self.validate_edits).bind("<<SheetModified>>", self.sheet_modified)

        # add some new commands to the in-built right click menu
        # setting data
        self.sheet.popup_menu_add_command(
            "Say Hello",
            self.say_hello,
            index_menu=False,
            header_menu=False,
            empty_space_menu=False,
        )
        # getting data
        self.sheet.popup_menu_add_command(
            "Print some data",
            self.print_data,
            empty_space_menu=False,
        )
        # overwrite Sheet data
        self.sheet.popup_menu_add_command("Reset Sheet data", self.reset)
        # set the header
        self.sheet.popup_menu_add_command(
            "Set header data",
            self.set_header,
            table_menu=False,
            index_menu=False,
            empty_space_menu=False,
        )
        # set the index
        self.sheet.popup_menu_add_command(
            "Set index data",
            self.set_index,
            table_menu=False,
            header_menu=False,
            empty_space_menu=False,
        )

        self.frame.grid(row=0, column=0, sticky="nswe")
        self.sheet.grid(row=0, column=0, sticky="nswe")

    def validate_edits(self, event):
        # print (event)
        if event.eventname.endswith("header"):
            return event.value + " edited header"
        elif event.eventname.endswith("index"):
            return event.value + " edited index"
        else:
            if not event.value:
                return "EMPTY"
            return event.value[:3]

    def say_hello(self):
        current_selection = self.sheet.get_currently_selected()
        if current_selection:
            box = (current_selection.row, current_selection.column)
            # set cell data, end user Undo enabled
            # more information at:
            # #setting-sheet-data
            self.sheet[box].options(undo=True).data = "Hello World!"
            # highlight the cell for 2 seconds
            self.highlight_area(box)

    def print_data(self):
        for box in self.sheet.get_all_selection_boxes():
            # get user selected area sheet data
            # more information at:
            # #getting-sheet-data
            data = self.sheet[box].data
            for row in data:
                print(row)

    def reset(self):
        # overwrites sheet data, more information at:
        # #setting-sheet-data
        self.sheet.set_sheet_data([[f"Row {r}, Column {c}\nnewline1\nnewline2" for c in range(20)] for r in range(100)])
        # reset header and index
        self.sheet.headers([])
        self.sheet.index([])

    def set_header(self):
        self.sheet.headers(
            [f"Header {(letter := num2alpha(i))} - {i + 1}\nHeader {letter} 2nd line!" for i in range(20)]
        )

    def set_index(self):
        self.sheet.set_index_width()
        self.sheet.row_index(
            [f"Index {(letter := num2alpha(i))} - {i + 1}\nIndex {letter} 2nd line!" for i in range(100)]
        )

    def sheet_modified(self, event):
        # uncomment below if you want to take a look at the event object
        # print ("The sheet was modified! Event object:")
        # for k, v in event.items():
        #     print (k, ":", v)
        # print ("\n")

        # otherwise more information at:
        # #event-data

        # highlight the modified cells briefly
        if event.eventname.startswith("move"):
            for box in self.sheet.get_all_selection_boxes():
                self.highlight_area(box)
        else:
            for box in event.selection_boxes:
                self.highlight_area(box)

    def highlight_area(self, box, time=800):
        # highlighting an area of the sheet
        # more information at:
        # #highlighting-cells
        self.sheet[box].bg = "indianred1"
        self.after(time, lambda: self.clear_highlight(box))

    def clear_highlight(self, box):
        self.sheet[box].dehighlight()


app = demo()
app.mainloop()

Initialization Options

These are all the initialization parameters, the only required argument is the sheets parent, every other parameter has default arguments.

def __init__(
    parent: tk.Misc,
    name: str = "!sheet",
    show_table: bool = True,
    show_top_left: bool | None = None,
    show_row_index: bool = True,
    show_header: bool = True,
    show_x_scrollbar: bool = True,
    show_y_scrollbar: bool = True,
    width: int | None = None,
    height: int | None = None,
    headers: None | list[object] = None,
    header: None | list[object] = None,
    row_index: None | list[object] = None,
    index: None | list[object] = None,
    default_header: Literal["letters", "numbers", "both"] | None = "letters",
    default_row_index: Literal["letters", "numbers", "both"] | None = "numbers",
    data_reference: None | Sequence[Sequence[object]] = None,
    data: None | Sequence[Sequence[object]] = None,
    # either (start row, end row, "rows"), (start column, end column, "rows") or
    # (cells start row, cells start column, cells end row, cells end column, "cells")  # noqa: E501
    startup_select: tuple[int, int, str] | tuple[int, int, int, int, str] = None,
    startup_focus: bool = True,
    total_columns: int | None = None,
    total_rows: int | None = None,
    default_column_width: int = 120,
    default_header_height: str | int = "1",
    default_row_index_width: int = 70,
    default_row_height: str | int = "1",
    min_column_width: int = 1,
    max_column_width: float = float("inf"),
    max_row_height: float = float("inf"),
    max_header_height: float = float("inf"),
    max_index_width: float = float("inf"),
    after_redraw_time_ms: int = 20,
    set_all_heights_and_widths: bool = False,
    zoom: int = 100,
    align: str = "w",
    header_align: str = "center",
    row_index_align: str | None = None,
    index_align: str = "center",
    displayed_columns: list[int] = [],
    all_columns_displayed: bool = True,
    displayed_rows: list[int] = [],
    all_rows_displayed: bool = True,
    to_clipboard_delimiter: str = "\t",
    to_clipboard_quotechar: str = '"',
    to_clipboard_lineterminator: str = "\n",
    from_clipboard_delimiters: list[str] | str = ["\t"],
    show_default_header_for_empty: bool = True,
    show_default_index_for_empty: bool = True,
    page_up_down_select_row: bool = True,
    paste_can_expand_x: bool = False,
    paste_can_expand_y: bool = False,
    paste_insert_column_limit: int | None = None,
    paste_insert_row_limit: int | None = None,
    show_dropdown_borders: bool = False,
    arrow_key_down_right_scroll_page: bool = False,
    cell_auto_resize_enabled: bool = True,
    auto_resize_row_index: bool | Literal["empty"] = "empty",
    auto_resize_columns: int | None = None,
    auto_resize_rows: int | None = None,
    set_cell_sizes_on_zoom: bool = False,
    font: tuple[str, int, str] = FontTuple(
        "Calibri",
        13 if USER_OS == "darwin" else 11,
        "normal",
    ),
    header_font: tuple[str, int, str] = FontTuple(
        "Calibri",
        13 if USER_OS == "darwin" else 11,
        "normal",
    ),
    index_font: tuple[str, int, str] = FontTuple(
        "Calibri",
        13 if USER_OS == "darwin" else 11,
        "normal",
    ),  # currently has no effect
    popup_menu_font: tuple[str, int, str] = FontTuple(
        "Calibri",
        13 if USER_OS == "darwin" else 11,
        "normal",
    ),
    max_undos: int = 30,
    column_drag_and_drop_perform: bool = True,
    row_drag_and_drop_perform: bool = True,
    empty_horizontal: int = 50,
    empty_vertical: int = 50,
    selected_rows_to_end_of_window: bool = False,
    horizontal_grid_to_end_of_window: bool = False,
    vertical_grid_to_end_of_window: bool = False,
    show_vertical_grid: bool = True,
    show_horizontal_grid: bool = True,
    display_selected_fg_over_highlights: bool = False,
    show_selected_cells_border: bool = True,
    edit_cell_tab: Literal["right", "down", ""] = "right",
    edit_cell_return: Literal["right", "down", ""] = "down",
    editor_del_key: Literal["forward", "backward", ""] = "forward",
    treeview: bool = False,
    treeview_indent: str | int = "5",
    rounded_boxes: bool = True,
    alternate_color: str = "",
    allow_cell_overflow: bool = False,
    table_wrap: Literal["", "w", "c"] = "c",  # "" no wrap, "w" word wrap, "c" char wrap
    index_wrap: Literal["", "w", "c"] = "c",  # "" no wrap, "w" word wrap, "c" char wrap
    header_wrap: Literal["", "w", "c"] = "c",  # "" no wrap, "w" word wrap, "c" char wrap
    sort_key: Callable = natural_sort_key,
    # colors
    outline_thickness: int = 0,
    outline_color: str = theme_light_blue["outline_color"],
    theme: str = "light blue",
    frame_bg: str = theme_light_blue["table_bg"],
    popup_menu_fg: str = theme_light_blue["popup_menu_fg"],
    popup_menu_bg: str = theme_light_blue["popup_menu_bg"],
    popup_menu_highlight_bg: str = theme_light_blue["popup_menu_highlight_bg"],
    popup_menu_highlight_fg: str = theme_light_blue["popup_menu_highlight_fg"],
    table_grid_fg: str = theme_light_blue["table_grid_fg"],
    table_bg: str = theme_light_blue["table_bg"],
    table_fg: str = theme_light_blue["table_fg"],
    table_editor_bg: str = theme_light_blue["table_editor_bg"],
    table_editor_fg: str = theme_light_blue["table_editor_fg"],
    table_editor_select_bg: str = theme_light_blue["table_editor_select_bg"],
    table_editor_select_fg: str = theme_light_blue["table_editor_select_fg"],
    table_selected_box_cells_fg: str = theme_light_blue["table_selected_box_cells_fg"],
    table_selected_box_rows_fg: str = theme_light_blue["table_selected_box_rows_fg"],
    table_selected_box_columns_fg: str = theme_light_blue["table_selected_box_columns_fg"],
    table_selected_cells_border_fg: str = theme_light_blue["table_selected_cells_border_fg"],
    table_selected_cells_bg: str = theme_light_blue["table_selected_cells_bg"],
    table_selected_cells_fg: str = theme_light_blue["table_selected_cells_fg"],
    table_selected_rows_border_fg: str = theme_light_blue["table_selected_rows_border_fg"],
    table_selected_rows_bg: str = theme_light_blue["table_selected_rows_bg"],
    table_selected_rows_fg: str = theme_light_blue["table_selected_rows_fg"],
    table_selected_columns_border_fg: str = theme_light_blue["table_selected_columns_border_fg"],
    table_selected_columns_bg: str = theme_light_blue["table_selected_columns_bg"],
    table_selected_columns_fg: str = theme_light_blue["table_selected_columns_fg"],
    resizing_line_fg: str = theme_light_blue["resizing_line_fg"],
    drag_and_drop_bg: str = theme_light_blue["drag_and_drop_bg"],
    index_bg: str = theme_light_blue["index_bg"],
    index_border_fg: str = theme_light_blue["index_border_fg"],
    index_grid_fg: str = theme_light_blue["index_grid_fg"],
    index_fg: str = theme_light_blue["index_fg"],
    index_editor_bg: str = theme_light_blue["index_editor_bg"],
    index_editor_fg: str = theme_light_blue["index_editor_fg"],
    index_editor_select_bg: str = theme_light_blue["index_editor_select_bg"],
    index_editor_select_fg: str = theme_light_blue["index_editor_select_fg"],
    index_selected_cells_bg: str = theme_light_blue["index_selected_cells_bg"],
    index_selected_cells_fg: str = theme_light_blue["index_selected_cells_fg"],
    index_selected_rows_bg: str = theme_light_blue["index_selected_rows_bg"],
    index_selected_rows_fg: str = theme_light_blue["index_selected_rows_fg"],
    index_hidden_rows_expander_bg: str = theme_light_blue["index_hidden_rows_expander_bg"],
    header_bg: str = theme_light_blue["header_bg"],
    header_border_fg: str = theme_light_blue["header_border_fg"],
    header_grid_fg: str = theme_light_blue["header_grid_fg"],
    header_fg: str = theme_light_blue["header_fg"],
    header_editor_bg: str = theme_light_blue["header_editor_bg"],
    header_editor_fg: str = theme_light_blue["header_editor_fg"],
    header_editor_select_bg: str = theme_light_blue["header_editor_select_bg"],
    header_editor_select_fg: str = theme_light_blue["header_editor_select_fg"],
    header_selected_cells_bg: str = theme_light_blue["header_selected_cells_bg"],
    header_selected_cells_fg: str = theme_light_blue["header_selected_cells_fg"],
    header_selected_columns_bg: str = theme_light_blue["header_selected_columns_bg"],
    header_selected_columns_fg: str = theme_light_blue["header_selected_columns_fg"],
    header_hidden_columns_expander_bg: str = theme_light_blue["header_hidden_columns_expander_bg"],
    top_left_bg: str = theme_light_blue["top_left_bg"],
    top_left_fg: str = theme_light_blue["top_left_fg"],
    top_left_fg_highlight: str = theme_light_blue["top_left_fg_highlight"],
    vertical_scroll_background: str = theme_light_blue["vertical_scroll_background"],
    horizontal_scroll_background: str = theme_light_blue["horizontal_scroll_background"],
    vertical_scroll_troughcolor: str = theme_light_blue["vertical_scroll_troughcolor"],
    horizontal_scroll_troughcolor: str = theme_light_blue["horizontal_scroll_troughcolor"],
    vertical_scroll_lightcolor: str = theme_light_blue["vertical_scroll_lightcolor"],
    horizontal_scroll_lightcolor: str = theme_light_blue["horizontal_scroll_lightcolor"],
    vertical_scroll_darkcolor: str = theme_light_blue["vertical_scroll_darkcolor"],
    horizontal_scroll_darkcolor: str = theme_light_blue["horizontal_scroll_darkcolor"],
    vertical_scroll_relief: str = theme_light_blue["vertical_scroll_relief"],
    horizontal_scroll_relief: str = theme_light_blue["horizontal_scroll_relief"],
    vertical_scroll_troughrelief: str = theme_light_blue["vertical_scroll_troughrelief"],
    horizontal_scroll_troughrelief: str = theme_light_blue["horizontal_scroll_troughrelief"],
    vertical_scroll_bordercolor: str = theme_light_blue["vertical_scroll_bordercolor"],
    horizontal_scroll_bordercolor: str = theme_light_blue["horizontal_scroll_bordercolor"],
    vertical_scroll_borderwidth: int = 1,
    horizontal_scroll_borderwidth: int = 1,
    vertical_scroll_gripcount: int = 0,
    horizontal_scroll_gripcount: int = 0,
    vertical_scroll_active_bg: str = theme_light_blue["vertical_scroll_active_bg"],
    horizontal_scroll_active_bg: str = theme_light_blue["horizontal_scroll_active_bg"],
    vertical_scroll_not_active_bg: str = theme_light_blue["vertical_scroll_not_active_bg"],
    horizontal_scroll_not_active_bg: str = theme_light_blue["horizontal_scroll_not_active_bg"],
    vertical_scroll_pressed_bg: str = theme_light_blue["vertical_scroll_pressed_bg"],
    horizontal_scroll_pressed_bg: str = theme_light_blue["horizontal_scroll_pressed_bg"],
    vertical_scroll_active_fg: str = theme_light_blue["vertical_scroll_active_fg"],
    horizontal_scroll_active_fg: str = theme_light_blue["horizontal_scroll_active_fg"],
    vertical_scroll_not_active_fg: str = theme_light_blue["vertical_scroll_not_active_fg"],
    horizontal_scroll_not_active_fg: str = theme_light_blue["horizontal_scroll_not_active_fg"],
    vertical_scroll_pressed_fg: str = theme_light_blue["vertical_scroll_pressed_fg"],
    horizontal_scroll_pressed_fg: str = theme_light_blue["horizontal_scroll_pressed_fg"],
    scrollbar_theme_inheritance: str = "default",
    scrollbar_show_arrows: bool = True,
    # changing the arrowsize (width) of the scrollbars
    # is not working with 'default' theme
    # use 'clam' theme instead if you want to change the width
    vertical_scroll_arrowsize: str | int = "",
    horizontal_scroll_arrowsize: str | int = "",
) -> None

You can change most of these settings after initialization using the set_options() function. - scrollbar_theme_inheritance and scrollbar_show_arrows will only work on Sheet() initialization, not with set_options()


Sheet Appearance

Sheet Colors

To change the colors of individual cells, rows or columns use the functions listed under highlighting cells.

For the colors of specific parts of the table such as gridlines and backgrounds use the function set_options(), keyword arguments specific to sheet colors are listed below. All the other set_options() arguments can be found here.

Use a tkinter color or a hex string e.g.

my_sheet_widget.set_options(table_bg="black")
my_sheet_widget.set_options(table_bg="#000000")
my_sheet_widget.set_options(horizontal_scroll_pressed_bg="red")

Set options

set_options(
top_left_bg
top_left_fg
top_left_fg_highlight

table_bg
table_grid_fg
table_fg
table_selected_box_cells_fg
table_selected_box_rows_fg
table_selected_box_columns_fg
table_selected_cells_border_fg
table_selected_cells_bg
table_selected_cells_fg
table_selected_rows_border_fg
table_selected_rows_bg
table_selected_rows_fg
table_selected_columns_border_fg
table_selected_columns_bg
table_selected_columns_fg

header_bg
header_border_fg
header_grid_fg
header_fg
header_selected_cells_bg
header_selected_cells_fg
header_selected_columns_bg
header_selected_columns_fg

index_bg
index_border_fg
index_grid_fg
index_fg
index_selected_cells_bg
index_selected_cells_fg
index_selected_rows_bg
index_selected_rows_fg

resizing_line_fg
drag_and_drop_bg
outline_thickness
outline_color
frame_bg
popup_menu_font
popup_menu_fg
popup_menu_bg
popup_menu_highlight_bg
popup_menu_highlight_fg

# scroll bars
vertical_scroll_background
horizontal_scroll_background
vertical_scroll_troughcolor
horizontal_scroll_troughcolor
vertical_scroll_lightcolor
horizontal_scroll_lightcolor
vertical_scroll_darkcolor
horizontal_scroll_darkcolor
vertical_scroll_bordercolor
horizontal_scroll_bordercolor
vertical_scroll_active_bg
horizontal_scroll_active_bg
vertical_scroll_not_active_bg
horizontal_scroll_not_active_bg
vertical_scroll_pressed_bg
horizontal_scroll_pressed_bg
vertical_scroll_active_fg
horizontal_scroll_active_fg
vertical_scroll_not_active_fg
horizontal_scroll_not_active_fg
vertical_scroll_pressed_fg
horizontal_scroll_pressed_fg
)

Otherwise you can change the theme using the below function.

change_theme(theme: str = "light blue", redraw: bool = True) -> Sheet

Scrollbar Appearance

Scrollbar colors:

The above function and keyword arguments can be used to change the colors of the scroll bars.

Scrollbar relief, size, arrows, etc.

Some scroll bar style options can only be changed on Sheet() initialization, others can be changed whenever using set_options():


Alternate Row Colors

For basic alternate row colors in the main table either:

Examples:

set_options(alternate_color="#E2EAF4")
my_sheet = Sheet(parent, alternate_color="gray80")

Note that any cell, row or column highlights will display over alternate row colors.


Header and Index

Set the header

set_header_data(value: object, c: int | None | Iterator = None, redraw: bool = True) -> Sheet
headers(
    newheaders: object = None,
    index: None | int = None,
    reset_col_positions: bool = False,
    show_headers_if_not_sheet: bool = True,
    redraw: bool = True,
) -> object

Set the index

set_index_data(value: object, r: int | None | Iterator = None, redraw: bool = True) -> Sheet
row_index(
    newindex: object = None,
    index: None | int = None,
    reset_row_positions: bool = False,
    show_index_if_not_sheet: bool = True,
    redraw: bool = True,
) -> object

Text Wrap and Overflow

Control text wrapping

You can set table, header and index text wrapping either at Sheet() initialization or using set_options().

Make use of the following parameters:

With one of the following arguments:

Examples:

# for word wrap at initialization
my_sheet = Sheet(parent, table_wrap="w")

# for character wrap using set_options()
my_sheet.set_options(table_wrap="c")

Control table text overflow

This setting only works for cells that are not center (north) aligned. Cell text can be set to overflow adjacent empty cells in the table like so:

Examples:

# for word wrap at initialization
my_sheet = Sheet(parent, allow_cell_overflow=True)

# for character wrap using set_options()
my_sheet.set_options(allow_cell_overflow=True)

Table functionality and bindings

Enable table functionality and bindings

enable_bindings(*bindings)

*has to be specifically enabled - See Notes.

Notes:

Example:


Disable table functionality and bindings

disable_bindings(*bindings)

Notes:


Bind specific table functionality

This function allows you to bind very specific table functionality to your own functions:

extra_bindings(
    bindings: str | list | tuple,
    func: Callable | None = None,
) -> Sheet

There are several ways to use this function:

bindings (str) options:

Undo/Redo:

Editing:

Moving:

Deleting:

Adding:

Resizing rows/columns:

Selection:

Event collections:

Further Notes:

Event Data

Using extra_bindings() the function you bind needs to have at least one argument which will receive a dict. The values of which can be accessed by dot notation e.g. event.eventname or event.cells.table:

for (row, column), old_value in event.cells.table.items():
    print (f"R{row}", f"C{column}", "Old Value:", old_value)

It has the following layout and keys:

{
    "eventname": "",
    "sheetname": "",
    "cells": {
        "table": {},
        "header": {},
        "index": {},
    },
    "moved": {
        "rows": {},
        "columns": {},
    },
    "added": {
        "rows": {},
        "columns": {},
    },
    "deleted": {
        "rows": {},
        "columns": {},
        "header": {},
        "index": {},
        "column_widths": {},
        "row_heights": {},
        "options": {},
        "displayed_columns": None,
        "displayed_rows": None,
    },
    "named_spans": {},
    "selection_boxes": {},
    "selected": tuple(),
    "being_selected": tuple(),
    "data": [],
    "key": "",
    "value": None,
    "loc": tuple(),
    "row": None,
    "column": None,
    "resized": {
        "rows": {},
        "columns": {},
    },
    "widget": None,
}

Keys:


Validate user cell edits

With these functions you can validate or modify most user sheet edits, includes cut, paste, delete (including column/row clear), dropdown boxes and cell edits.

Edit validation

This function will be called for every cell edit in an action.

edit_validation(func: Callable | None = None) -> Sheet

Parameters:

Notes:

Bulk edit validation

This function will be called at the end of an action and delay any edits until after validation.

bulk_table_edit_validation(func: Callable | None = None) -> Sheet

Parameters:

Notes:

Example:

from tksheet import Sheet
import tkinter as tk

from typing import Any


class demo(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)
        self.frame = tk.Frame(self)
        self.frame.grid_columnconfigure(0, weight=1)
        self.frame.grid_rowconfigure(0, weight=1)
        self.sheet = Sheet(
            self.frame,
            data=[[f"Row {r}, Column {c}" for c in range(3)] for r in range(3)],
        )
        self.sheet.enable_bindings()
        self.sheet.bulk_table_edit_validation(self.validate)
        self.frame.grid(row=0, column=0, sticky="nswe")
        self.sheet.grid(row=0, column=0, sticky="nswe")

    def validate(self, event: dict) -> Any:
        """
        Whatever keys and values are left in event["data"]
        when the function returns are the edits that will be made

        An example below shows preventing edits if the proposed edit
        contains a space

        But you can also modify the values, or add more key, value
        pairs to event["data"]
        """
        not_valid = set()
        for (r, c), value in event.data.items():
            if " " in value:
                not_valid.add((r, c))
        event.data = {k: v for k, v in event.data.items() if k not in not_valid}


app = demo()
app.mainloop()

Add commands to the in-built right click popup menu

popup_menu_add_command(
    label: str,
    func: Callable,
    table_menu: bool = True,
    index_menu: bool = True,
    header_menu: bool = True,
    empty_space_menu: bool = True,
) -> Sheet

Remove commands added using popup_menu_add_command from the in-built right click popup menu

popup_menu_del_command(label: str | None = None) -> Sheet

Enable or disable mousewheel, left click etc

basic_bindings(enable: bool = False) -> Sheet

These functions are links to the Sheets own functionality. Functions such as cut() rely on whatever is currently selected on the Sheet.

cut(event: object = None) -> Sheet
copy(event: object = None) -> Sheet
paste(event: object = None) -> Sheet
delete(event: object = None) -> Sheet
undo(event: object = None) -> Sheet
redo(event: object = None) -> Sheet
zoom_in() -> Sheet
zoom_out() -> Sheet

Get the last event data dict

@property
def event() -> EventDataDict

Set focus to the Sheet

focus_set(
    canvas: Literal[
        "table",
        "header",
        "row_index",
        "index",
        "topleft",
        "top_left",
    ] = "table",
) -> Sheet

tkinter and tksheet Events

Sheet bind

Binding Usable with event_generate()
"<<SheetModified>>" -
"<<SheetRedrawn>>" -
"<<SheetSelect>>" -
"<<Copy>>" X
"<<Cut>>" X
"<<Paste>>" X
"<<Delete>>" X
"<<Undo>>" X
"<<Redo>>" X
"<<SelectAll>>" X
bind(
    event: str,
    func: Callable,
    add: str | None = None,
)

Parameters:

Example:

# self.sheet_was_modified is your function
self.sheet.bind("<<SheetModified>>", self.sheet_was_modified)

Example for event_generate():

self.sheet.event_generate("<<Copy>>")

Sheet unbind

With this function you can unbind things you have bound using the bind() function.

unbind(binding: str) -> Sheet

Sheet Languages and Bindings

In this section are instructions to change some of tksheets in-built language and bindings:

Please note that due to the limitations of the Tkinter Canvas tksheet doesn’t support right-to-left (RTL) languages.

Changing right click menu labels

You can change the labels for tksheets in-built right click popup menu by using the set_options() function with any of the following keyword arguments:

edit_header_label
edit_header_accelerator
edit_index_label
edit_index_accelerator
edit_cell_label
edit_cell_accelerator
cut_label
cut_accelerator
cut_contents_label
cut_contents_accelerator
copy_label
copy_accelerator
copy_contents_label
copy_contents_accelerator
paste_label
paste_accelerator
delete_label
delete_accelerator
clear_contents_label
clear_contents_accelerator
delete_columns_label
delete_columns_accelerator
insert_columns_left_label
insert_columns_left_accelerator
insert_column_label
insert_column_accelerator
insert_columns_right_label
insert_columns_right_accelerator
delete_rows_label
delete_rows_accelerator
insert_rows_above_label
insert_rows_above_accelerator
insert_rows_below_label
insert_rows_below_accelerator
insert_row_label
insert_row_accelerator
select_all_label
select_all_accelerator
undo_label
undo_accelerator

sort_cells_label
sort_cells_x_label
sort_row_label
sort_column_label
sort_rows_label
sort_columns_label
sort_cells_reverse_label
sort_cells_x_reverse_label
sort_row_reverse_label
sort_column_reverse_label
sort_rows_reverse_label
sort_columns_reverse_label
sort_cells_accelerator
sort_cells_x_accelerator
sort_row_accelerator
sort_column_accelerator
sort_rows_accelerator
sort_columns_accelerator
sort_cells_reverse_accelerator
sort_cells_x_reverse_accelerator
sort_row_reverse_accelerator
sort_column_reverse_accelerator
sort_rows_reverse_accelerator
sort_columns_reverse_accelerator

Example:

# changing the copy label to the spanish for Copy
sheet.set_options(copy_label="Copiar")

Changing key bindings

You can change the bindings for tksheets in-built functionality such as cut, copy, paste by using the set_options() function with any the following keyword arguments:

copy_bindings
cut_bindings
paste_bindings
undo_bindings
redo_bindings
delete_bindings
select_all_bindings
select_columns_bindings
select_rows_bindings
row_start_bindings
table_start_bindings
tab_bindings
up_bindings
right_bindings
down_bindings
left_bindings
shift_up_bindings
shift_right_bindings
shift_down_bindings
shift_left_bindings
prior_bindings
next_bindings
find_bindings
find_next_bindings
find_previous_bindings
escape_bindings
toggle_replace_bindings

The argument must be a list of tkinter binding strs. In the below example the binding for copy is changed to "<Control-e>" and "<Control-E>".

# changing the binding for copy
sheet.set_options(copy_bindings=["<Control-e>", "<Control-E>"])

The default values for these bindings can be found in the tksheet file sheet_options.py.

Key bindings for other languages

There is limited support in tkinter for keybindings in languages other than english, for example tkinters .bind() function doesn't cooperate with cyrillic characters.

There are ways around this however, see below for a limited example of how this might be achieved:

from __future__ import annotations

import tkinter as tk

from tksheet import Sheet


class demo(tk.Tk):
    def __init__(self) -> None:
        tk.Tk.__init__(self)
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)
        self.sheet = Sheet(
            parent=self,
            data=[[f"{r} {c}" for c in range(5)] for r in range(5)],
        )
        self.sheet.enable_bindings()
        self.sheet.grid(row=0, column=0, sticky="nswe")
        self.bind_all("<Key>", self.any_key)

    def any_key(self, event: tk.Event) -> None:
        """
        Establish that the Control key is held down
        """
        ctrl = (event.state & 4 > 0)
        if not ctrl:
            return
        """
        From here you can use event.keycode and event.keysym to determine
        which key has been pressed along with Control
        """
        print(event.keycode)
        print(event.keysym)
        """
        If the keys are the ones you want to have bound to Sheet functionality
        You can then call the Sheets functionality using event_generate()
        For example:
        """
        # if the key is correct then:
        self.sheet.event_generate("<<Copy>>")


app = demo()
app.mainloop()

Span Objects

In tksheet versions > 7 there are functions which utilise an object named Span. These objects are a subclass of dict but with various additions and dot notation attribute access.

Spans basically represent an contiguous area of the sheet. They can be one of three kinds:

They can be used with some of the sheets functions such as data getting/setting and creation of things on the sheet such as dropdown boxes.

Spans store:

Whether cells, rows or columns are affected will depend on the spans kind.

Creating a span

You can create a span by:

or

Both methods return the created span object.

span(
    *key: CreateSpanTypes,
    type_: str = "",
    name: str = "",
    table: bool = True,
    index: bool = False,
    header: bool = False,
    tdisp: bool = False,
    idisp: bool = True,
    hdisp: bool = True,
    transposed: bool = False,
    ndim: int = 0,
    convert: object = None,
    undo: bool = True,
    emit_event: bool = False,
    widget: object = None,
    expand: None | str = None,
    formatter_options: dict | None = None,
    **kwargs,
) -> Span
"""
Create a span / get an existing span by name
Returns the created span
"""

Parameters:

Notes:

Span creation syntax

When creating a span using the below methods:

For example python index 0 as in [0] is the first whereas excel index 1 as in "A1" is the first.

If you need to convert python indexes into column letters you can use the function num2alpha importable from tksheet:

from tksheet import (
    Sheet,
    num2alpha as n2a,
)

# column index five as a letter
n2a(5)

Span creation examples using square brackets

"""
EXAMPLES USING SQUARE BRACKETS
"""

span = sheet[0] # first row
span = sheet["1"] # first row

span = sheet[0:2] # first two rows
span = sheet["1:2"] # first two rows

span = sheet[:] # entire sheet
span = sheet[":"] # entire sheet

span = sheet[:2] # first two rows
span = sheet[":2"] # first two rows

""" THESE TWO HAVE DIFFERENT OUTCOMES """
span = sheet[2:] # all rows after and not inlcuding python index 1
span = sheet["2:"] # all rows after and not including python index 0

span = sheet["A"] # first column
span = sheet["A:C"] # first three columns

""" SOME CELL AREA EXAMPLES """
span = sheet[0, 0] # cell A1
span = sheet[(0, 0)] # cell A1
span = sheet["A1:C1"] # cells A1, B1, C1
span = sheet[0, 0, 1, 3] # cells A1, B1, C1
span = sheet[(0, 0, 1, 3)] # cells A1, B1, C1
span = sheet[(0, 0), (1, 3)] # cells A1, B1, C1
span = sheet[((0, 0), (1, 3))] # cells A1, B1, C1

span = sheet["A1:2"]
span = sheet[0, 0, 2, None]
"""
["A1:2"]
All the cells starting from (0, 0)
expanding down to include row 1
but not including cells beyond row
1 and expanding out to include all
columns

    A   B   C   D
1   x   x   x   x
2   x   x   x   x
3
4
...
"""

span = sheet["A1:B"]
span = sheet[0, 0, None, 2]
"""
["A1:B"]
All the cells starting from (0, 0)
expanding out to include column 1
but not including cells beyond column
1 and expanding down to include all
rows

    A   B   C   D
1   x   x
2   x   x
3   x   x
4   x   x
...
"""

""" GETTING AN EXISTING NAMED SPAN """
# you can retrieve an existing named span quickly by surrounding its name in <> e.g.
named_span_retrieval = sheet["<the name of the span goes here>"]

Span creation examples using sheet.span()

"""
EXAMPLES USING span()
"""

"""
USING NO ARGUMENTS
"""
sheet.span() # entire sheet, in this case not including header or index

"""
USING ONE ARGUMENT

str or int or slice()
"""

# with one argument you can use the same string syntax used for square bracket span creation
sheet.span("A1")
sheet.span(0) # row at python index 0, all columns
sheet.span(slice(0, 2)) # rows at python indexes 0 and 1, all columns
sheet.span(":") # entire sheet

"""
USING TWO ARGUMENTS
int | None, int | None

or

(int | None, int | None), (int | None, int | None)
"""
sheet.span(0, 0) # row 0, column 0 - the first cell
sheet.span(0, None) # row 0, all columns
sheet.span(None, 0) # column 0, all rows

sheet.span((0, 0), (1, 1)) # row 0, column 0 - the first cell
sheet.span((0, 0), (None, 2)) # rows 0 - end, columns 0 and 1

"""
USING FOUR ARGUMENTS
int | None, int | None, int | None, int | None
"""

sheet.span(0, 0, 1, 1) # row 0, column 0 - the first cell
sheet.span(0, 0, None, 2) # rows 0 - end, columns 0 and 1

Span properties

Spans have a few @property functions:

Get a spans kind

span.kind
span = sheet.span("A1:C4")
print (span.kind)
# prints "cell"

span = sheet.span(":")
print (span.kind)
# prints "cell"

span = sheet.span("1:3")
print (span.kind)
# prints "row"

span = sheet.span("A:C")
print (span.kind)
# prints "column"

# after importing num2alpha from tksheet
print (sheet[num2alpha(0)].kind)
# prints "column"

Get span rows and columns

span.rows
span.columns

Returns a SpanRange object. The below examples are for span.rows but you can use span.columns for the spans columns exactly the same way.

# use as an iterator
span = sheet.span("A1:C4")
for row in span.rows:
    pass
# use as a reversed iterator
for row in reversed(span.rows):
    pass

# check row membership
span = sheet.span("A1:C4")
print (2 in span.rows)
# prints True

# check span.rows equality, also can do not equal
span = self.sheet["A1:C4"]
span2 = self.sheet["1:4"]
print (span.rows == span2.rows)
# prints True

# check len
span = self.sheet["A1:C4"]
print (len(span.rows))
# prints 4

Span methods

Spans have the following methods, all of which return the span object itself so you can chain the functions e.g. span.options(undo=True).clear().bg = "indianred1"

Modify a spans attributes

span.options(
    type_: str | None = None,
    name: str | None = None,
    table: bool | None = None,
    index: bool | None = None,
    header: bool | None = None,
    tdisp: bool | None = None,
    idisp: bool | None  = None,
    hdisp: bool | None  = None,
    transposed: bool | None = None,
    ndim: int | None = None,
    convert: Callable | None = None,
    undo: bool | None = None,
    emit_event: bool | None = None,
    widget: object = None,
    expand: str | None = None,
    formatter_options: dict | None = None,
    **kwargs,
) -> Span

Note that if None is used for any of the following parameters then that Spans attribute will be unchanged:

# entire sheet
span = sheet["A1"].options(expand="both")

# column A
span = sheet["A1"].options(expand="down")

# row 0
span = sheet["A1"].options(
    expand="right",
    ndim=1, # to return a single list when getting data
)

All of a spans modifiable attributes are listed here:

If necessary you can also modify these attributes the same way you would an objects. e.g.

# span now takes in all columns, including A
span = self.sheet("A")
span.upto_c = None

# span now adds to sheets undo stack when using data modifying functions that use spans
span = self.sheet("A")
span.undo = True

Using a span to format data

Formats table data, see the help on formatting for more information. Note that using this function also creates a format rule for the affected table cells.

span.format(
    formatter_options: dict = {},
    formatter_class: object = None,
    redraw: bool = True,
    **kwargs,
) -> Span

Example:

# using square brackets
sheet[:].format(int_formatter())

# or instead using sheet.span()
sheet.span(":").format(int_formatter())

These examples show the formatting of the entire sheet (not including header and index) as int and creates a format rule for all currently existing cells. Named spans are required to create a rule for all future existing cells as well, for example those created by the end user inserting rows or columns.

Using a span to delete data format rules

Delete any currently existing format rules for parts of the table that are covered by the span. Should not be used where there are data formatting rules created by named spans, see Named spans for more information.

span.del_format() -> Span

Example:

span1 = sheet[2:4]
span1.format(float_formatter())
span1.del_format()

Using a span to create highlights

span.highlight(
    bg: bool | None | str = False,
    fg: bool | None | str = False,
    end: bool | None = None,
    overwrite: bool = False,
    redraw: bool = True,
) -> Span

There are two ways to create highlights using a span:

Method 1 example using .highlight():

# highlights column A background red, text color black
sheet["A"].highlight(bg="red", fg="black")

# the same but after having saved a span
my_span = sheet["A"]
my_span.highlight(bg="red", fg="black")

Method 2 example using .bg/.fg:

# highlights column A background red, text color black
sheet["A"].bg = "red"
sheet["A"].fg = "black"

# the same but after having saved a span
my_span = sheet["A"]
my_span.bg = "red"
my_span.fg = "black"

Using a span to delete highlights

Delete any currently existing highlights for parts of the sheet that are covered by the span. Should not be used where there are highlights created by named spans, see Named spans for more information.

span.dehighlight() -> Span

Example:

span1 = sheet[2:4].highlight(bg="red", fg="black")
span1.dehighlight()

Using a span to create dropdown boxes

Creates dropdown boxes for parts of the sheet that are covered by the span. For more information see here.

span.dropdown(
    values: list = [],
    set_value: object = None,
    state: Literal["normal", "readonly", "disabled"] = "normal",
    redraw: bool = True,
    selection_function: Callable | None = None,
    modified_function: Callable | None = None,
    search_function: Callable = dropdown_search_function,
    validate_input: bool = True,
    text: None | str = None,
) -> Span

Example:

sheet["D"].dropdown(
    values=["on", "off"],
    set_value="off",
)

Using a span to delete dropdown boxes

Delete dropdown boxes for parts of the sheet that are covered by the span. Should not be used where there are dropdown box rules created by named spans, see Named spans for more information.

span.del_dropdown() -> Span

Example:

dropdown_span = sheet["D"].dropdown(values=["on", "off"],
                                    set_value="off")
dropdown_span.del_dropdown()

Using a span to create check boxes

Create check boxes for parts of the sheet that are covered by the span.

span.checkbox(
    edit_data: bool = True,
    checked: bool | None = None,
    state: Literal["normal", "disabled"] = "normal",
    redraw: bool = True,
    check_function: Callable | None = None,
    text: str = "",
) -> Span

Parameters:

Notes:

Example:

sheet["D"].checkbox(
    checked=True,
    text="Switch",
)

Using a span to delete check boxes

Delete check boxes for parts of the sheet that are covered by the span. Should not be used where there are check box rules created by named spans, see Named spans for more information.

span.del_checkbox() -> Span

Example:

checkbox_span = sheet["D"].checkbox(checked=True,
                                    text="Switch")
checkbox_span.del_checkbox()

Using a span to set cells to read only

Create a readonly rule for parts of the table that are covered by the span.

span.readonly(readonly: bool = True) -> Span

Using a span to create text alignment rules

Create a text alignment rule for parts of the sheet that are covered by the span.

span.align(
    align: str | None,
    redraw: bool = True,
) -> Span

Example:

sheet["D"].align("right")

There are two ways to create alignment rules using a span:

Method 1 example using .align():

# column D right text alignment
sheet["D"].align("right")

# the same but after having saved a span
my_span = sheet["D"]
my_span.align("right")

Method 2 example using .align =:

# column D right text alignment
sheet["D"].align = "right"

# the same but after having saved a span
my_span = sheet["D"]
my_span.align = "right"

Using a span to delete text alignment rules

Delete text alignment rules for parts of the sheet that are covered by the span. Should not be used where there are alignment rules created by named spans, see Named spans for more information.

span.del_align() -> Span

Example:

align_span = sheet["D"].align("right")
align_span.del_align()

Using a span to clear cells

Clear cell data from all cells that are covered by the span.

span.clear(
    undo: bool | None = None,
    emit_event: bool | None = None,
    redraw: bool = True,
) -> Span

Parameters:

Example:

# clears column D
sheet["D"].clear()

Using a span to tag cells

Tag cells, rows or columns depending on the spans kind, more information on tags here.

tag(*tags) -> Span

Notes:

Example:

# tags rows 2, 3, 4 with "hello world"
sheet[2:5].tag("hello world")

Using a span to untag cells

Remove all tags from cells, rows or columns depending on the spans kind, more information on tags here.

untag() -> Span

Notes:

Example:

# tags rows 2, 3, 4 with "hello" and "bye"
sheet[2:5].tag("hello", "bye")

# removes both "hello" and "bye" tags from rows 2, 3, 4
sheet[2:5].untag()

Set the spans orientation

The attribute span.transposed (bool) is used by data getting and setting functions that utilize spans. When True: - Returned sublists from data getting functions will represent columns rather than rows. - Data setting functions will assume that a single sequence is a column rather than row and that a list of lists is a list of columns rather than a list of rows.

You can toggle the transpotition of the span by using:

span.transpose() -> Span

If the attribute is already True this makes it False and vice versa.

span = sheet["A:D"].transpose()
# this span is now transposed
print (span.transposed)
# prints True

span.transpose()
# this span is no longer transposed
print (span.transposed)
# prints False

Expand the spans area

Expand the spans area either all the way to the right (x axis) or all the way down (y axis) or both.

span.expand(direction: str = "both") -> Span

Named Spans

Named spans are like spans but with a type, some keyword arguments saved in span.kwargs and then created by using a Sheet() function. Like spans, named spans are also contiguous areas of the sheet.

Named spans can be used to:

Note that generally when a user moves rows/columns around the dimensions of the named span essentially move with either end of the span:

For the end user, when a span is just a single row/column (and is not expanded/unlimited) it cannot be expanded but it can be deleted if the row/column is deleted.

Creating a named span

For a span to become a named span it needs:

After a span has the above items the following function has to be used to make it a named span and create the options on the sheet:

named_span(span: Span)
"""
Adds a named span to the sheet
Returns the span
"""

Examples of creating named spans:

# Will highlight rows 3 up to and including 5
span1 = self.sheet.span(
    "3:5",
    type_="highlight",
    bg="green",
    fg="black",
)
self.sheet.named_span(span1)

#  Will always keep the entire sheet formatted as `int` no matter how many rows/columns are inserted
span2 = self.sheet.span(
    ":",
    # you don't have to provide a `type_` when using the `formatter_kwargs` argument
    formatter_options=int_formatter(),
)
self.sheet.named_span(span2)

Deleting a named span

del_named_span(name: str)

Example, creating and deleting a span:

# span covers the entire sheet
self.sheet.named_span(
    self.sheet.span(
        name="my highlight span",
        type_="highlight",
        bg="dark green",
        fg="#FFFFFF",
    )
)
self.sheet.del_named_span("my highlight span")

# ValueError is raised if name does not exist
self.sheet.del_named_span("this name doesnt exist")
# ValueError: Span 'this name doesnt exist' does not exist.

Other named span functions

Sets the Sheets internal dict of named spans:

set_named_spans(named_spans: None | dict = None) -> Sheet

Get an existing named span:

get_named_span(name: str) -> dict

Get all existing named spans:

get_named_spans() -> dict

Getting Sheet Data

Using a span to get sheet data

A Span object (more information here) is returned when using square brackets on a Sheet like so:

span = self.sheet["A1"]

You can also use sheet.span():

span = self.sheet.span("A1")

The above spans represent the cell A1 - row 0, column 0.

A reserved span attribute named data can then be used to retrieve the data for cell A1, example below:

span = self.sheet["A1"]
cell_a1_data = span.data

The data that is retrieved entirely depends on the area the span represents. You can also use span.value to the same effect.

There are certain other span attributes which have an impact on the data returned, explained below:

Some more complex examples of data retrieval:

"single cell"
cell_a1_data = self.sheet["A1"].data

"entire sheet including headers and index"
entire_sheet_data = self.sheet["A1"].expand().options(header=True, index=True).data

"header data, no table or index data"
# a list of displayed header cells
header_data = self.sheet["A:C"].options(table=False, header=True).data

# a header value
header_data = self.sheet["A"].options(table=False, hdisp=False, header=True).data

"index data, no table or header data"
# a list of displayed index cells
index_data = self.sheet[:3].options(table=False, index=True).data

# or using sheet.span() a list of displayed index cells
index_data = self.sheet.span(slice(None, 3), table=False, index=True).data

# a row index value
index_data = self.sheet[3].options(table=False, idisp=False, index=True).data

"sheet data as columns instead of rows, with actual header data"
sheet_data = self.sheet[:].transpose().options(hdisp=False, header=True).data

# or instead using sheet.span() with only kwargs
sheet_data = self.sheet.span(transposed=True, hdisp=False, header=True).data

There is also a Sheet() function for data retrieval (it is used internally by the above data getting methods):

sheet.get_data(
    *key: CreateSpanTypes,
) -> object

Examples:

data = self.sheet.get_data("A1")
data = self.sheet.get_data(0, 0, 3, 3)
data = self.sheet.get_data(
    self.sheet.span(":D", transposed=True)
)

Get a single cells data

This is a higher performance method to get a single cells data which may be useful for example when performing a very large number of single cell data retrievals in a loop.

get_cell_data(r: int, c: int, get_displayed: bool = False) -> object

Generate sheet rows one at a time

This function is useful if you need a lot of sheet data, and produces one row at a time (may save memory use in certain scenarios). It does not use spans.

yield_sheet_rows(
    get_displayed: bool = False,
    get_header: bool = False,
    get_index: bool = False,
    get_index_displayed: bool = True,
    get_header_displayed: bool = True,
    only_rows: int | Iterator[int] | None = None,
    only_columns: int | Iterator[int] | None = None,
) -> Iterator[list[object]]

Parameters:


Get table data, readonly

@property
data()

The name of the actual internal sheet data variable

.MT.data

Sheet methods

Sheet objects also have some functions similar to lists. Note that these functions do not include the header or index.

Iterate over table rows:

for row in self.sheet:
    print (row)

# and in reverse
for row in reversed(self.sheet):
    print (row)

Check if the table has a particular value (membership):

# returns True or False
search_value = "the cell value I'm looking for"
print (search_value in self.sheet)

Get the number of rows in the sheet

get_total_rows(include_index: bool = False) -> int

Get the number of columns in the sheet

get_total_columns(include_header: bool = False) -> int

Get a value for a particular cell if that cell was empty

get_value_for_empty_cell(
    r: int,
    c: int,
    r_ops: bool = True,
    c_ops: bool = True,
) -> object

Setting Sheet Data

Fundamentally, there are two ways to set table data:

Overwriting the table

set_sheet_data(
    data: list | tuple | None = None,
    reset_col_positions: bool = True,
    reset_row_positions: bool = True,
    redraw: bool = True,
    verify: bool = False,
    reset_highlights: bool = False,
    keep_formatting: bool = True,
    delete_options: bool = False,
) -> object

Parameters:

Notes:


@data.setter
data(value: object)

Notes:


Reset all or specific sheet elements and attributes

reset(
    table: bool = True,
    header: bool = True,
    index: bool = True,
    row_heights: bool = True,
    column_widths: bool = True,
    cell_options: bool = True,
    undo_stack: bool = True,
    selections: bool = True,
    sheet_options: bool = False,
    redraw: bool = True,
) -> Sheet

Parameters:

Notes:


Modifying sheet data

A Span object (more information here) is returned when using square brackets on a Sheet like so:

span = self.sheet["A1"]

You can also use sheet.span():

span = self.sheet.span("A1")

The above span example represents the cell A1 - row 0, column 0. A reserved span attribute named data (you can also use .value) can then be used to modify sheet data starting from cell A1. example below:

span = self.sheet["A1"]
span.data = "new value for cell A1"

# or even shorter:
self.sheet["A1"].data = "new value for cell A1"

# or with sheet.span()
self.sheet.span("A1").data = "new value for cell A1"

If you provide a list or tuple it will set more than one cell, starting from the spans start cell. In the example below three cells are set in the first row, starting from cell B1:

self.sheet["B1"].data = ["row 0, column 1 new value (B1)",
                         "row 0, column 2 new value (C1)",
                         "row 0, column 3 new value (D1)"]

You can set data in column orientation with a transposed span:

self.sheet["B1"].transpose().data = ["row 0, column 1 new value (B1)",
                                     "row 1, column 1 new value (B2)",
                                     "row 2, column 1 new value (B3)"]

When setting data only a spans start cell is taken into account, the end cell is ignored. The example below demonstrates this, the spans end - "B1" is ignored and 4 cells get new values:

self.sheet["A1:B1"].data = ["A1 new val", "B1 new val", "C1 new val", "D1 new val"]

These are the span attributes which have an impact on the data set: - table (bool) when True will make all functions used with the span target the main table as well as the header/index if those are True. - index (bool) when True will make all functions used with the span target the index as well as the table/header if those are True. - header (bool) when True will make all functions used with the span target the header as well as the table/index if those are True. - transposed (bool) is used by data getting and setting functions that utilize spans. When True: - Returned sublists from data getting functions will represent columns rather than rows. - Data setting functions will assume that a single sequence is a column rather than row and that a list of lists is a list of columns rather than a list of rows. - widget (object) is the reference to the original sheet which created the span (this is the widget that data is set to). This can be changed to a different sheet if required e.g. my_span.widget = new_sheet.

Some more complex examples of setting data:

"""
SETTING ROW DATA
"""
# first row gets some new values and the index gets a new value also
self.sheet[0].options(index=True).data = ["index val", "row 0 col 0", "row 0 col 1", "row 0 col 2"]

# or instead using sheet.span() first row gets some new values and the index gets a new value also
self.sheet.span(0, index=True).data = ["index val", "row 0 col 0", "row 0 col 1", "row 0 col 2"]

# first two rows get some new values, index included
self.sheet[0].options(index=True).data = [["index 0", "row 0 col 0", "row 0 col 1", "row 0 col 2"],
                                          ["index 1", "row 1 col 0", "row 1 col 1", "row 1 col 2"]]

"""
SETTING COLUMN DATA
"""
# first column gets some new values and the header gets a new value also
self.sheet["A"].options(transposed=True, header=True).data = ["header val", "row 0 col 0", "row 1 col 0", "row 2 col 0"]

# or instead using sheet.span() first column gets some new values and the header gets a new value also
self.sheet.span("A", transposed=True, header=True).data = ["header val", "row 0 col 0", "row 1 col 0", "row 2 col 0"]

# first two columns get some new values, header included
self.sheet["A"].options(transposed=True, header=True).data = [["header 0", "row 0 col 0", "row 1 col 0", "row 2 col 0"],
                                                              ["header 1", "row 0 col 1", "row 1 col 1", "row 2 col 1"]]

"""
SETTING CELL AREA DATA
"""
# cells B2, C2, B3, C3 get new values
self.sheet["B2"].data = [["B2 new val", "C2 new val"],
                         ["B3 new val", "C3 new val"]]

# or instead using sheet.span() cells B2, C2, B3, C3 get new values
self.sheet.span("B2").data = [["B2 new val", "C2 new val"],
                              ["B3 new val", "C3 new val"]]

"""
SETTING CELL AREA DATA INCLUDING HEADER AND INDEX
"""
self.sheet_span = self.sheet.span(
    header=True,
    index=True,
    hdisp=False,
    idisp=False,
)

# set data for the span which was created above
self.sheet_span.data = [["",  "A",  "B",  "C"]
                        ["1", "A1", "B1", "C1"],
                        ["2", "A2", "B2", "C2"]]

Sheet set data function

You can also use the Sheet function set_data().

set_data(
    *key: CreateSpanTypes,
    data: object = None,
    undo: bool | None = None,
    emit_event: bool | None = None,
    redraw: bool = True,
    event_data: EventDataDict | None = None,
) -> EventDataDict

Parameters:

Example:

self.sheet.set_data(
    "A1",
    [["",  "A",  "B",  "C"]
     ["1", "A1", "B1", "C1"],
     ["2", "A2", "B2", "C2"]],
)

You can clear cells/rows/columns using a spans clear() function or the Sheets clear() function. Below is the Sheets clear function:

clear(
    *key: CreateSpanTypes,
    undo: bool | None = None,
    emit_event: bool | None = None,
    redraw: bool = True,
) -> EventDataDict

Insert a row into the sheet

insert_row(
    row: list[object] | tuple[object] | None = None,
    idx: str | int | None = None,
    height: int | None = None,
    row_index: bool = False,
    fill: bool = True,
    undo: bool = True,
    emit_event: bool = False,
    redraw: bool = True,
) -> EventDataDict

Parameters:


Insert a column into the sheet

insert_column(
    column: list[object] | tuple[object] | None = None,
    idx: str | int | None = None,
    width: int | None = None,
    header: bool = False,
    fill: bool = True,
    undo: bool = True,
    emit_event: bool = False,
    redraw: bool = True,
) -> EventDataDict

Parameters:


Insert multiple columns into the sheet

insert_columns(
    columns: list[tuple[object] | list[object]] | tuple[tuple[object] | list[object]] | int = 1,
    idx: str | int | None = None,
    widths: list[int] | tuple[int] | None = None,
    headers: bool = False,
    fill: bool = True,
    undo: bool = True,
    emit_event: bool = False,
    create_selections: bool = True,
    add_row_heights: bool = True,
    push_ops: bool = True,
    redraw: bool = True,
) -> EventDataDict

Parameters:


Insert multiple rows into the sheet

insert_rows(
    rows: list[tuple[object] | list[object]] | tuple[tuple[object] | list[object]] | int = 1,
    idx: str | int | None = None,
    heights: list[int] | tuple[int] | None = None,
    row_index: bool = False,
    fill: bool = True,
    undo: bool = True,
    emit_event: bool = False,
    create_selections: bool = True,
    add_column_widths: bool = True,
    push_ops: bool = True,
    tree: bool = True,
    redraw: bool = True,
) -> EventDataDict

Parameters:


Delete a row from the sheet

del_row(
    idx: int = 0,
    data_indexes: bool = True,
    undo: bool = True,
    emit_event: bool = False,
    redraw: bool = True,
) -> EventDataDict

Parameters:


Delete multiple rows from the sheet

del_rows(
    rows: int | Iterator[int],
    data_indexes: bool = True,
    undo: bool = True,
    emit_event: bool = False,
    redraw: bool = True,
) -> EventDataDict

Parameters:


Delete a column from the sheet

del_column(
    idx: int = 0,
    data_indexes: bool = True,
    undo: bool = True,
    emit_event: bool = False,
    redraw: bool = True,
) -> EventDataDict

Parameters:


Delete multiple columns from the sheet

del_columns(
    columns: int | Iterator[int],
    data_indexes: bool = True,
    undo: bool = True,
    emit_event: bool = False,
    redraw: bool = True,
) -> EventDataDict

Parameters:


Expands or contracts the sheet data dimensions.

sheet_data_dimensions(
    total_rows: int | None = None,
    total_columns: int | None = None,
) -> Sheet

Parameters:


set_sheet_data_and_display_dimensions(
    total_rows: int | None = None,
    total_columns: int | None = None,
) -> Sheet

Parameters:


total_rows(
    number: int | None = None,
    mod_positions: bool = True,
    mod_data: bool = True,
) -> int | Sheet

Parameters:


total_columns(
    number: int | None = None,
    mod_positions: bool = True,
    mod_data: bool = True,
) -> int | Sheet

Parameters:


Move a single row to a new location

move_row(
    row: int,
    moveto: int,
) -> tuple[dict, dict, dict]

Move a single column to a new location

move_column(
    column: int,
    moveto: int,
) -> tuple[dict[int, int], dict[int, int], EventDataDict]:

Move any rows to a new location

move_rows(
    move_to: int | None = None,
    to_move: list[int] | None = None,
    move_data: bool = True,
    data_indexes: bool = False,
    create_selections: bool = True,
    undo: bool = True,
    emit_event: bool = False,
    move_heights: bool = True,
    event_data: EventDataDict | None = None,
    redraw: bool = True,
) -> tuple[dict[int, int], dict[int, int], EventDataDict]:

Parameters:

Notes: - The rows in to_move do not have to be contiguous.


Move any columns to a new location

move_columns(
    move_to: int | None = None,
    to_move: list[int] | None = None,
    move_data: bool = True,
    data_indexes: bool = False,
    create_selections: bool = True,
    undo: bool = True,
    emit_event: bool = False,
    move_widths: bool = True,
    event_data: EventDataDict | None = None,
    redraw: bool = True,
) -> tuple[dict[int, int], dict[int, int], EventDataDict]:

Parameters:

Notes:


Move any columns to new locations

mapping_move_columns(
    data_new_idxs: dict[int, int],
    disp_new_idxs: None | dict[int, int] = None,
    move_data: bool = True,
    create_selections: bool = True,
    undo: bool = True,
    emit_event: bool = False,
    redraw: bool = True,
) -> tuple[dict[int, int], dict[int, int], EventDataDict]

Parameters:


Get a mapping (dict) of all old: new column indexes.

full_move_columns_idxs(data_idxs: dict[int, int]) -> dict[int, int]

Move any rows to new locations

mapping_move_rows(
    data_new_idxs: dict[int, int],
    disp_new_idxs: None | dict[int, int] = None,
    move_data: bool = True,
    create_selections: bool = True,
    undo: bool = True,
    emit_event: bool = False,
    redraw: bool = True,
) -> tuple[dict[int, int], dict[int, int], EventDataDict]

Parameters:


Get a mapping (dict) of all old: new row indexes.

full_move_rows_idxs(data_idxs: dict[int, int]) -> dict[int, int]

Make all data rows the same length

equalize_data_row_lengths(include_header: bool = True) -> int

Sorting the Table

There are three built-in sorting keys to choose from but you can always create your own and use that instead. See here for more information on how to set the default sorting key.

natural_sort_key

This is the default sorting key for natural sorting of various Python types:

Order:

  1. None
  2. Empty strings
  3. bool
  4. int, float (inc. strings that are numbers)
  5. datetime (inc. strings that are dates)
  6. strings (including string file paths and paths as POSIX strings) & unknown objects with str
  7. unknown objects

version_sort_key

An alternative sorting key that respects and sorts most version numbers:

fast_sort_key

A faster key for natural sorting of various Python types. This key should probably be used if you intend on sorting sheets with over a million cells:

Setting the default sorting key

Setting the sorting key at initialization:

from tksheet import Sheet, natural_sort_key

my_sheet = Sheet(parent=parent, sort_key=natural_sort_key)

Setting the sorting key after initialization:

from tksheet import Sheet, natural_sort_key

my_sheet.set_options(sort_key=natural_sort_key)

Using a sorting key with a tksheet sort function call:

from tksheet import Sheet, natural_sort_key

my_sheet.sort_columns(0, key=natural_sort_key)

Notes about sorting

Sorting cells

sort(
    *box: CreateSpanTypes,
    reverse: bool = False,
    row_wise: bool = False,
    validation: bool = True,
    key: Callable | None = None,
    undo: bool = True,
) -> EventDataDict

Parameters:

Notes:

Example:

# a box of cells row-wise
# start at row 0 & col 0, up to but not including row 10 & col 10
my_sheet.sort(0, 0, 10, 10, row_wise=True)

# sort the whole table
my_sheet.sort(None)

Sorting row values

def sort_rows(
    rows: AnyIter[int] | Span | int | None = None,
    reverse: bool = False,
    validation: bool = True,
    key: Callable | None = None,
    undo: bool = True,
) -> EventDataDict

Parameters:

Notes:

Sorting column values

def sort_columns(
    columns: AnyIter[int] | Span | int | None = None,
    reverse: bool = False,
    validation: bool = True,
    key: Callable | None = None,
    undo: bool = True,
) -> EventDataDict

Parameters:

Notes:

Sorting the order of all rows using a column

def sort_rows_by_column(
    column: int | None = None,
    reverse: bool = False,
    key: Callable | None = None,
    undo: bool = True,
) -> EventDataDict

Parameters:

Notes:

Sorting the order of all columns using a row

def sort_columns_by_row(
    row: int | None = None,
    reverse: bool = False,
    key: Callable | None = None,
    undo: bool = True,
) -> EventDataDict

Parameters:


Find and Replace

An in-built find and replace window can be enabled using enable_bindings(), e.g:

my_sheet.enable_bindings("find", "replace")

# all bindings, including find and replace
my_sheet.enable_bindings()

See enable_bindings for more information.

There are also some Sheet() functions that can be utilized, shown below.

Check if the find window is open

@property
find_open() -> bool

e.g. find_is_open = sheet.find_open

Open or close the find window

open_find(focus: bool = False) -> Sheet
close_find() -> Sheet

Find and select

next_match(within: bool | None = None, find: str | None = None) -> Sheet
prev_match(within: bool | None = None, find: str | None = None) -> Sheet

Parameters:

Notes:

Replace all using mapping

replace_all(mapping: dict[str, str], within: bool = False) -> EventDataDict

Parameters:

Notes:


Highlighting Cells

Creating highlights

Using spans to create highlights

Span objects (more information here) can be used to highlight cells, rows, columns, the entire sheet, headers and the index.

You can use either of the following methods: - Using a span method e.g. span.highlight() more information here. - Using a sheet method e.g. sheet.highlight(Span)

Or if you need user inserted row/columns in the middle of highlight areas to also be highlighted you can use named spans, more information here.

Whether cells, rows or columns are highlighted depends on the kind of span.

highlight(
    *key: CreateSpanTypes,
    bg: bool | None | str = False,
    fg: bool | None | str = False,
    end: bool | None = None,
    overwrite: bool = False,
    redraw: bool = True,
) -> Span

Parameters:

Example:

# highlight cell - row 3, column 5
self.sheet.highlight(
    (3, 5),
    bg="dark green",
    fg="white",
)

# or

# same cells, background red, text color black
sheet[3, 5].bg = "red"
sheet[3, 5].fg = "black"

Other ways to create highlights

Cells

highlight_cells(
    row: int | Literal["all"] = 0,
    column: int | Literal["all"] = 0,
    cells: list[tuple[int, int]] = [],
    canvas: Literal["table", "index", "header"] = "table",
    bg: bool | None | str = False,
    fg: bool | None | str = False,
    redraw: bool = True,
    overwrite: bool = True,
) -> Sheet

Rows

highlight_rows(
    rows: Iterator[int] | int,
    bg: None | str = None,
    fg: None | str = None,
    highlight_index: bool = True,
    redraw: bool = True,
    end_of_screen: bool = False,
    overwrite: bool = True,
) -> Sheet

Columns

highlight_columns(
    columns: Iterator[int] | int,
    bg: bool | None | str = False,
    fg: bool | None | str = False,
    highlight_header: bool = True,
    redraw: bool = True,
    overwrite: bool = True,
) -> Sheet

Deleting highlights

Using spans to delete highlights

If the highlights were created by a named span then the named span must be deleted, more information here.

Otherwise you can use either of the following methods to delete/remove highlights:

dehighlight(
    *key: CreateSpanTypes,
    redraw: bool = True,
) -> Span

Parameters:

Example:

# highlight column B
self.sheet.highlight(
    "B",
    bg="dark green",
    fg="white",
)

# dehighlight column B
self.sheet.dehighlight("B")

Other ways to delete highlights

Cells

dehighlight_cells(
    row: int | Literal["all"] = 0,
    column: int = 0,
    cells: list[tuple[int, int]] = [],
    canvas: Literal["table", "row_index", "header"] = "table",
    all_: bool = False,
    redraw: bool = True,
) -> Sheet

Rows

dehighlight_rows(
    rows: list[int] | Literal["all"] = [],
    redraw: bool = True,
) -> Sheet

Columns

dehighlight_columns(
    columns: list[int] | Literal["all"] = [],
    redraw: bool = True,
) -> Sheet

All

dehighlight_all(
    cells: bool = True,
    rows: bool = True,
    columns: bool = True,
    header: bool = True,
    index: bool = True,
    redraw: bool = True,
) -> Sheet

Dropdown Boxes

Creating dropdown boxes

Span objects (more information here) can be used to create dropdown boxes for cells, rows, columns, the entire sheet, headers and the index.

You can use either of the following methods:

Or if you need user inserted row/columns in the middle of areas with dropdown boxes to also have dropdown boxes you can use named spans, more information here.

Whether dropdown boxes are created for cells, rows or columns depends on the kind of span.

dropdown(
    *key: CreateSpanTypes,
    values: list = [],
    edit_data: bool = True,
    set_values: dict[tuple[int, int] | int, object] | None = None,
    set_value: object = None,
    state: Literal["normal", "readonly", "disabled"] = "normal",
    redraw: bool = True,
    selection_function: Callable | None = None,
    modified_function: Callable | None = None,
    search_function: Callable = dropdown_search_function,
    validate_input: bool = True,
    text: None | str = None,
) -> Span

Notes:

Parameters:

Example:

# create dropdown boxes in column "D"
self.sheet.dropdown(
    "D",
    values=[0, 1, 2, 3, 4],
)

Deleting dropdown boxes

If the dropdown boxes were created by a named span then the named span must be deleted, more information here.

Otherwise you can use either of the following methods to delete/remove dropdown boxes. - Using a span method e.g. span.del_dropdown() more information here. - Using a sheet method e.g. sheet.del_dropdown(Span) details below:

del_dropdown(
    *key: CreateSpanTypes,
    redraw: bool = True,
) -> Span

Parameters:

Example:

# create dropdown boxes in column "D"
self.sheet.dropdown(
    "D",
    values=[0, 1, 2, 3, 4],
)

# delete dropdown boxes in column "D"
self.sheet.del_dropdown("D")

Get chosen dropdown boxes values

get_dropdown_values(r: int = 0, c: int = 0) -> None | list
get_header_dropdown_values(c: int = 0) -> None | list
get_index_dropdown_values(r: int = 0) -> None | list

Set the values and cell value of a chosen dropdown box

set_dropdown_values(
    r: int = 0,
    c: int = 0,
    set_existing_dropdown: bool = False,
    values: list = [],
    set_value: object = None,
) -> Sheet
set_header_dropdown_values(
    c: int = 0,
    set_existing_dropdown: bool = False,
    values: list = [],
    set_value: object = None,
) -> Sheet
set_index_dropdown_values(
    r: int = 0,
    set_existing_dropdown: bool = False,
    values: list = [],
    set_value: object = None,
) -> Sheet

Parameters:


Set or get bound dropdown functions

dropdown_functions(
    r: int,
    c: int,
    selection_function: str | Callable = "",
    modified_function: str | Callable = "",
) -> None | dict
header_dropdown_functions(
    c: int,
    selection_function: str | Callable = "",
    modified_function: str | Callable = "",
) -> None | dict
index_dropdown_functions(
    r: int,
    selection_function: str | Callable = "",
    modified_function: str | Callable = "",
) -> None | dict

Get a dictionary of all dropdown boxes

get_dropdowns() -> dict

Returns:

{(row int, column int): {'values': values,
                         'select_function': selection_function,
                         'modified_function': modified_function,
                         'state': state,
                         'text': text}}
get_header_dropdowns() -> dict
get_index_dropdowns() -> dict

Open a dropdown box

open_dropdown(r: int, c: int) -> Sheet
open_header_dropdown(c: int) -> Sheet
open_index_dropdown(r: int) -> Sheet

Close a dropdown box

close_dropdown(r: int | None = None, c: int | None = None) -> Sheet
close_header_dropdown(c: int | None = None) -> Sheet
close_index_dropdown(r: int | None = None) -> Sheet

Notes: - Also destroys any opened text editor windows.


Check Boxes

Creating check boxes

Span objects (more information here) can be used to create check boxes for cells, rows, columns, the entire sheet, headers and the index.

You can use either of the following methods:

Or if you need user inserted row/columns in the middle of areas with check boxes to also have check boxes you can use named spans, more information here.

Whether check boxes are created for cells, rows or columns depends on the kind of span.

checkbox(
    *key: CreateSpanTypes,
    edit_data: bool = True,
    checked: bool | None = None,
    state: Literal["normal", "disabled"] = "normal",
    redraw: bool = True,
    check_function: Callable | None = None,
    text: str = "",
) -> Span

Parameters:

Notes:

Example:

self.sheet.checkbox(
    "D",
    checked=True,
)

Deleting check boxes

If the check boxes were created by a named span then the named span must be deleted, more information here.

Otherwise you can use either of the following methods to delete/remove check boxes:

del_checkbox(
    *key: CreateSpanTypes,
    redraw: bool = True,
) -> Span

Parameters:

Example:

# creating checkboxes in column D
self.sheet.checkbox(
    "D",
    checked=True,
)

# deleting checkboxes in column D
self.sheet.del_checkbox("D")

Set or toggle a check box

click_checkbox(
    *key: CreateSpanTypes,
    checked: bool | None = None,
    redraw: bool = True,
) -> Span
click_header_checkbox(c: int, checked: bool | None = None) -> Sheet
click_index_checkbox(r: int, checked: bool | None = None) -> Sheet

Get a dictionary of all check box dictionaries

get_checkboxes() -> dict
get_header_checkboxes() -> dict
get_index_checkboxes() -> dict

Data Formatting

By default tksheet stores all user inputted data as strings and while tksheet can store and display any datatype with a __str__() method this has some obvious limitations.

Data formatting aims to provide greater functionality when working with different datatypes and provide strict typing for the sheet. With formatting you can convert sheet data and user input to a specific datatype.

Additionally, formatting also provides a function for displaying data on the table GUI (as a rounded float for example) and logic for handling invalid and missing data.

tksheet has several basic built-in formatters and provides functionality for creating your own custom formats as well.

A demonstration of all the built-in and custom formatters can be found here.

Creation and deletion of data formatting rules

Creating a data format rule

Span objects (more information here) can be used to format data for cells, rows, columns and the entire sheet.

You can use either of the following methods: - Using a span method e.g. span.format() more information here. - Using a sheet method e.g. sheet.format(Span)

Or if you need user inserted row/columns in the middle of areas with data formatting to also be formatted you can use named spans, more information here.

Whether data is formatted for cells, rows or columns depends on the kind of span.

format(
    *key: CreateSpanTypes,
    formatter_options: dict = {},
    formatter_class: object = None,
    redraw: bool = True,
    **kwargs,
) -> Span

Notes:

  1. When applying multiple overlapping formats with e.g. a formatted cell which overlaps a formatted row, the priority is as follows:
    • Cell formats first.
    • Row formats second.
    • Column formats third.
  2. Data formatting will effectively override validate_input = True on cells with dropdown boxes.
  3. When getting data take careful note of the get_displayed options, as these are the difference between getting the actual formatted data and what is displayed on the table GUI.

Parameters:


Deleting a data format rule

If the data format rule was created by a named span then the named span must be deleted, more information here.

Otherwise you can use either of the following methods to delete/remove data formatting rules:

del_format(
    *key: CreateSpanTypes,
    clear_values: bool = False,
    redraw: bool = True,
) -> Span

Delete all formatting

del_all_formatting(clear_values: bool = False) -> Sheet

Reapply formatting to entire sheet

reapply_formatting() -> Sheet

Check if a cell is formatted

formatted(r: int, c: int) -> dict

Formatters

In addition to the generic formatter, tksheet provides formatters for many different data types.

A basic example showing how formatting some columns as float might be done:

import tkinter as tk

from tksheet import (
    Sheet,
    float_formatter,
)
from tksheet import (
    num2alpha,
)


class demo(tk.Tk):
    def __init__(self):
        super().__init__()
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)
        self.sheet = Sheet(
            self,
            data=[[f"{r}", f"{r}"] for r in range(5)],
            expand_sheet_if_paste_too_big=True,
            theme="dark blue",
        )
        """
        Format example
        """
        # some keyword arguments inside float_formatter()
        self.sheet.format(
            num2alpha(0), # column A
            formatter_options=float_formatter(
                decimals=1,
                nullable=True,
            ),
        )
        # some keyword arguments outside
        # of float_formatter() instead
        self.sheet.format(
            "B", # column B
            formatter_options=float_formatter(),
            decimals=3,
            nullable=False,
        )
        """
        Rest of code
        """
        self.sheet.grid(row=0, column=0, sticky="nswe")
        self.sheet.enable_bindings(
            "all",
            "ctrl_select",
            "edit_header",
            "edit_index",
        )


app = demo()
app.mainloop()

You can use any of the following formatters as an argument for the parameter formatter_options.

A full list of keyword arguments available to these formatters is shown here.


Int Formatter

The int_formatter is the basic configuration for a simple interger formatter.

int_formatter(
    datatypes: tuple[object] | object = int,
    format_function: Callable = to_int,
    to_str_function: Callable = to_str,
    invalid_value: object = "NaN",
    **kwargs,
) -> dict

Parameters:

Example:

sheet.format_cell(0, 0, formatter_options = tksheet.int_formatter())

Float Formatter

The float_formatter is the basic configuration for a simple float formatter. It will always round float-likes to the specified number of decimal places, for example "5.999" will be converted to "6.0" if decimals = 1.

float_formatter(
    datatypes: tuple[object] | object = float,
    format_function: Callable = to_float,
    to_str_function: Callable = float_to_str,
    invalid_value: object = "NaN",
    decimals: int = 2,
    **kwargs
) -> dict

Parameters:

Example:

sheet.format_cell(0, 0, formatter_options = tksheet.float_formatter(decimals = None)) # A float formatter with maximum float() decimal places

Percentage Formatter

The percentage_formatter is the basic configuration for a simple percentage formatter. It will always round float-likes as a percentage to the specified number of decimal places, for example "5.999%" will be converted to "6.0%" if decimals = 1.

percentage_formatter(
    datatypes: tuple[object] | object = float,
    format_function: Callable = to_percentage,
    to_str_function: Callable = percentage_to_str,
    invalid_value: object = "NaN",
    decimals: int = 0,
    **kwargs,
) -> dict

Parameters:

Example:

sheet.format_cell(0, 0, formatter_options=tksheet.percentage_formatter(decimals=1)) # A percentage formatter with 1 decimal place

Note:

By default percentage_formatter() converts user entry 21 to 2100% and 21% to 21%. An example where 21 is converted to 21% instead is shown below:

# formats column A as percentage

# uses:
# format_function=alt_to_percentage
# to_str_function=alt_percentage_to_str
sheet.format(
    "A",
    formatter_options=tksheet.percentage_formatter(
        format_function=alt_to_percentage,
        to_str_function=alt_percentage_to_str,
    )
)

Bool Formatter

bool_formatter(
    datatypes: tuple[object] | object = bool,
    format_function: Callable = to_bool,
    to_str_function: Callable = bool_to_str,
    invalid_value: object = "NA",
    truthy: set = truthy,
    falsy: set = falsy,
    **kwargs,
) -> dict

Parameters:

Example:

# A bool formatter with custom truthy and falsy values to account for aussie and kiwi slang
sheet.format_cell(0, 0, formatter_options = tksheet.bool_formatter(truthy = tksheet.truthy | {"nah yeah"}, falsy = tksheet.falsy | {"yeah nah"}))

Generic Formatter

formatter(
    datatypes: tuple[object] | object,
    format_function: Callable,
    to_str_function: Callable = to_str,
    invalid_value: object = "NaN",
    nullable: bool = True,
    pre_format_function: Callable | None = None,
    post_format_function: Callable | None = None,
    clipboard_function: Callable | None = None,
    **kwargs,
) -> dict

This is the generic formatter options interface. You can use this to create your own custom formatters. The following options are available.

Note that all these options can also be passed to the Sheet() format functions as keyword arguments and are available as attributes for all formatters.

You can also provide functions of your own creation for all the below arguments which take functions if you require.


Datetime Formatters and Designing Your Own Custom Formatters

tksheet is at the moment a dependency free library and so doesn't include a datetime parser as is.

You can however very easily make a datetime parser if you are willing to install a third-party package. Recommended are:

Both of these packages have a very comprehensive datetime parser which can be used to create a custom datetime formatter for tksheet.

Below is a simple example of how you might create a custom datetime formatter using the dateutil package.

from tksheet import *
from datetime import datetime, date
from dateutil.parser import parse

def to_local_datetime(dt, **kwargs):
    '''
    Our custom format_function, converts a string or a date to a datetime object in the local timezone.
    '''
    if isinstance(dt, datetime):
        pass # Do nothing
    elif isinstance(dt, date):
        dt = datetime(dt.year, dt.month, dt.day) # Always good to account for unexpected inputs
    else:
        try:
            dt = parser.parse(dt)
        except:
            raise ValueError(f"Could not parse {dt} as a datetime")
    if dt.tzinfo is None:
        dt = dt.replace(tzinfo = tzlocal()) # If no timezone is specified, assume local timezone
    dt = dt.astimezone(tzlocal()) # Convert to local timezone
    return dt

def datetime_to_str(dt, **kwargs):
    '''
    Our custom to_str_function, converts a datetime object to a string with a format that can be specfied in kwargs.
    '''
    return dt.strftime(kwargs['format'])

# Now we can create our custom formatter dictionary from the generic formatter interface in tksheet
datetime_formatter = formatter(datatypes = datetime,
                               format_function = to_local_datetime,
                               to_str_function = datetime_to_str,
                               invalid_value = "NaT",
                               format = "%d/%m/%Y %H:%M:%S",
                               )

# From here we can pass our datetime_formatter into sheet.format() or span.format() just like any other formatter

For those wanting even more customisation of their formatters you also have the option of creating a custom formatter class. This is a more advanced topic and is not covered here, but it's recommended to create a new class which is a subclass of the tksheet.Formatter class and overriding the methods you would like to customise. This custom class can then be passed into the format_cells() formatter_class argument.


Readonly Cells

Creating a readonly rule

Span objects (more information here) can be used to create readonly rules for cells, rows, columns, the entire sheet, headers and the index.

You can use either of the following methods:

Or if you need user inserted row/columns in the middle of areas with a readonly rule to also have a readonly rule you can use named spans, more information here.

Whether cells, rows or columns are readonly depends on the kind of span.

readonly(
    *key: CreateSpanTypes,
    readonly: bool = True,
) -> Span

Parameters:


Deleting a readonly rule

If the readonly rule was created by a named span then the named span must be deleted, more information here.

Otherwise you can use either of the following methods to delete/remove readonly rules:

# creating a readonly rule
self.sheet.readonly(
    self.sheet.span("A", header=True),
    readonly=True,
)

# deleting the readonly rule
self.sheet.readonly(
    self.sheet.span("A", header=True),
    readonly=False,
)

Parameters:


Text Font and Alignment

Font

Set the table and index font

font(
    newfont: tuple[str, int, str] | None = None,
    reset_row_positions: bool = True,
) -> tuple[str, int, str]

Set the header font

header_font(newfont: tuple[str, int, str] | None = None) -> tuple[str, int, str]

Text Alignment

There are functions to set the text alignment for specific cells/rows/columns and also functions to set the text alignment for a whole part of the sheet (table/index/header).

Unfortunately vertical alignment is not available.

Whole widget text alignment

Set the text alignment for the whole of the table (doesn't include index/header).

table_align(
    align: str = None,
    redraw: bool = True,
) -> str | Sheet

Set the text alignment for the whole of the header.

header_align(
    align: str = None,
    redraw: bool = True,
) -> str | Sheet

Set the text alignment for the whole of the index.

row_index_align(
    align: str = None,
    redraw: bool = True,
) -> str | Sheet

# can also use index_align() which behaves the same

Creating a specific cell row or column text alignment rule

The following function is for setting text alignment for specific cells, rows or columns in the table, header and index.

Span objects (more information here) can be used to create text alignment rules for cells, rows, columns, the entire sheet, headers and the index.

You can use either of the following methods:

Or if you need user inserted row/columns in the middle of areas with an alignment rule to also have an alignment rule you can use named spans, more information here.

Whether cells, rows or columns are affected depends on the kind of span.

align(
    *key: CreateSpanTypes,
    align: str | None = None,
    redraw: bool = True,
) -> Span

Parameters:

Deleting a specific text alignment rule

If the text alignment rule was created by a named span then the named span must be deleted, more information here.

Otherwise you can use either of the following methods to delete/remove specific text alignment rules:

del_align(
    *key: CreateSpanTypes,
    redraw: bool = True,
) -> Span

Parameters:

Get existing specific text alignments

Cell text alignments:

get_cell_alignments() -> dict

Row text alignments:

get_row_alignments() -> dict

Column text alignments:

get_column_alignments() -> dict

Getting Cell Properties

The below functions can be used to retrieve cell options/properties such as highlights, format, readonly etc.

Get table cell properties

Retrieve options for a single cell in the main table. Also retrieves any row/column options impacting that cell.

props(
    row: int,
    column: int | str,
    key: None
    | Literal[
        "format",
        "highlight",
        "dropdown",
        "checkbox",
        "readonly",
        "align",
    ] = None,
    cellops: bool = True,
    rowops: bool = True,
    columnops: bool = True,
) -> dict

Parameters:

Example:

# making column B, including header read only
sheet.readonly(sheet["B"].options(header=True))

# checking if row 0, column 1 (B) is readonly:
cell_is_readonly = sheet.props(0, 1, "readonly")

# can also use a string for the column:
cell_is_readonly = sheet.props(0, "b", "readonly")

Get index cell properties

Retrieve options for a single cell in the index.

index_props(
    row: int,
    key: None
    | Literal[
        "format",
        "highlight",
        "dropdown",
        "checkbox",
        "readonly",
        "align",
    ] = None,
) -> dict

Parameters:


Get header cell properties

Retrieve options for a single cell in the header.

header_props(
    column: int | str,
    key: None
    | Literal[
        "format",
        "highlight",
        "dropdown",
        "checkbox",
        "readonly",
        "align",
    ] = None,
) -> dict

Parameters:


Getting Selected Cells

All selected cell/box getting functions return or generate displayed cell coordinates.

Get the currently selected cell

This is always a single cell of displayed indices. If you have hidden rows or columns you can change the integers to data indices using the following functions:

get_currently_selected() -> tuple | Selected

Notes:

Example:

currently_selected = self.sheet.get_currently_selected()
if currently_selected:
    row = currently_selected.row
    column = currently_selected.column
    type_ = currently_selected.type_

if self.sheet.selected:
    ...

Get selected rows

get_selected_rows(
    get_cells: bool = False,
    get_cells_as_rows: bool = False,
    return_tuple: bool = False,
) -> tuple[int] | tuple[tuple[int, int]] | set[int] | set[tuple[int, int]]

Get selected columns

get_selected_columns(
    get_cells: bool = False,
    get_cells_as_columns: bool = False,
    return_tuple: bool = False,
) -> tuple[int] | tuple[tuple[int, int]] | set[int] | set[tuple[int, int]]

Get selected cells

get_selected_cells(
    get_rows: bool = False,
    get_columns: bool = False,
    sort_by_row: bool = False,
    sort_by_column: bool = False,
    reverse: bool = False,
) -> list[tuple[int, int]] | set[tuple[int, int]]

Generate selected cells

gen_selected_cells(
    get_rows: bool = False,
    get_columns: bool = False,
) -> Generator[tuple[int, int]]

Get all selection boxes

get_all_selection_boxes() -> tuple[tuple[int, int, int, int]]

Get all selection boxes and their types

get_all_selection_boxes_with_types() -> list[tuple[tuple[int, int, int, int], str]]

Equivalent to get_all_selection_boxes_with_types() but shortened as @property.

@property
boxes() -> list[tuple[tuple[int, int, int, int], str]]

Check if cell is selected

cell_selected(
    r: int,
    c: int,
    rows: bool = False,
    columns: bool = False,
) -> bool

Check if row is selected

row_selected(r: int, cells: bool = False) -> bool

Check if column is selected

column_selected(c: int, cells: bool = False) -> bool

Check if any cells, rows or columns are selected, there are options for exclusions

anything_selected(
    exclude_columns: bool = False,
    exclude_rows: bool = False,
    exclude_cells: bool = False,
) -> bool

Check if user has the entire table selected

all_selected() -> bool

get_ctrl_x_c_boxes(nrows: bool = True) -> tuple[dict[tuple[int, int, int, int], str], int]

@property
ctrl_boxes() -> dict[tuple[int, int, int, int], str]

get_selected_min_max() -> tuple[int, int, int, int] | tuple[None, None, None, None]

Modifying Selected Cells

All selected cell/box setting functions use displayed cell coordinates.

set_currently_selected(row: int | None = None, column: int | None = None) -> Sheet

select_row(row: int, redraw: bool = True, run_binding_func: bool = True) -> Sheet

select_column(column: int, redraw: bool = True, run_binding_func: bool = True) -> Sheet

select_cell(row: int, column: int, redraw: bool = True, run_binding_func: bool = True) -> Sheet

select_all(redraw: bool = True, run_binding_func: bool = True) -> Sheet

Deselect a specific cell, row or column.

deselect(
    row: int | None | Literal["all"] = None,
    column: int | None = None,
    cell: tuple | None = None,
    redraw: bool = True,
) -> Sheet

Deselect any cell, row or column selection box conflicting with rows and/or columns.

deselect_any(
    rows: Iterator[int] | int | None,
    columns: Iterator[int] | int | None,
    redraw: bool = True,
) -> Sheet

add_cell_selection(
    row: int,
    column: int,
    redraw: bool = True,
    run_binding_func: bool = True,
    set_as_current: bool = True,
) -> Sheet

add_row_selection(
    row: int,
    redraw: bool = True,
    run_binding_func: bool = True,
    set_as_current: bool = True,
) -> Sheet

add_column_selection(
    column: int,
    redraw: bool = True,
    run_binding_func: bool = True,
    set_as_current: bool = True,
) -> Sheet

toggle_select_cell(
    row: int,
    column: int,
    add_selection: bool = True,
    redraw: bool = True,
    run_binding_func: bool = True,
    set_as_current: bool = True,
) -> Sheet

toggle_select_row(
    row: int,
    add_selection: bool = True,
    redraw: bool = True,
    run_binding_func: bool = True,
    set_as_current: bool = True,
) -> Sheet

toggle_select_column(
    column: int,
    add_selection: bool = True,
    redraw: bool = True,
    run_binding_func: bool = True,
    set_as_current: bool = True,
) -> Sheet

create_selection_box(
    r1: int,
    c1: int,
    r2: int,
    c2: int,
    type_: str = "cells",
) -> int

@boxes.setter
boxes(boxes: Sequence[tuple[tuple[int, int, int, int], str]])

Example:

sheet.boxes = [
    ((0, 0, 3, 3), "cells"),
    ((4, 0, 5, 10), "rows"),
]

recreate_all_selection_boxes() -> Sheet

Row Heights and Column Widths

Auto resize column widths to fit the window

To enable auto resizing of columns to the Sheet window use set_options() with the keyword argument auto_resize_columns. This argument can either be an int or None. If set as an int the columns will automatically resize to fit the width of the window, the int value being the minimum of each column in pixels. If None it will disable the auto resizing. Example:

# auto resize columns, column minimum width set to 150 pixels
set_options(auto_resize_columns=150)

Auto resize row heights to fit the window

To enable auto resizing of rows to the Sheet window use set_options() with the keyword argument auto_resize_rows. This argument can either be an int or None. If set as an int the rows will automatically resize to fit the width of the window, the int value being the minimum of each row in pixels. If None it will disable the auto resizing. Example:

# auto resize rows, row minimum width set to 30 pixels
set_options(auto_resize_rows=30)

Set default column width in pixels

default_column_width(width: int | None = None) -> int

Set default row height in pixels or lines

default_row_height(height: int | str | None = None) -> int

Set default header bar height in pixels or lines

default_header_height(height: int | str | None = None) -> int

Set a specific cell size to its text

set_cell_size_to_text(
    row: int,
    column: int,
    only_set_if_too_small: bool = False,
    redraw: bool = True,
) -> Sheet

Set all row heights and column widths to cell text sizes

set_all_cell_sizes_to_text(
    redraw: bool = True,
    width: int | None = None,
    slim: bool = False,
) -> tuple[list[float], list[float]]

Set all column widths to a specific width in pixels

set_all_column_widths(
    width: int | None = None,
    only_set_if_too_small: bool = False,
    redraw: bool = True,
    recreate_selection_boxes: bool = True,
) -> Sheet

Set all row heights to a specific height in pixels

set_all_row_heights(
    height: int | None = None,
    only_set_if_too_small: bool = False,
    redraw: bool = True,
    recreate_selection_boxes: bool = True,
) -> Sheet

Set or get a specific column width

column_width(
    column: int | Literal["all", "displayed"] | None = None,
    width: int | Literal["default", "text"] | None = None,
    only_set_if_too_small: bool = False,
    redraw: bool = True,
) -> Sheet | int

Set or get a specific row height

row_height(
    row: int | Literal["all", "displayed"] | None = None,
    height: int | Literal["default", "text"] | None = None,
    only_set_if_too_small: bool = False,
    redraw: bool = True,
) -> Sheet | int

Get the sheets column widths

get_column_widths(canvas_positions: bool = False) -> list[float]

Get the sheets row heights

get_row_heights(canvas_positions: bool = False) -> list[float]

Get a rows text height

get_row_text_height(
    row: int,
    visible_only: bool = False,
    only_if_too_small: bool = False,
) -> int

Get a columns text width

get_column_text_width(
    column: int,
    visible_only: bool = False,
    only_if_too_small: bool = False,
) -> int

Set or reset displayed column widths/lines

set_column_widths(
    column_widths: Iterator[int, float] | None = None,
    canvas_positions: bool = False,
    reset: bool = False,
) -> Sheet

Set or reset displayed row heights/lines

set_row_heights(
    row_heights: Iterator[int, float] | None = None,
    canvas_positions: bool = False,
    reset: bool = False,
) -> Sheet

Set the width of the index to a str or existing values

set_width_of_index_to_text(text: None | str = None, *args, **kwargs) -> Sheet

Set the width of the index to a value in pixels

set_index_width(pixels: int, redraw: bool = True) -> Sheet

Set the height of the header to a str or existing values

set_height_of_header_to_text(text: None | str = None) -> Sheet

Set the height of the header to a value in pixels

set_header_height_pixels(pixels: int, redraw: bool = True) -> Sheet

Set the height of the header to accomodate a number of lines

set_header_height_lines(nlines: int, redraw: bool = True) -> Sheet

Delete displayed column lines

del_column_position(idx: int, deselect_all: bool = False) -> Sheet
del_column_positions(idxs: Iterator[int] | None = None) -> Sheet

Delete displayed row lines

del_row_position(idx: int, deselect_all: bool = False) -> Sheet
del_row_positions(idxs: Iterator[int] | None = None) -> Sheet

Insert a displayed row line

insert_row_position(
    idx: Literal["end"] | int = "end",
    height: int | None = None,
    deselect_all: bool = False,
    redraw: bool = False,
) -> Sheet

Insert a displayed column line

insert_column_position(
    idx: Literal["end"] | int = "end",
    width: int | None = None,
    deselect_all: bool = False,
    redraw: bool = False,
) -> Sheet

Insert multiple displayed row lines

insert_row_positions(
    idx: Literal["end"] | int = "end",
    heights: Sequence[float] | int | None = None,
    deselect_all: bool = False,
    redraw: bool = False,
) -> Sheet

Insert multiple displayed column lines

insert_column_positions(
    idx: Literal["end"] | int = "end",
    widths: Sequence[float] | int | None = None,
    deselect_all: bool = False,
    redraw: bool = False,
) -> Sheet

Set the number of displayed row lines and column lines

sheet_display_dimensions(
    total_rows: int | None =None,
    total_columns: int | None =None,
) -> tuple[int, int] | Sheet

Move a displayed row line

move_row_position(row: int, moveto: int) -> Sheet

Move a displayed column line

move_column_position(column: int, moveto: int) -> Sheet

Get a list of default column width values

get_example_canvas_column_widths(total_cols: int | None = None) -> list[float]

Get a list of default row height values

get_example_canvas_row_heights(total_rows: int | None = None) -> list[float]

Verify a list of row heights or canvas y coordinates

verify_row_heights(row_heights: list[float], canvas_positions: bool = False) -> bool

Verify a list of column widths or canvas x coordinates

verify_column_widths(column_widths: list[float], canvas_positions: bool = False) -> bool

Make a row height valid

valid_row_height(height: int) -> int

Make a column width valid

valid_column_width(width: int) -> int

Get visible rows

@property
visible_rows() -> tuple[int, int]

Get visible columns

@property
visible_columns() -> tuple[int, int]

Identifying Bound Event Mouse Position

The below functions require a mouse click event, for example you could bind right click, example here, and then identify where the user has clicked.


Determine if a tk event.widget is the Sheet.

event_widget_is_sheet(
    event: object,
    table: bool = True,
    index: bool = True,
    header: bool = True,
    top_left: bool = True,
) -> bool

Notes:


Check if any Sheet widgets have focus.

has_focus() -> bool:

identify_region(event: object) -> Literal["table", "index", "header", "top left"]

identify_row(
    event: object,
    exclude_index: bool = False,
    allow_end: bool = True,
) -> int | None

identify_column(
    event: object,
    exclude_header: bool = False,
    allow_end: bool = True,
) -> int | None

Sheet control actions

For example: sheet.bind("<Control-B>", sheet.paste)

cut(event: object = None, validation: bool = True) -> None | EventDataDict
paste(event: object = None, validation: bool = True) -> None | EventDataDict
delete(event: object = None, validation: bool = True) -> None | EventDataDict
copy(event: object = None) -> None | EventDataDict
undo(event: object = None) -> None | EventDataDict
redo(event: object = None) -> None | EventDataDict

Scroll Positions and Cell Visibility

Sync scroll positions between widgets

sync_scroll(widget: object) -> Sheet

Syncing two sheets:

self.sheet1.sync_scroll(self.sheet2)

Syncing three sheets:

# syncs sheet 1 and 2 between each other
self.sheet1.sync_scroll(self.sheet2)

# syncs sheet 1 and 3 between each other
self.sheet1.sync_scroll(self.sheet3)

# syncs sheet 2 and 3 between each other
self.sheet2.sync_scroll(self.sheet3)

Unsync scroll positions between widgets

unsync_scroll(widget: object = None) -> Sheet

See or scroll to a specific cell on the sheet

see(
    row: int = 0,
    column: int = 0,
    keep_yscroll: bool = False,
    keep_xscroll: bool = False,
    bottom_right_corner: bool | None = None,
    check_cell_visibility: bool = True,
    redraw: bool = True,
) -> Sheet

Check if a cell has any part of it visible

cell_visible(r: int, c: int) -> bool

Check if a cell is totally visible

cell_completely_visible(r: int, c: int, seperate_axes: bool = False) -> bool

set_xview(position: None | float = None, option: str = "moveto") -> Sheet | tuple[float, float]

Notes: - If position is None then tuple[float, float] of main table xview() is returned. - xview and xview_moveto have the same behaviour.


set_yview(position: None | float = None, option: str = "moveto") -> Sheet | tuple[float, float]

get_xview() -> tuple[float, float]

get_yview() -> tuple[float, float]

set_view(x_args: [str, float], y_args: [str, float]) -> Sheet

Hiding Columns

Note that once you have hidden columns you can use the function displayed_column_to_data(column) to retrieve a column data index from a displayed index.

Display only certain columns

display_columns(
    columns: None | Literal["all"] | Iterator[int] = None,
    all_columns_displayed: None | bool = None,
    reset_col_positions: bool = True,
    refresh: bool = False,
    redraw: bool = False,
    deselect_all: bool = True,
    **kwargs,
) -> list[int] | None

Parameters:

Examples:

# display all columns
self.sheet.display_columns("all")

# displaying specific columns only
self.sheet.display_columns([2, 4, 7], all_displayed = False)

Get all columns displayed boolean

Get the bool

@property
all_columns()

Set the bool

@all_columns.setter
all_columns(a: bool)

e.g. sheet.all_columns = True.

all_columns_displayed(a: bool | None = None) -> bool

Hide specific columns

hide_columns(
    columns: int | set | Iterator[int] = set(),
    redraw: bool = True,
    deselect_all: bool = True,
    data_indexes: bool = False,
) -> Sheet

Parameters:

Example:

columns_to_hide = set(sheet.data_c(c) for c in sheet.get_selected_columns())
sheet.hide_columns(
    columns_to_hide,
    data_indexes=True,
)

Unhide specific columns

show_columns(
    columns: int | Iterator[int],
    redraw: bool = True,
    deselect_all: bool = True,
) -> Sheet

Parameters:

Notes:

Example:

# converting displayed column indexes to data indexes using data_c(c)
columns = set(sheet.data_c(c) for c in sheet.get_selected_columns())

# hiding columns
sheet.hide_columns(
    columns,
    data_indexes=True,
)

# showing them again
sheet.show_columns(columns)

Displayed column index to data

Convert a displayed column index to a data index. If the internal all_columns_displayed attribute is True then it will return the provided argument.

displayed_column_to_data(c)
data_c(c)

Get currently displayed columns

@property
displayed_columns() -> list[int]

Hiding Rows

Note that once you have hidden rows you can use the function displayed_row_to_data(row) to retrieve a row data index from a displayed index.

Display only certain rows

display_rows(
    rows: None | Literal["all"] | Iterator[int] = None,
    all_rows_displayed: None | bool = None,
    reset_row_positions: bool = True,
    refresh: bool = False,
    redraw: bool = False,
    deselect_all: bool = True,
    **kwargs,
) -> list[int] | None

Parameters:

Examples:

# display all rows
self.sheet.display_rows("all")

# display specific rows only
self.sheet.display_rows([2, 4, 7], all_displayed = False)

Hide specific rows

hide_rows(
    rows: int | set | Iterator[int] = set(),
    redraw: bool = True,
    deselect_all: bool = True,
    data_indexes: bool = False,
) -> Sheet

Parameters:

Example:

rows_to_hide = set(sheet.data_r(r) for r in sheet.get_selected_rows())
sheet.hide_rows(
    rows_to_hide,
    data_indexes=True,
)

Unhide specific rows

show_rows(
    rows: int | Iterator[int],
    redraw: bool = True,
    deselect_all: bool = True,
) -> Sheet

Parameters:

Notes:

Example:

# converting displayed row indexes to data indexes using data_r(r)
rows = set(sheet.data_r(r) for r in sheet.get_selected_rows())

# hiding rows
sheet.hide_rows(
    rows,
    data_indexes=True,
)

# showing them again
sheet.show_rows(rows)

Get or set the all rows displayed boolean

Get the bool

@property
all_rows()

Set the bool

@all_rows.setter
all_rows(a: bool)

e.g. sheet.all_rows = True.

all_rows_displayed(a: bool | None = None) -> bool

Displayed row index to data

Convert a displayed row index to a data index. If the internal all_rows_displayed attribute is True then it will return the provided argument.

displayed_row_to_data(r)
data_r(r)

Get currently displayed rows

@property
displayed_rows() -> list[int]

Hiding Sheet Elements

Hide parts of the Sheet or all of it

hide(
    canvas: Literal[
        "all",
        "row_index",
        "header",
        "top_left",
        "x_scrollbar",
        "y_scrollbar",
    ] = "all",
) -> Sheet

Show parts of the Sheet or all of it

show(
    canvas: Literal[
        "all",
        "row_index",
        "header",
        "top_left",
        "x_scrollbar",
        "y_scrollbar",
    ] = "all",
) -> Sheet

Sheet Height and Width

Modify widget height and width in pixels

height_and_width(
    height: int | None = None,
    width: int | None = None,
) -> Sheet

get_frame_y(y: int) -> int

get_frame_x(x: int) -> int

Cell Text Editor

Open the currently selected cell in the main table

open_cell(ignore_existing_editor: bool = True) -> Sheet

Open the currently selected cell but in the header

open_header_cell(ignore_existing_editor: bool = True) -> Sheet

Open the currently selected cell but in the index

open_index_cell(ignore_existing_editor: bool = True) -> Sheet

Set the cell text editor value if it is open

Table:

set_text_editor_value(
    text: str = "",
) -> Sheet

Index:

set_index_text_editor_value(
    text: str = "",
) -> Sheet

Header:

set_header_text_editor_value(
    text: str = "",
) -> Sheet

Close any existing text editor

close_text_editor(set_data: bool = True) -> Sheet

Notes:

Parameters:


Get an existing text editors value

get_text_editor_value() -> str | None

Notes:


Hide all text editors

destroy_text_editor(event: object = None) -> Sheet

Get the table tk Text widget which acts as the text editor

get_text_editor_widget(event: object = None) -> tk.Text | None

Bind additional keys to the text editor window

bind_key_text_editor(key: str, function: Callable) -> Sheet

Unbind additional text editors bindings set using the above function

unbind_key_text_editor(key: str) -> Sheet

Sheet Options and Other Functions

set_options(redraw: bool = True, **kwargs) -> Sheet

The list of key word arguments available for set_options() are as follows, see here as a guide for argument types.

name

show_top_left

sort_key
edit_cell_tab
edit_cell_return
editor_del_key
auto_resize_columns
auto_resize_rows
to_clipboard_delimiter
to_clipboard_quotechar
to_clipboard_lineterminator
from_clipboard_delimiters
show_dropdown_borders
show_default_header_for_empty
show_default_index_for_empty
selected_rows_to_end_of_window
horizontal_grid_to_end_of_window
vertical_grid_to_end_of_window
paste_insert_column_limit
paste_insert_row_limit
paste_can_expand_x
paste_can_expand_y
arrow_key_down_right_scroll_page
enable_edit_cell_auto_resize
page_up_down_select_row
display_selected_fg_over_highlights
show_horizontal_grid
show_vertical_grid
empty_horizontal
empty_vertical
default_row_height
default_column_width
default_header_height
default_row_index_width
row_drag_and_drop_perform
column_drag_and_drop_perform
auto_resize_default_row_index
default_header
default_row_index
min_column_width
max_column_width
max_row_height
max_header_height
max_index_width
font
header_font
index_font

show_selected_cells_border
theme
top_left_bg
top_left_fg
top_left_fg_highlight

table_bg
table_grid_fg
table_fg
table_selected_box_cells_fg
table_selected_box_rows_fg
table_selected_box_columns_fg
table_selected_cells_border_fg
table_selected_cells_bg
table_selected_cells_fg
table_selected_rows_border_fg
table_selected_rows_bg
table_selected_rows_fg
table_selected_columns_border_fg
table_selected_columns_bg
table_selected_columns_fg

header_bg
header_border_fg
header_grid_fg
header_fg
header_selected_cells_bg
header_selected_cells_fg
header_selected_columns_bg
header_selected_columns_fg

index_bg
index_border_fg
index_grid_fg
index_fg
index_selected_cells_bg
index_selected_cells_fg
index_selected_rows_bg
index_selected_rows_fg

resizing_line_fg
drag_and_drop_bg
outline_thickness
outline_color
frame_bg
popup_menu_font
popup_menu_fg
popup_menu_bg
popup_menu_highlight_bg
popup_menu_highlight_fg

# scroll bars
vertical_scroll_background
horizontal_scroll_background
vertical_scroll_troughcolor
horizontal_scroll_troughcolor
vertical_scroll_lightcolor
horizontal_scroll_lightcolor
vertical_scroll_darkcolor
horizontal_scroll_darkcolor
vertical_scroll_bordercolor
horizontal_scroll_bordercolor
vertical_scroll_active_bg
horizontal_scroll_active_bg
vertical_scroll_not_active_bg
horizontal_scroll_not_active_bg
vertical_scroll_pressed_bg
horizontal_scroll_pressed_bg
vertical_scroll_active_fg
horizontal_scroll_active_fg
vertical_scroll_not_active_fg
horizontal_scroll_not_active_fg
vertical_scroll_pressed_fg
horizontal_scroll_pressed_fg
scrollbar_theme_inheritance
scrollbar_show_arrows
vertical_scroll_arrowsize
horizontal_scroll_arrowsize
vertical_scroll_borderwidth
horizontal_scroll_borderwidth
vertical_scroll_gripcount
horizontal_scroll_gripcount

# for changing the in-built right click menus labels
# use a string as an argument

sort_cells_label
sort_cells_x_label
sort_row_label
sort_column_label
sort_rows_label
sort_columns_label
sort_cells_reverse_label
sort_cells_x_reverse_label
sort_row_reverse_label
sort_column_reverse_label
sort_rows_reverse_label
sort_columns_reverse_label
sort_cells_accelerator
sort_cells_x_accelerator
sort_row_accelerator
sort_column_accelerator
sort_rows_accelerator
sort_columns_accelerator
sort_cells_reverse_accelerator
sort_cells_x_reverse_accelerator
sort_row_reverse_accelerator
sort_column_reverse_accelerator
sort_rows_reverse_accelerator
sort_columns_reverse_accelerator

edit_header_label
edit_header_accelerator
edit_index_label
edit_index_accelerator
edit_cell_label
edit_cell_accelerator
cut_label
cut_accelerator
cut_contents_label
cut_contents_accelerator
copy_label
copy_accelerator
copy_contents_label
copy_contents_accelerator
paste_label
paste_accelerator
delete_label
delete_accelerator
clear_contents_label
clear_contents_accelerator
delete_columns_label
delete_columns_accelerator
insert_columns_left_label
insert_columns_left_accelerator
insert_column_label
insert_column_accelerator
insert_columns_right_label
insert_columns_right_accelerator
delete_rows_label
delete_rows_accelerator
insert_rows_above_label
insert_rows_above_accelerator
insert_rows_below_label
insert_rows_below_accelerator
insert_row_label
insert_row_accelerator
select_all_label
select_all_accelerator
undo_label
undo_accelerator

# for changing the keyboard bindings for copy, paste, etc.
# use a list of strings as an argument
copy_bindings
cut_bindings
paste_bindings
undo_bindings
redo_bindings
delete_bindings
select_all_bindings
select_columns_bindings
select_rows_bindings
row_start_bindings
table_start_bindings
tab_bindings
up_bindings
right_bindings
down_bindings
left_bindings
shift_up_bindings
shift_right_bindings
shift_down_bindings
shift_left_bindings
prior_bindings
next_bindings

Notes:

kwargs = {
    "copy_bindings": [
        "<Control-g>",
        "<Control-G>",
    ],
    "cut_bindings": [
        "<Control-c>",
        "<Control-C>",
    ],
}
sheet.set_options(**kwargs)

Get internal storage dictionary of highlights, readonly cells, dropdowns etc. Specifically for cell options.

get_cell_options(key: None | str = None, canvas: Literal["table", "row_index", "header"] = "table") -> dict

Get internal storage dictionary of highlights, readonly rows, dropdowns etc. Specifically for row options.

get_row_options(key: None | str = None) -> dict

Get internal storage dictionary of highlights, readonly columns, dropdowns etc. Specifically for column options.

get_column_options(key: None | str = None) -> dict

Get internal storage dictionary of highlights, readonly header cells, dropdowns etc. Specifically for header options.

get_header_options(key: None | str = None) -> dict

Get internal storage dictionary of highlights, readonly row index cells, dropdowns etc. Specifically for row index options.

get_index_options(key: None | str = None) -> dict

Delete any formats, alignments, dropdown boxes, check boxes, highlights etc. that are larger than the sheets currently held data, includes row index and header in measurement of dimensions.

del_out_of_bounds_options() -> Sheet

Delete all alignments, dropdown boxes, check boxes, highlights etc.

reset_all_options() -> Sheet

Flash a dashed box of chosen dimensions.

show_ctrl_outline(
    canvas: Literal["table"] = "table",
    start_cell: tuple[int, int] = (0, 0),
    end_cell: tuple[int, int] = (1, 1),
) -> Sheet

Various functions related to the Sheets internal undo and redo stacks.

# clears both undos and redos
reset_undos() -> Sheet

# get the Sheets modifiable deque variables which store changes for undo and redo
get_undo_stack() -> deque
get_redo_stack() -> deque

# set the Sheets undo and redo stacks, returns Sheet widget
set_undo_stack(stack: deque) -> Sheet
set_redo_stack(stack: deque) -> Sheet

Refresh the table.

refresh(redraw_header: bool = True, redraw_row_index: bool = True) -> Sheet

Refresh the table.

redraw(redraw_header: bool = True, redraw_row_index: bool = True) -> Sheet

Treeview Mode

tksheet has a treeview mode which behaves similarly to the ttk treeview widget, it is not a drop in replacement for it though.

Always either use a fresh Sheet() instance or use Sheet.reset() before enabling treeview mode.

TO NOTE:

Creating a treeview mode sheet

You can make a treeview mode sheet by using the initialization parameter treeview:

sheet = Sheet(parent, treeview=True)

Or by using Sheet.reset() and Sheet.set_options().

my_sheet.reset()
my_sheet.set_options(treeview=True)

See the other sections on sheet initialization and examples for the other usual Sheet() parameters.

Treeview mode functions

Functions designed for use with treeview mode.

Insert an item

insert(
    parent: str = "",
    index: None | int | Literal["end"] = None,
    iid: None | str = None,
    text: None | str = None,
    values: None | list[object] = None,
    create_selections: bool = False,
    undo: bool = True,
) -> str

Parameters:

Notes:

Example:

sheet.insert(
    iid="top level",
    text="Top level",
    values=["cell A1", "cell B1"],
)
sheet.insert(
    parent="top level",
    iid="mid level",
    text="Mid level",
    values=["cell A2", "cell B2"],
)

Insert multiple items

bulk_insert(
    data: list[list[object]],
    parent: str = "",
    index: None | int | Literal["end"] = None,
    iid_column: int | None = None,
    text_column: int | None | str = None,
    create_selections: bool = False,
    include_iid_column: bool = True,
    include_text_column: bool = True,
    undo: bool = True,
) -> dict[str, int]

Parameters:

Notes:

Example:

sheet.insert(iid="ID-1", text="ID-1", values=["ID 1 Value"])
sheet.bulk_insert(
    data=[
        ["CID-1", "CID 1 Value"],
        ["CID-2", "CID 2 Value"],
    ],
    parent="ID-1",
    iid_column=0,
    text_column=0,
    include_iid_column=False,
    include_text_column=False,
)

Build a tree from data

This takes a list of lists where sublists are rows and a few arguments to bulk insert items into the treeview. Note that:

tree_build(
    data: list[list[object]],
    iid_column: int,
    parent_column: int,
    text_column: None | int = None,
    push_ops: bool = False,
    row_heights: Sequence[int] | None | False = None,
    open_ids: Iterator[str] | None = None,
    safety: bool = True,
    ncols: int | None = None,
    lower: bool = False,
    include_iid_column: bool = True,
    include_parent_column: bool = True,
    include_text_column: bool = True,
) -> Sheet

Parameters:

Notes:

Example:

data = [
    ["id1", "", "id1 val"],
    ["id2", "id1", "id2 val"],
]
sheet.tree_build(
    data=data,
    iid_column=0,
    parent_column=1,
    include_iid_column=False,
    include_parent_column=False,
)

Reset the treeview

tree_reset() -> Sheet

Get treeview iids that are open

tree_get_open() -> set[str]

Set the open treeview iids

tree_set_open(open_ids: Iterator[str]) -> Sheet

Open treeview iids

tree_open(*items, redraw: bool = True) -> Sheet

Close treeview iids

tree_close(*items, redraw: bool = True) -> Sheet

Set or get an iids attributes

item(
    item: str,
    iid: str | None = None,
    text: str | None = None,
    values: list | None = None,
    open_: bool | None = None,
    undo: bool = True,
    emit_event: bool = True,
    redraw: bool = True,
) -> DotDict | Sheet

Parameters:

Notes: - If no arguments are given a DotDict is returned with the item attributes.

{
    "text": ...,
    "values": ...,
    "open_": ...,
}

Get an iids row number

itemrow(item: str) -> int

Get a row numbers item

rowitem(row: int, data_index: bool = False) -> str | None

Get treeview children

get_children(item: None | str = None) -> Generator[str]
tree_traverse(item: None | str = None) -> Generator[str]:

Get item descendants

descendants(item: str, check_open: bool = False) -> Generator[str]:

Get the currently selected treeview item

@property
tree_selected() -> str | None:

Delete treeview items

del_items(*items) -> Sheet

Move items to a new parent

set_children(parent: str, *newchildren) -> Sheet

Move an item to a new parent

move(
    item: str,
    parent: str,
    index: int | None = None,
    select: bool = True,
    undo: bool = True,
    emit_event: bool = False,
) -> tuple[dict[int, int], dict[int, int], EventDataDict]

Parameters:

Notes:


Check an item exists

exists(item: str) -> bool

Get an items parent

parent(item: str) -> str

Get an items index

index(item: str) -> int

Check if an item is currently displayed

item_displayed(item: str) -> bool

Display an item

Make sure an items parents are all open, does not scroll to the item.

display_item(item: str, redraw: bool = False) -> Sheet

Scroll to an item

scroll_to_item(item: str, redraw: bool = False) -> Sheet

Get currently selected items

selection(cells: bool = False) -> list[str]

Notes: - Returns a list of selected iids (selected rows but as iids).

Parameters: - cells when True any selected cells will also qualify as selected items.


Set selected items

selection_set(*items, run_binding: bool = True, redraw: bool = True) -> Sheet

Add selected items

selection_add(*items, run_binding: bool = True, redraw: bool = True) -> Sheet

Remove selections

selection_remove(*items, redraw: bool = True) -> Sheet

Toggle selections

selection_toggle(*items, redraw: bool = True) -> Sheet

Progress Bars

Progress bars can be created for individual cells. They will only update when tkinter updates.

Create a progress bar

create_progress_bar(
    row: int,
    column: int,
    bg: str,
    fg: str,
    name: Hashable,
    percent: int = 0,
    del_when_done: bool = False,
) -> Sheet

Modify progress bars

progress_bar(
    name: Hashable | None = None,
    cell: tuple[int, int] | None = None,
    percent: int | None = None,
    bg: str | None = None,
    fg: str | None = None,
) -> Sheet

Either name or cell can be used to refer to existing progress bars:

Values that can be modified:


Delete progress bars

Note that this will delete the progress bars data from the Sheet as well.

del_progress_bar(
    name: Hashable | None = None,
    cell: tuple[int, int] | None = None,
) -> Sheet

Either name or cell can be used to refer to existing progress bars:


Tags

Tags can be used to keep track of specific cells, rows and columns wherever they move. Note that:

Tag a specific cell

tag_cell(
    cell: tuple[int, int],
    *tags,
) -> Sheet

Example:

sheet.tag_cell((0, 0), "tag a1", "tag a1 no.2")

Tag specific rows

tag_rows(
    rows: int | Iterator[int],
    *tags,
) -> Sheet

Tag specific columns

tag_columns(
    columns: int | Iterator[int],
    *tags,
) -> Sheet

Tag using a span

tag(
    *key: CreateSpanTypes,
    tags: Iterator[str] | str = "",
) -> Sheet

Untag

untag(
    cell: tuple[int, int] | None = None,
    rows: int | Iterator[int] | None = None,
    columns: int | Iterator[int] | None = None,
) -> Sheet

Delete tags

tag_del(
    *tags,
    cells: bool = True,
    rows: bool = True,
    columns: bool = True,
) -> Sheet

Get cells, rows or columns associated with tags

tag_has(
    *tags,
) -> DotDict

Notes:

Example:

sheet.tag_rows((0, 1), "row tag a", "row tag b")
sheet.tag_rows(4, "row tag b")
sheet.tag_rows(5, "row tag c")
sheet.tag_rows(6, "row tag d")
with_tags = sheet.tag_has("row tag b", "row tag c")

print (with_tags.rows)
# prints {0, 1, 4, 5}

Example Loading Data from Excel

Using pandas library, requires additional libraries: - pandas - openpyxl

from tksheet import Sheet
import tkinter as tk
import pandas as pd


class demo(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.grid_columnconfigure(0, weight = 1)
        self.grid_rowconfigure(0, weight = 1)
        self.frame = tk.Frame(self)
        self.frame.grid_columnconfigure(0, weight = 1)
        self.frame.grid_rowconfigure(0, weight = 1)
        self.sheet = Sheet(self.frame,
                           data = pd.read_excel("excel_file.xlsx",      # filepath here
                                                #sheet_name = "sheet1", # optional sheet name here
                                                engine = "openpyxl",
                                                header = None).values.tolist())
        self.sheet.enable_bindings()
        self.frame.grid(row = 0, column = 0, sticky = "nswe")
        self.sheet.grid(row = 0, column = 0, sticky = "nswe")


app = demo()
app.mainloop()

Example Custom Right Click and Text Editor Validation

This is to demonstrate:

from tksheet import Sheet
import tkinter as tk


class demo(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)
        self.frame = tk.Frame(self)
        self.frame.grid_columnconfigure(0, weight=1)
        self.frame.grid_rowconfigure(0, weight=1)
        self.sheet = Sheet(self.frame,
                           data=[[f"Row {r}, Column {c}\nnewline1\nnewline2" for c in range(50)] for r in range(500)])
        self.sheet.enable_bindings(
            "single_select",
            "drag_select",
            "edit_cell",
            "paste",
            "cut",
            "copy",
            "delete",
            "select_all",
            "column_select",
            "row_select",
            "column_width_resize",
            "double_click_column_resize",
            "arrowkeys",
            "row_height_resize",
            "double_click_row_resize",
            "right_click_popup_menu",
            "rc_select",
        )
        self.sheet.extra_bindings("begin_edit_cell", self.begin_edit_cell)
        self.sheet.edit_validation(self.validate_edits)
        self.sheet.popup_menu_add_command("Say Hello", self.new_right_click_button)
        self.frame.grid(row=0, column=0, sticky="nswe")
        self.sheet.grid(row=0, column=0, sticky="nswe")

    def new_right_click_button(self, event=None):
        print ("Hello World!")

    def begin_edit_cell(self, event=None):
        return event.value

    def validate_edits(self, event):
        # remove spaces from any cell edits, including paste
        if isinstance(event.value, str) and event.value:
            return event.value.replace(" ", "")


app = demo()
app.mainloop()

Example Displaying Selections

from tksheet import (
    Sheet,
    num2alpha,
)
import tkinter as tk


class demo(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.grid_columnconfigure(0, weight = 1)
        self.grid_rowconfigure(0, weight = 1)
        self.frame = tk.Frame(self)
        self.frame.grid_columnconfigure(0, weight = 1)
        self.frame.grid_rowconfigure(0, weight = 1)
        self.sheet = Sheet(self.frame,
                           data = [[f"Row {r}, Column {c}\nnewline1\nnewline2" for c in range(50)] for r in range(500)])
        self.sheet.enable_bindings("all", "ctrl_select")
        self.sheet.extra_bindings([("all_select_events", self.sheet_select_event)])
        self.show_selections = tk.Label(self)
        self.frame.grid(row = 0, column = 0, sticky = "nswe")
        self.sheet.grid(row = 0, column = 0, sticky = "nswe")
        self.show_selections.grid(row = 1, column = 0, sticky = "nsw")

    def sheet_select_event(self, event = None):
        if event.eventname == "select" and event.selection_boxes and event.selected:
            # get the most recently selected box in case there are multiple
            box = next(reversed(event.selection_boxes))
            type_ = event.selection_boxes[box]
            if type_ == "cells":
                self.show_selections.config(text=f"{type_.capitalize()}: {box.from_r + 1},{box.from_c + 1} : {box.upto_r},{box.upto_c}")
            elif type_ == "rows":
                self.show_selections.config(text=f"{type_.capitalize()}: {box.from_r + 1} : {box.upto_r}")
            elif type_ == "columns":
                self.show_selections.config(text=f"{type_.capitalize()}: {num2alpha(box.from_c)} : {num2alpha(box.upto_c - 1)}")
        else:
            self.show_selections.config(text="")


app = demo()
app.mainloop()

Example List Box

This is to demonstrate some simple customization to make a different sort of widget (a list box).

from tksheet import Sheet
import tkinter as tk

class Sheet_Listbox(Sheet):
    def __init__(self,
                 parent,
                 values = []):
        Sheet.__init__(self,
                       parent = parent,
                       show_horizontal_grid = False,
                       show_vertical_grid = False,
                       show_header = False,
                       show_row_index = False,
                       show_top_left = False,
                       empty_horizontal = 0,
                       empty_vertical = 0)
        if values:
            self.values(values)

    def values(self, values = []):
        self.set_sheet_data([[v] for v in values],
                            reset_col_positions = False,
                            reset_row_positions = False,
                            redraw = False,
                            verify = False)
        self.set_all_cell_sizes_to_text()


class demo(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.grid_columnconfigure(0,
                                  weight = 1)
        self.grid_rowconfigure(0,
                               weight = 1)
        self.listbox = Sheet_Listbox(self,
                                     values = [f"_________  Item {i}  _________" for i in range(2000)])
        self.listbox.grid(row = 0,
                          column = 0,
                          sticky = "nswe")
        #self.listbox.values([f"new values {i}" for i in range(50)]) set values


app = demo()
app.mainloop()

Example Header Dropdown Boxes and Row Filtering

A very simple demonstration of row filtering using header dropdown boxes.

from tksheet import (
    Sheet,
    num2alpha as n2a,
)
import tkinter as tk


class demo(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)
        self.frame = tk.Frame(self)
        self.frame.grid_columnconfigure(0, weight=1)
        self.frame.grid_rowconfigure(0, weight=1)
        self.data = [
            ["3", "c", "z"],
            ["1", "a", "x"],
            ["1", "b", "y"],
            ["2", "b", "y"],
            ["2", "c", "z"],
        ]
        self.sheet = Sheet(
            self.frame,
            data=self.data,
            column_width=180,
            theme="dark",
            height=700,
            width=1100,
        )
        self.sheet.enable_bindings(
            "copy",
            "rc_select",
            "arrowkeys",
            "double_click_column_resize",
            "column_width_resize",
            "column_select",
            "row_select",
            "drag_select",
            "single_select",
            "select_all",
        )
        self.frame.grid(row=0, column=0, sticky="nswe")
        self.sheet.grid(row=0, column=0, sticky="nswe")

        self.sheet.dropdown(
            self.sheet.span(n2a(0), header=True, table=False),
            values=["all", "1", "2", "3"],
            set_value="all",
            selection_function=self.header_dropdown_selected,
            text="Header A Name",
        )
        self.sheet.dropdown(
            self.sheet.span(n2a(1), header=True, table=False),
            values=["all", "a", "b", "c"],
            set_value="all",
            selection_function=self.header_dropdown_selected,
            text="Header B Name",
        )
        self.sheet.dropdown(
            self.sheet.span(n2a(2), header=True, table=False),
            values=["all", "x", "y", "z"],
            set_value="all",
            selection_function=self.header_dropdown_selected,
            text="Header C Name",
        )

    def header_dropdown_selected(self, event=None):
        hdrs = self.sheet.headers()
        # this function is run before header cell data is set by dropdown selection
        # so we have to get the new value from the event
        hdrs[event.loc] = event.value
        if all(dd == "all" for dd in hdrs):
            self.sheet.display_rows("all")
        else:
            rows = [
                rn for rn, row in enumerate(self.data) if all(row[c] == e or e == "all" for c, e in enumerate(hdrs))
            ]
            self.sheet.display_rows(rows=rows, all_displayed=False)
        self.sheet.redraw()


app = demo()
app.mainloop()

Example Readme Screenshot Code

The code used to make a screenshot for the readme file.

from tksheet import (
    Sheet,
    num2alpha as n2a,
)
import tkinter as tk


class demo(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)
        self.frame = tk.Frame(self)
        self.frame.grid_columnconfigure(0, weight=1)
        self.frame.grid_rowconfigure(0, weight=1)
        self.sheet = Sheet(
            self.frame,
            empty_horizontal=0,
            empty_vertical=0,
            paste_can_expand_x=True,
            paste_can_expand_y=True,
            align="w",
            header_align="c",
            data=[[f"Row {r}, Column {c}\nnewline 1\nnewline 2" for c in range(6)] for r in range(21)],
            headers=[
                "Dropdown Column",
                "Checkbox Column",
                "Center Aligned Column",
                "East Aligned Column",
                "",
                "",
            ],
            theme="dark",
            height=520,
            width=930,
        )
        self.sheet.enable_bindings("all", "edit_index", "edit_header")
        self.sheet.popup_menu_add_command(
            "Hide Rows",
            self.hide_rows,
            table_menu=False,
            header_menu=False,
            empty_space_menu=False,
        )
        self.sheet.popup_menu_add_command(
            "Show All Rows",
            self.show_rows,
            table_menu=False,
            header_menu=False,
            empty_space_menu=False,
        )
        self.sheet.popup_menu_add_command(
            "Hide Columns",
            self.hide_columns,
            table_menu=False,
            index_menu=False,
            empty_space_menu=False,
        )
        self.sheet.popup_menu_add_command(
            "Show All Columns",
            self.show_columns,
            table_menu=False,
            index_menu=False,
            empty_space_menu=False,
        )
        self.frame.grid(row=0, column=0, sticky="nswe")
        self.sheet.grid(row=0, column=0, sticky="nswe")
        colors = (
            "#509f56",
            "#64a85b",
            "#78b160",
            "#8cba66",
            "#a0c36c",
            "#b4cc71",
            "#c8d576",
            "#dcde7c",
            "#f0e782",
            "#ffec87",
            "#ffe182",
            "#ffdc7d",
            "#ffd77b",
            "#ffc873",
            "#ffb469",
            "#fea05f",
            "#fc8c55",
            "#fb784b",
            "#fa6441",
            "#f85037",
        )
        self.sheet.align_columns(columns=2, align="c")
        self.sheet.align_columns(columns=3, align="e")
        self.sheet.create_index_dropdown(r=0, values=["Dropdown"] + [f"{i}" for i in range(15)])
        self.sheet.create_index_checkbox(r=3, checked=True, text="Checkbox")
        self.sheet.create_dropdown(r="all", c=0, values=["Dropdown"] + [f"{i}" for i in range(15)])
        self.sheet.create_checkbox(r="all", c=1, checked=True, text="Checkbox")
        self.sheet.create_header_dropdown(c=0, values=["Header Dropdown"] + [f"{i}" for i in range(15)])
        self.sheet.create_header_checkbox(c=1, checked=True, text="Header Checkbox")
        self.sheet.align_cells(5, 0, align="c")
        self.sheet.highlight_cells(5, 0, bg="gray50", fg="blue")
        self.sheet.highlight_cells(17, canvas="index", bg="yellow", fg="black")
        self.sheet.highlight_cells(12, 1, bg="gray90", fg="purple")
        for r in range(len(colors)):
            self.sheet.highlight_cells(row=r, column=3, fg=colors[r])
            self.sheet.highlight_cells(row=r, column=4, bg=colors[r], fg="black")
            self.sheet.highlight_cells(row=r, column=5, bg=colors[r], fg="purple")
        self.sheet.highlight_cells(column=5, canvas="header", bg="white", fg="purple")
        self.sheet.align(n2a(2), align="c")
        self.sheet.align(n2a(3), align="e")
        self.sheet.dropdown(
            self.sheet.span("A", header=True),
            values=["Dropdown"] + [f"{i}" for i in range(15)],
        )
        self.sheet.checkbox(
            self.sheet.span("B", header=True),
            checked=True,
            text="Checkbox",
        )
        self.sheet.align(5, 0, align="c")
        self.sheet.highlight(5, 0, bg="gray50", fg="blue")
        self.sheet.highlight(
            self.sheet.span(17, index=True, table=False),
            bg="yellow",
            fg="black",
        )
        self.sheet.highlight(12, 1, bg="gray90", fg="purple")
        for r in range(len(colors)):
            self.sheet.highlight(r, 3, fg=colors[r])
            self.sheet.highlight(r, 4, bg=colors[r], fg="black")
            self.sheet.highlight(r, 5, bg=colors[r], fg="purple")
        self.sheet.highlight(
            self.sheet.span(n2a(5), header=True, table=False),
            bg="white",
            fg="purple",
        )
        self.sheet.set_all_column_widths()
        self.sheet.extra_bindings("all", self.all_extra_bindings)

    def hide_rows(self, event=None):
        rows = self.sheet.get_selected_rows()
        if rows:
            self.sheet.hide_rows(rows)

    def show_rows(self, event=None):
        self.sheet.display_rows("all", redraw=True)

    def hide_columns(self, event=None):
        columns = self.sheet.get_selected_columns()
        if columns:
            self.sheet.hide_columns(columns)

    def show_columns(self, event=None):
        self.sheet.display_columns("all", redraw=True)

    def all_extra_bindings(self, event=None):
        return event.value


app = demo()
app.mainloop()

Example Saving tksheet as a csv File

To both load a csv file and save tksheet data as a csv file not including headers and index.

from tksheet import Sheet
import tkinter as tk
from tkinter import filedialog
import csv
from os.path import normpath
import io


class demo(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.withdraw()
        self.title("tksheet")
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)
        self.frame = tk.Frame(self)
        self.frame.grid_columnconfigure(0, weight=1)
        self.frame.grid_rowconfigure(0, weight=1)
        self.sheet = Sheet(self.frame, data=[[f"Row {r}, Column {c}" for c in range(6)] for r in range(21)])
        self.sheet.enable_bindings("all", "edit_header", "edit_index")
        self.frame.grid(row=0, column=0, sticky="nswe")
        self.sheet.grid(row=0, column=0, sticky="nswe")
        self.sheet.popup_menu_add_command("Open csv", self.open_csv)
        self.sheet.popup_menu_add_command("Save sheet", self.save_sheet)
        self.sheet.set_all_cell_sizes_to_text()
        self.sheet.change_theme("light green")

        # create a span which encompasses the table, header and index
        # all data values, no displayed values
        self.sheet_span = self.sheet.span(
            header=True,
            index=True,
            hdisp=False,
            idisp=False,
        )

        # center the window and unhide
        self.update_idletasks()
        w = self.winfo_screenwidth() - 20
        h = self.winfo_screenheight() - 70
        size = (900, 500)
        self.geometry("%dx%d+%d+%d" % (size + ((w / 2 - size[0] / 2), h / 2 - size[1] / 2)))
        self.deiconify()

    def save_sheet(self):
        filepath = filedialog.asksaveasfilename(
            parent=self,
            title="Save sheet as",
            filetypes=[("CSV File", ".csv"), ("TSV File", ".tsv")],
            defaultextension=".csv",
            confirmoverwrite=True,
        )
        if not filepath or not filepath.lower().endswith((".csv", ".tsv")):
            return
        try:
            with open(normpath(filepath), "w", newline="", encoding="utf-8") as fh:
                writer = csv.writer(
                    fh,
                    dialect=csv.excel if filepath.lower().endswith(".csv") else csv.excel_tab,
                    lineterminator="\n",
                )
                writer.writerows(self.sheet_span.data)
        except Exception as error:
            print(error)
            return

    def open_csv(self):
        filepath = filedialog.askopenfilename(parent=self, title="Select a csv file")
        if not filepath or not filepath.lower().endswith((".csv", ".tsv")):
            return
        try:
            with open(normpath(filepath), "r") as filehandle:
                filedata = filehandle.read()
            self.sheet.reset()
            self.sheet_span.data = [
                r
                for r in csv.reader(
                    io.StringIO(filedata),
                    dialect=csv.Sniffer().sniff(filedata),
                    skipinitialspace=False,
                )
            ]
        except Exception as error:
            print(error)
            return


app = demo()
app.mainloop()


Example Using and Creating Formatters

from tksheet import (
    Sheet,
    formatter,
    float_formatter,
    int_formatter,
    percentage_formatter,
    bool_formatter,
    truthy,
    falsy,
    num2alpha,
)
import tkinter as tk
from datetime import datetime, date
from dateutil import parser, tz
from math import ceil
import re

date_replace = re.compile("|".join(re.escape(char) for char in "()[]<>"))


# Custom formatter methods
def round_up(x):
    try:  # might not be a number if empty
        return float(ceil(x))
    except Exception:
        return x


def only_numeric(s):
    return "".join(n for n in f"{s}" if n.isnumeric() or n == ".")


def convert_to_local_datetime(dt: str, **kwargs):
    if isinstance(dt, datetime):
        pass
    elif isinstance(dt, date):
        dt = datetime(dt.year, dt.month, dt.day)
    else:
        if isinstance(dt, str):
            dt = date_replace.sub("", dt)
        try:
            dt = parser.parse(dt)
        except Exception:
            raise ValueError(f"Could not parse {dt} as a datetime")
    if dt.tzinfo is None:
        dt.replace(tzinfo=tz.tzlocal())
    dt = dt.astimezone(tz.tzlocal())
    return dt.replace(tzinfo=None)


def datetime_to_string(dt: datetime, **kwargs):
    return dt.strftime("%d %b, %Y, %H:%M:%S")


# Custom Formatter with additional kwargs


def custom_datetime_to_str(dt: datetime, **kwargs):
    return dt.strftime(kwargs["format"])


class demo(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)
        self.frame = tk.Frame(self)
        self.frame.grid_columnconfigure(0, weight=1)
        self.frame.grid_rowconfigure(0, weight=1)
        self.sheet = Sheet(self.frame, empty_vertical=0, empty_horizontal=0, data=[[f"{r}"] * 11 for r in range(20)])
        self.sheet.enable_bindings()
        self.frame.grid(row=0, column=0, sticky="nswe")
        self.sheet.grid(row=0, column=0, sticky="nswe")
        self.sheet.headers(
            [
                "Non-Nullable Float Cell\n1 decimals places",
                "Float Cell",
                "Int Cell",
                "Bool Cell",
                "Percentage Cell\n0 decimal places",
                "Custom Datetime Cell",
                "Custom Datetime Cell\nCustom Format String",
                "Float Cell that\nrounds up",
                "Float cell that\n strips non-numeric",
                "Dropdown Over Nullable\nPercentage Cell",
                "Percentage Cell\n2 decimal places",
            ]
        )

        # num2alpha converts column integer to letter

        # Some examples of data formatting
        self.sheet[num2alpha(0)].format(float_formatter(nullable=False))
        self.sheet[num2alpha(1)].format(float_formatter())
        self.sheet[num2alpha(2)].format(int_formatter())
        self.sheet[num2alpha(3)].format(bool_formatter(truthy=truthy | {"nah yeah"}, falsy=falsy | {"yeah nah"}))
        self.sheet[num2alpha(4)].format(percentage_formatter())

        # Custom Formatters
        # Custom using generic formatter interface
        self.sheet[num2alpha(5)].format(
            formatter(
                datatypes=datetime,
                format_function=convert_to_local_datetime,
                to_str_function=datetime_to_string,
                nullable=False,
                invalid_value="NaT",
            )
        )

        # Custom format
        self.sheet[num2alpha(6)].format(
            datatypes=datetime,
            format_function=convert_to_local_datetime,
            to_str_function=custom_datetime_to_str,
            nullable=True,
            invalid_value="NaT",
            format="(%Y-%m-%d) %H:%M %p",
        )

        # Unique cell behaviour using the post_conversion_function
        self.sheet[num2alpha(7)].format(float_formatter(post_format_function=round_up))
        self.sheet[num2alpha(8)].format(float_formatter(), pre_format_function=only_numeric)
        self.sheet[num2alpha(9)].dropdown(values=["", "104%", 0.24, "300%", "not a number"], set_value=1,)
        self.sheet[num2alpha(9)].format(percentage_formatter(), decimals=0)
        self.sheet[num2alpha(10)].format(percentage_formatter(decimals=5))


app = demo()
app.mainloop()

Contributing

Welcome and thank you for your interest in tksheet!

tksheet Goals

Dependencies

tksheet is designed to only use built-in Python libraries (without third-party dependencies). Please ensure that your contributions do not introduce any new dependencies outside of Python's built-in libraries.

License

tksheet is released under the MIT License. You can find the full text of the license here.

By contributing to the tksheet project, you agree to license your contributions under the same MIT License. Please make sure to read and understand the terms and conditions of the license before contributing.

Contributing Code

To contribute, please follow these steps:

  1. Fork the tksheet repository.
  2. If you are working on a new feature, create a new branch for your contribution. Use a descriptive name for the branch that reflects the feature you're working on.
  3. Make your changes in your local branch, following the code style and conventions established in the project.
  4. Test your changes thoroughly to ensure they do not introduce any new bugs or issues.
  5. Submit a pull request to the main branch of the tksheet repository, including a clear title and detailed description of your changes. Pull requests ideally should include a small but comprehensive demonstration of the feature you are adding.
  6. Don't forget to update the documentation!

Note: If you're submitting a bugfix, it's generally preferred to submit it directly to the relevant branch, rather than creating a separate branch.

Asking Questions

Got a question that hasn't been answered in the closed issues or is missing from the documentation? please follow these guidelines:

Issues

Please use the Issues tab to report any issues or ask for assistance.

When submitting an issue, please follow these guidelines:

Enhancements or Suggestions

If you have an idea for a new feature, improvement or change, please follow these guidelines:

Contributors and Special Thanks

A special thank you to: