Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added UI edit events, bugfixes #171

Merged
merged 3 commits into from
Apr 26, 2023
Merged

Added UI edit events, bugfixes #171

merged 3 commits into from
Apr 26, 2023

Conversation

CalJaDav
Copy link
Contributor

@CalJaDav CalJaDav commented Apr 26, 2023

Hello!
I have added events for external listeners so that programs know when changes are being made to the sheet via the UI. This can be useful in cases where parts of the sheet or program need to be updated dynamically based on what is on the sheet without comparing the sheet data every mainloop iteration. Each edit to the table should produce one event that occurs after tksheet has made all the internal changes to its data.

Note that edits to the sheet data through code should produce no event, this is because the code should know if it is making changes and it is easy to create infinite loops of editing events if the code changes to sheet data produce events which spawn more changes.

This is somewhat a response to issue #170. I think this is a better way for users to handle calculated cells going forward as the actual logic sits outside the widget itself.

Here is a small code example where column C is the sum of A and B:

from tksheet import Sheet, float_formatter
import tkinter as tk

class demo(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.bind('<<SheetDataChangeEvent>>', self.on_data_change)
        self.i = 0
        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, total_rows=2, total_columns=2
                           )
        self.sheet.enable_bindings()
        self.frame.grid(row = 0, column = 0, sticky = "nswe")
        self.sheet.grid(row = 0, column = 0, sticky = "nswe")
        self.sheet.align_columns(0, "center")
        self.sheet.align_columns(1, "left")
        self.sheet.format_sheet(formatter_options = float_formatter(decimals = 7))
        self.sheet.set_sheet_data([[None, None, None],[1,1, None]])
        self.sheet.readonly_columns(2)
        self.perform_calcs()
        

    def on_data_change(self, event):
        self.i += 1
        self.perform_calcs()

    def perform_calcs(self):
        for r, row in enumerate(self.sheet.get_sheet_data()):
            try: 
                row[2] = row[0] + row[1]
            except:
                row[2] = None
            print(row)
            self.sheet.set_row_data(r, row)
        self.sheet.redraw()
    
app = demo()
app.mainloop()

@ragardner
Copy link
Owner

Hey,

Thanks for the bugfixes and suggestion. I agree and think that it is currently awkward to monitor every sheet modified event, having to create extra_bindings() for everything

I am thinking I might add some information on what sort of modification as well

@ragardner ragardner merged commit dc4f7fd into ragardner:master Apr 26, 2023
@ragardner
Copy link
Owner

I may or may not plug this into the existing extra_bindings(), with "sheet_modified" or something, I am undecided at the moment so don't write the docs for it

@ragardner
Copy link
Owner

ragardner commented Apr 26, 2023

I decided that I will add both something for extra_bindings and also I might change which object the event is emitted from so that you can do something like if self.my_sheet == event.widget: in a later release so I won't advertise this just yet but will do a release with these changes for the moment anyway

Thanks again!

@CalJaDav
Copy link
Contributor Author

CalJaDav commented Apr 26, 2023

Agreed. tkinter does not handle binding data to custom virtual events very well. There are ways around it, but it is fairly hacky, so we will need to provide some custom functions for the user to use to unpack the event data. I'll attach the way I handle passing data through custom virtual events in my apps.

I think it would be useful pass data the following inside our event.

  • Sheet id: A string which defaults to a UUID that the user can use to identify the sheet that triggered the event. (Most important)
  • Event type: What triggered the event, ctrl-z, paste, cell edit, etc.
  • Rows/Cols/Cells changed.
def bind_event_with_data(widget, sequence, func, add = None):
    def _substitute(*args):
        e = lambda: None 
        e.data = eval(args[0])
        e.widget = widget
        return (e,)

    funcid = widget._register(func, _substitute, needcleanup=1)
    cmd = '{0}if {{"[{1} %d]" == "break"}} break\n'.format('+' if add else '', funcid)
    widget.tk.call('bind', widget._w, sequence, cmd)

if __name__ == '__main__':
    class demo(tk.Tk):
        
        def __init__(self):
            super().__init__()
            self.button = tk.Button(self, text='Click me', command = self.on_click)
            self.button.pack()
            bind_event_with_data(self, '<<CustomEventWithData>>', self.on_event)

        def on_click(self):
            self.event_generate('<<CustomEventWithData>>', data={'var1': '1', 'var2': '2'}) # Note that only strings can be passed in this way
        
        def on_event(self, event):
            print(f"var1: {event.data['var1']}, var2: {event.data['var1']}")
        
    demo().mainloop()

@ragardner
Copy link
Owner

Thanks for the additional info and code, I will get back to you about it soon

@ragardner
Copy link
Owner

ragardner commented Apr 30, 2023

What I've done in 6.0.3 is put your function inside Sheet(), I haven't attached any data to it yet except an empty dict but you use it like so:

self.sheet.bind_event("<<SheetModified>>", self.on_event)

It'll send the sheet object and the data dict

I think this is okay but let me know if not

@juddiny03
Copy link

Cool, lo he estado usando y me esta funcionando esa solucion...

@juddiny03
Copy link

Lo que hice fue 6.0.3poner su función dentro de Sheet(), todavía no le he adjuntado ningún dato, excepto un dictado vacío, pero lo usa así:

self.sheet.bind_event("<<SheetModified>>", self.on_event)

Enviará el objeto de hoja y el dictado de datos.

Creo que esto está bien, pero avísame si no

Hola, me funciona hasta el momento, es una buena opcio que se pueda hacer calculos.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants