'Setting individual column headers in Python Tkinter's Tksheet widget / rolling back changes in same

I am writing a UI for a simulation program which accepts tabular data. The basic functionality I need is for the user to be able to enter / change data in cells either by directly typing into them, or by pasting data (usually from an excel sheet). The program checks this data and either accepts or rejects it before running the simulation. I also want to let the user type in their own column headers for the table.

Tksheet is an awesome Tkinter add-on, giving an excel-like "feel" to the input frame, but its documentation leaves much to be desired. (For instance: each event generates a different event-information array--see code for two event-processing routines below--but nowhere is it specified what these parameters are. It is left for the user to discover using trial and error, or trying to read the source code--which is not documented either).

I have two specific questions:

  1. Is there any way to not-commit, or to roll back, changes to the table? If my data-tests fail, how do I prevent potentially harmful user input from being entered into the table? Obviously I can (and do) add a begin_*** event in which I can keep copies of the original values, and then reset the table values if the data testing at the end_*** event fails, but this is wasteful and inelegant. I have a feeling that the set_data_ref_on_destroy property has something to do with such a capability, but the documentation does not explain what this parameter is or how to use it.

  2. How can I change a single column header at a time? The .headers property seems to work only with a full list of headers starting with column 0 (if I run self.sheet.headers([single_value], index = i) it ignores the index parameter and plugs single_value in column 0) Again, I can set the column headers to something non-default at init and keep a running list of all headers, so that I can reset all the headers on each change, but this is wasteful and inelegant.

In the following code sample I set up a simple table, and bind three user-generated events: one for typing a value to a cell, one for pasting a block of values, and one for adding an option to the right-click menu of a column header, to allow the user to type a name to the column.

from tksheet import Sheet
import tkinter as tk
import tkinter.messagebox as msg
import tkinter.simpledialog as sd

class demo(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.grid_columnconfigure(0, weight=1)  # This configures the window's escalators
        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.frame.grid(row=0, column=0, sticky="nswe")
        self.sheet = Sheet(self.frame, data=[[]])  # set up empty table
        self.sheet.grid(row=0, column=0, sticky="nswe")
        self.sheet.enable_bindings(bindings=  # enable table behavior
                                   ("single_select",
                                    "select_all",
                                    "column_select",
                                    "row_select",
                                    "drag_select",
                                    "arrowkeys",
                                    "column_width_resize",
                                    "double_click_column_resize",
                                    "row_height_resize",
                                    "double_click_row_resize",
                                    "right_click_popup_menu",
                                    "rc_select",  # rc = right click
                                    "copy",
                                    "cut",
                                    "paste",
                                    "delete",
                                    "undo",
                                    "edit_cell"
                                    ))
        # Note that options that change the structure/size of the table (e.g. insert/delete col/row) are disabled

        # make sure that pasting data won't change table size
        self.sheet.set_options(expand_sheet_if_paste_too_big=False)
        # bind specific events to my own functions
        self.sheet.extra_bindings("end_edit_cell", func=self.cell_edited)
        self.sheet.extra_bindings("end_paste", func=self.cells_pasted)
        label = "Change column name"  # Add option to the right-click menu for column headers
        self.sheet.popup_menu_add_command(label, self.column_header_change, table_menu=False, index_menu=False, header_menu=True)

    # Event functions
    def cell_edited(self, info_tuple):
        r, c, key_pressed, updated_value = info_tuple  # break the info about the event to individual variables
        if check_input(updated_value):
            pass  # go do stuff with the updated table
        else:
            msg.showwarning("Input Error", "'" + updated_value + "' is not a legal value")
            pass  # what do I do here? How do I make tksheet *not* insert the change to the table?

    def cells_pasted(self, info_tuple):
        key_pressed, rc_tuple, updated_array = info_tuple  # break the info about the event to individual variables
        r, c = rc_tuple  # row & column where paste begins
        if check_input(updated_array):
            pass  # go do stuff with the updated table
        else:
            msg.showwarning("Input Error", "pasted array contains illegal values")
            pass  # what do I do here? How do I make tksheet *not* insert the change to the table?

    def column_header_change(self):
        r, c = self.sheet.get_currently_selected()
        col_name = sd.askstring("User Input", "Enter column name:")
        if col_name is not None and col_name != "":  # if user cancelled (or didn't enter anything), do nothing
            self.sheet.headers([col_name], index=c)  # This does not work - it always changes the 1st col
            self.sheet.redraw()


# from here down is test code
def check_input(value):  # instead of actual data testing we let the tester choose a pass/fail response
    return msg.askyesno("Instead of input checking","Did input pass entry checks?")


test = demo()
lst = ["hello", "world"]
test.sheet.insert_column(values=lst)
lst = [0, "hello", "yourself"]
test.sheet.insert_column(values=lst)
test.mainloop()


Solution 1:[1]

I realize that the original post is now 5 months old, and I'm a relative n00b, but I hope this helps.

Given a tksheet instance 'sheet' that has already been populated with headers ["A"."B"."C"], the following works to change the header "B" to "NEW":

sheet.headers()[1]="NEW"

Hope this helps.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Reza Rahemtola