functiontabsframescustomtkinter

CustomTkinter with multiple tabs, frames and classes - get widget values


I have a ctk application with many widgets spread across multiple tabs and frames. The inputs are so extensive I would ultimately like the user to be able to save their entries to a named file and retrieve them later. At the moment I am struggling to get all the widget values which are spread across all tabs when the Save button is clicked. The save button is on one frame but my coded functions within the various classes are not seeing each other.

A minimal example that covers the complexities is provided below:

import customtkinter as ctk
from tkinterdnd2 import TkinterDnD, DND_ALL
from tkinter import StringVar

class TabView(ctk.CTkTabview, TkinterDnD.DnDWrapper):
    def __init__(self, master):
        super().__init__(master)
        self.TkdndVersion = TkinterDnD._require(self)
        global path_string

        self.add('Tab 1')
        self.add('Tab 2')

        # Input file drag and drop.
        path_string = StringVar(value='Drop a file here')
        self.PathLabel = ctk.CTkLabel(master=self.tab('Tab 1'), text='Input File', anchor='w')
        self.PathLabel.grid(row=0, column=0, padx=10, pady=10, sticky='ew')
        self.PathEntry = ctk.CTkEntry(master=self.tab('Tab 1'), textvariable=path_string)
        self.PathEntry.grid(row=0, column=1, columnspan=3, padx=10, pady=10, sticky='ew')
        self.PathEntry.drop_target_register(DND_ALL)
        self.PathEntry.dnd_bind('<<Drop>>', self.fn_get_path)

        # Number of shuffles.
        self.ShuffleEntry = ctk.CTkEntry(master=self.tab('Tab 1'), textvariable=ctk.StringVar(value='10'))
        self.ShuffleEntry.grid(row=1, column=1, padx=10, pady=10, sticky='ew')

        # Number of units.
        self.UnitsEntry = ctk.CTkEntry(master=self.tab('Tab 2'), textvariable=ctk.StringVar(value='1000'))
        self.UnitsEntry.grid(row=1, column=1, padx=10, pady=10, sticky='ew')

        # Settings frame with save now button.
        self.SettingsFrame = SettingsFrame(master=self.tab('Tab 2'))
        self.SettingsFrame.grid(row=2, column=1, padx=20, pady=20)

    def fn_get_path(self, event):
        path_string.set(value=event.data.strip('{}'))

    def fn_save_now(self):
        App.fn_save_settings(TabView)
        return

class SettingsFrame(ctk.CTkFrame):
    def __init__(self, master):
        super().__init__(master)
        self.SaveNowButton = ctk.CTkButton(master=self, width=40, height=20, text='Save',
                                           command=lambda: TabView.fn_save_now(self))
        self.SaveNowButton.grid(row=5, column=4, columnspan=1, padx=10, pady=10, sticky='ew')

class App(ctk.CTk):
    def __init__(self):
        super().__init__()
        self.tab_view = TabView(master=self)
        self.tab_view.grid(row=0, column=0, padx=20, pady=20)

    def fn_save_settings(self):
        Setting_1 = path_string.get()
        Setting_2 = self.tab_view.ShuffleEntry.get() # Gives a no attribute error
        Setting_3 = self.tab_view.UnitsEntry.get()   # Gives a no attribute error
        print(Setting_1, Setting_2, Setting_3)
        return

app = App()
app.mainloop()

In this case the user can drop an input file into the PathEntry box and specify a couple of other entries. I would like them to be able to save their entries at any point by clicking the Save button on the second tab. I am managing to retrieve the path_string but not the other two settings of interest. How can I retrieve Setting_2 and Setting_3? I have tried using tab_view / master / controller options without success and I have tried moving functions around. If I can retrieve all the values then I expect I will be able to write code to save them but any further tips would be appreciated.


Solution

  • A solution I have got working is to remove the SettingsFrame class and put the contents into the TabView class:

    import customtkinter as ctk
    from tkinterdnd2 import TkinterDnD, DND_ALL
    from tkinter import StringVar
    
    class TabView(ctk.CTkTabview, TkinterDnD.DnDWrapper):
        def __init__(self, master):
            super().__init__(master)
            self.TkdndVersion = TkinterDnD._require(self)
            global path_string
    
            self.add('Tab 1')
            self.add('Tab 2')
    
            # Input file drag and drop.
            path_string = StringVar(value='Drop a file here')
            self.PathLabel = ctk.CTkLabel(master=self.tab('Tab 1'), text='Input File', anchor='w')
            self.PathLabel.grid(row=0, column=0, padx=10, pady=10, sticky='ew')
            self.PathEntry = ctk.CTkEntry(master=self.tab('Tab 1'), textvariable=path_string)
            self.PathEntry.grid(row=0, column=1, columnspan=3, padx=10, pady=10, sticky='ew')
            self.PathEntry.drop_target_register(DND_ALL)
            self.PathEntry.dnd_bind('<<Drop>>', self.fn_get_path)
    
            # Number of shuffles.
            self.ShuffleEntry = ctk.CTkEntry(master=self.tab('Tab 1'), textvariable=ctk.StringVar(value='10'))
            self.ShuffleEntry.grid(row=1, column=1, padx=10, pady=10, sticky='ew')
    
            # Number of units.
            self.UnitsEntry = ctk.CTkEntry(master=self.tab('Tab 2'), textvariable=ctk.StringVar(value='1000'))
            self.UnitsEntry.grid(row=1, column=1, padx=10, pady=10, sticky='ew')
    
            # Settings frame with save now button.
            self.settings_frame()
    
        def fn_get_path(self, event):
            path_string.set(value=event.data.strip('{}'))
    
        def settings_frame(self):
            self.SettingsFrame = ctk.CTkFrame(master=self.tab('Tab 2'))
            self.SettingsFrame.grid(row=2, column=1, padx=20, pady=20)
    
            self.SaveNowButton = ctk.CTkButton(self.SettingsFrame, width=40, height=20, text='Save',
                                               command=lambda: self.fn_save_now())
            self.SaveNowButton.grid(row=5, column=4, columnspan=1, padx=10, pady=10, sticky='ew')
    
        def fn_save_now(self, *_args):
            App.fn_save_settings(self)
            return
    
    class App(ctk.CTk):
        def __init__(self):
            super().__init__()
            self.tab_view = TabView(master=self)
            self.tab_view.grid(row=0, column=0, padx=20, pady=20)
    
        def fn_save_settings(self):
            Setting_1 = path_string.get()
            Setting_2 = self.ShuffleEntry.get() # Gives a no attribute error
            Setting_3 = self.UnitsEntry.get()   # Gives a no attribute error
            print(Setting_1, Setting_2, Setting_3)
            return
    
    app = App()
    app.mainloop()
    

    The TabView class will get massive but at least I have something that works!