Search code examples
pythontkintercombobox

tkinter: How to get value from ComboBox in one frame with button in different frame


The goal is to allow the user to select from the combobox dropdown and then click Select Files button. The button will get the product type (the value from the combobox) and will open the correct directory based on what is chosen in the combobox. Problem is, I cannot seem to get this value since the button and combobox are in different frames. I feel like I'm missing something basic here.

import tkinter as tk
from tkinter import ttk
from tkinter import filedialog as fd


def select_files(prod_type):
    path = f"\\\\10.10.3.7\\Production\\Test_Folder\\{prod_type}"
    filetypes = (
        ('PDF Files', '*.pdf'),
    )

    filenames = fd.askopenfilenames(
        title='Open files',
        initialdir=path,
        filetypes=filetypes)

    for file in filenames:
        print(file)


class InputFrame(ttk.Frame):
    def __init__(self, parent):
        super().__init__(parent)
        # setup the grid layout manager
        self.columnconfigure(0, weight=1)
        self.columnconfigure(0, weight=3)
        self.__create_widgets()

    def __create_widgets(self):
        # Product
        ttk.Label(self, text='Product:').grid(column=0, row=0, sticky=tk.W)
        self.product_type = tk.StringVar()
        self.product_combo = ttk.Combobox(self, width=30, textvariable=self.product_type)
        self.product_combo['values'] = ('Notepad', 'Flat Notecard', 'Folded Notecard', 'Journal')
        self.product_combo.set('Notepad')
        self.product_combo['state'] = 'readonly'
        self.product_combo.grid(column=1, row=0, sticky=tk.W)

        # Algorithm:
        ttk.Label(self, text='Algorithm:').grid(column=0, row=1, sticky=tk.W)
        algo_var = tk.StringVar()
        algo_combo = ttk.Combobox(self, width=30)
        algo_combo['values'] = ('panel', 'fuzzy')
        algo_combo.set('panel')
        algo_combo.grid(column=1, row=1, sticky=tk.W)

        # Orientation:
        ttk.Label(self, text='Orientation:').grid(column=0, row=2, sticky=tk.W)
        orientation_var = tk.StringVar()
        orientation_combo = ttk.Combobox(self, width=30, textvariable=orientation_var)
        orientation_combo['values'] = ('auto', 'portrait', 'landscape')
        orientation_combo.set('auto')
        orientation_combo.grid(column=1, row=2, sticky=tk.W)

        # Margin:
        ttk.Label(self, text='Margin:').grid(column=0, row=3, sticky=tk.W)
        margin = ttk.Entry(self, width=30)
        margin.grid(column=1, row=3, sticky=tk.W)

        # Gap:
        ttk.Label(self, text='Gap:').grid(column=0, row=4, sticky=tk.W)
        gap = ttk.Entry(self, width=30)
        gap.grid(column=1, row=4, sticky=tk.W)

        # Repeat:
        ttk.Label(self, text='Repeat:').grid(column=0, row=5, sticky=tk.W)
        repeat_number = tk.StringVar()
        repeat = ttk.Spinbox(self, from_=1, to=10, width=30, textvariable=repeat_number)
        repeat.set(1)
        repeat.grid(column=1, row=5, sticky=tk.W)

        for widget in self.winfo_children():
            widget.grid(padx=0, pady=5)


class ButtonFrame(ttk.Frame):
    def __init__(self, parent):
        super().__init__(parent)
        # setup the grid layout manager
        self.columnconfigure(0, weight=1)

        self.__create_widgets()

    def __create_widgets(self):
        ttk.Button(self, text='Select Files', command=self.on_go_pressed).grid(column=0, row=0)
        ttk.Button(self, text='Generate PDF').grid(column=0, row=1)

        for widget in self.winfo_children():
            widget.grid(padx=0, pady=3)

    def on_go_pressed(self):
        select_files(InputFrame.product_type.get())


class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("PDF n-up")
        self.eval('tk::PlaceWindow . center')
        self.geometry('400x200')
        self.resizable(0, 0)
        self.attributes('-toolwindow', True)

        # layout on the root window
        self.columnconfigure(0, weight=4)
        self.columnconfigure(1, weight=1)

        self.__create_widgets()

    def __create_widgets(self):
        # create the input frame
        input_frame = InputFrame(self)
        input_frame.grid(column=0, row=0)

        # create the button frame
        button_frame = ButtonFrame(self)
        button_frame.grid(column=1, row=0)


if __name__ == '__main__':
    app = App()
    app.mainloop()

