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.
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.
CodePudding user response:
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.