Accessing data object from multiple tkinter frames

Question:

What I’m doing

I’m writing a graph-viewing app. The user opens the app, clicks ‘Import’ in the sidebar, chooses a CSV file and then sees the graph embedded in the app. The user then clicks ‘Slice’ in the sidebar, and the app detects regions of the graph (eg: phase 1 is from x=10 to x=20). The user may then adjust the regions, using a set of Entry widgets underneath the graph, if they feel the app has done it incorrectly. Lastly, the user clicks ‘Export’ and chooses where to save the file. The app saves a duplicate of the CSV file that was imported at the start, with flags added to mark the regions.

This is the interface I have designed so far, with an artists impression of the soon-to-exist graph added for effect

What I’ve done so far

I’ve finished the interface layout, shown above. I’ve tried to follow a clear structure of classes. I have an App.py file, which contains an instance of the main_window class. That contains an instance of the sidebar class and main frame class. The sidebar class contains four instances of the sidebar_button class, etc etc. Note that I’m actually using customtkinter instead of tkinter, so it looks nicer. But I don’t think that should change anything important.

Also, yes I know that the class tkinter.Button exists and so it may seem weird that I’ve defined my own sidebar_button class. I’ve just done it that way because all four sidebar buttons have the same visual properties (colour, highlight colour, corner radius etc) and I was trying to avoid duplicate code. My sidebar_button class is merely a subclass of tkinter.Button.

I’ve also started writing my data class, which contains the property df (a pandas dataframe) and methods such as import, slice and export.

What’s confusing me

I’ve read that its good to keep frontend and backend separate, which is why I’ve written the data class. The app should contain a single instance of this class, which GUI objects can access. However, all the GUI objects are sorted neatly into different classes, so I’m not sure how they can access the data object. I think I should instantiate the data object inside of the root tkinter object (my main_window object) or possibly in the higher-level file App.py (where main_window is instantiated). But then how would an object such as the Import button access the data.import method? The Import button is a sidebar_button object, instantiated inside of the sidebar object, which is instantiated inside of the main_window object. Maybe I have to pass the data object down through the hierarchy of objects? Ie: pass data to the main_window object, and then pass it from there to the sidebar object, and then to the sidebar_button object? It seems messy to me, but maybe I’m wrong. What is the proper way? Note that I cannot simply instantiate the data object inside of a sidebar_button object, because it needs to be accessed by lots of other GUI objects in the app.

One other question, about handles vs values

Also, am I right in thinking that I have to pass the handle for my data object rather than the value, since I want all of the GUI objects that access data to be reading from and making adjustments to the same dataframe, that is contained within it?

I tried setting data as a global variable, but this didn’t work because each of my classes are defined in a different file for neatness. I started researching this method a bit more and got the sense that it was not the ‘proper’ way to go about things.

Asked By: Isaac Blanc

||

Answers:

In the end, I used the master property, as shown in the reduced example below. I don’t know if this is the best or ‘proper’ approach, but it seems fairly neat to me.

Firstly, I instantiated the data and sidebar objects in the constructor for my Main_window class.

class Main_window(customtkinter.CTk):
    def __init__(self):
        super().__init__()

        # create data object
        self.data = Data()

        # create sidebar
        self.sidebar = Sidebar(self)

Secondly, I instantiated the three sidebar_button objects in the Sidebar class constructor.

class Sidebar(customtkinter.CTkFrame):
    def __init__(self,master):
        super().__init__(master) 

        # create buttons
        import_button = Sidebar_button(self,"Import",command=master.data.import_CSV)
        slice_button  = Sidebar_button(self,"Slice", command=master.data.auto_slice)
        export_button = Sidebar_button(self,"Export",command=master.data.export_CSV)

When I call the sidebar object constructor, I pass main_window to master using the self keyword. This means I can access the main_window object from the sidebar object.

I want to program the app so that the user can import a CSV file by clicking the ‘Import’ button. This button is inside of the sidebar, which is inside of the main_window. The method for loading a CSV file belongs to the data object, which is also inside of the main_window. So, inside of the sidebar constructor, I call the sidebar_button constructor and pass command=master.data.import_CSV. This is like passing command=main_window.data.import_CSV, because main_window is the master of sidebar.

For objects in my app that are more deeply nested, I simply used master.master.master etc. This seems messy to me, but I don’t know of a better way.

Answered By: Isaac Blanc