Solution

  • You can pass the product type variable to ButtonFrame when you create it, since you create the ButtonFrame after creating the InputFrame. Here is the code, with changed and added lines marked accordingly:

    import tkinter as tk
    from tkinter import ttk
    from tkinter import filedialog as fd
    
    
    def select_files(prod_type):
        path = f"/home/sam/Pictures/Test/{prod_type}"
        filetypes = (
            ('PDF Files', '*.pdf'),
        )
    
        filenames = fd.askopenfilenames(
            title='Open files',
            initialdir=path,
            filetypes=filetypes)
    
        for file in filenames:
            print(file)
    
    
    class InputFrame(ttk.Frame):
        def __init__(self, parent):
            super().__init__(parent)
            # setup the grid layout manager
            self.columnconfigure(0, weight=1)
            self.columnconfigure(0, weight=3)
            self.__create_widgets()
    
        def __create_widgets(self):
            # Product
            ttk.Label(self, text='Product:').grid(column=0, row=0, sticky=tk.W)
            self.product_type = tk.StringVar()
            self.product_combo = ttk.Combobox(self, width=30, textvariable=self.product_type)
            self.product_combo['values'] = ('Notepad', 'Flat Notecard', 'Folded Notecard', 'Journal')
            self.product_combo.set('Notepad')
            self.product_combo['state'] = 'readonly'
            self.product_combo.grid(column=1, row=0, sticky=tk.W)
    
            # Algorithm:
            ttk.Label(self, text='Algorithm:').grid(column=0, row=1, sticky=tk.W)
            algo_var = tk.StringVar()
            algo_combo = ttk.Combobox(self, width=30)
            algo_combo['values'] = ('panel', 'fuzzy')
            algo_combo.set('panel')
            algo_combo.grid(column=1, row=1, sticky=tk.W)
    
            # Orientation:
            ttk.Label(self, text='Orientation:').grid(column=0, row=2, sticky=tk.W)
            orientation_var = tk.StringVar()
            orientation_combo = ttk.Combobox(self, width=30, textvariable=orientation_var)
            orientation_combo['values'] = ('auto', 'portrait', 'landscape')
            orientation_combo.set('auto')
            orientation_combo.grid(column=1, row=2, sticky=tk.W)
    
            # Margin:
            ttk.Label(self, text='Margin:').grid(column=0, row=3, sticky=tk.W)
            margin = ttk.Entry(self, width=30)
            margin.grid(column=1, row=3, sticky=tk.W)
    
            # Gap:
            ttk.Label(self, text='Gap:').grid(column=0, row=4, sticky=tk.W)
            gap = ttk.Entry(self, width=30)
            gap.grid(column=1, row=4, sticky=tk.W)
    
            # Repeat:
            ttk.Label(self, text='Repeat:').grid(column=0, row=5, sticky=tk.W)
            repeat_number = tk.StringVar()
            repeat = ttk.Spinbox(self, from_=1, to=10, width=30, textvariable=repeat_number)
            repeat.set(1)
            repeat.grid(column=1, row=5, sticky=tk.W)
    
            for widget in self.winfo_children():
                widget.grid(padx=0, pady=5)
    
    
    class ButtonFrame(ttk.Frame):
        def __init__(self, parent, product_type): ### EDITED THIS LINE
            super().__init__(parent)
            # setup the grid layout manager
            self.columnconfigure(0, weight=1)
    
            self.product_type = product_type ### ADDED THIS LINE
    
            self.__create_widgets()
    
        def __create_widgets(self):
            ttk.Button(self, text='Select Files', command=self.on_go_pressed).grid(column=0, row=0)
            ttk.Button(self, text='Generate PDF').grid(column=0, row=1)
    
            for widget in self.winfo_children():
                widget.grid(padx=0, pady=3)
    
        def on_go_pressed(self):
            select_files(self.product_type.get()) ### EDITED THIS LINE
    
    
    class App(tk.Tk):
        def __init__(self):
            super().__init__()
            self.title("PDF n-up")
            self.eval('tk::PlaceWindow . center')
            self.geometry('400x200')
            self.resizable(0, 0)
            # self.attributes('-toolwindow', True)
    
            # layout on the root window
            self.columnconfigure(0, weight=4)
            self.columnconfigure(1, weight=1)
    
            self.__create_widgets()
    
        def __create_widgets(self):
            # create the input frame
            input_frame = InputFrame(self)
            input_frame.grid(column=0, row=0)
    
            # create the button frame
            button_frame = ButtonFrame(self, input_frame.product_type) ### EDITED THIS LINE
            button_frame.grid(column=1, row=0)
    
    
    if __name__ == '__main__':
        app = App()
        app.mainloop()
    

    Notice how in the line button_frame = ButtonFrame(self, input_frame.product_type), I give it input_frame.product_type as an argument, so that it can access it any time it wants.

    The changes to ButtonFrame required for this: I changed the __init__() method so that it can receive another argument. It then assigns the value of the product_type argument to a variable so that it can use it later. When it calls select_files(), it simply uses self.product_type.

    As to why calling select_files(InputFrame.product_type.get()) doesn't work: product_type is a variable that is created only when InputFrame is initialized. In the line above, you reference the class itself, not an instance of the class, so it has not been initialized. Therefore, anything that is created in InputFrame.__init__() is not defined, becuase __init__() wasn't called.