-2

I have a GUI that dynamically generates widgets based on the user selected number of systems:

GUI

These widgets are generated using a Callback function like the below sample of code:

class Window():
    def __init__(self, master):
        master.title('Production Analysis Tool')
           # callback function to create entry boxes based on number of systems

        self.L0 = Label(root, text="Equipment Parameters:", font = ('TKDefaultFont', 9, 'bold'))
        self.L0.grid(row=3,column=0, sticky=W)
        inverter_file = r'F:\CORP\PROJECTS\07599-A_Solar Development\Screening\_Production Analysis Tool\User Inputs\Inverter_Data.csv'      
        module_file = r'F:\CORP\PROJECTS\07599-A_Solar Development\Screening\_Production Analysis Tool\User Inputs\Module_Data.csv'    

        def update_scroll_region(event):
            canvas.configure(scrollregion=canvas.bbox("all"))

        def callback(*args):
            dynamic_widgets = Frame(canvas)
            canvas.create_window(0,0, anchor='nw', window = dynamic_widgets)
            self.system_size = int(self.system_size_raw.get())
    # Inverter Type  
            self.Lblank = Label(dynamic_widgets, text = "").grid(row=8, column=1, sticky=W)
            self.L3 = Label(dynamic_widgets, text = "Inverter Type")
            self.L3.grid(row=9, column=1, sticky=W)
            global inverter_types # declare array as global parameter so it can be accessed outside function
            inverter_types = []
            for i in range(self.system_size):
                inverter_list = get_inverter_list(inverter_file)
                inverter_list = ["Select"] + inverter_list
                self.inverter_types_raw = StringVar()
                self.L3a = Label(dynamic_widgets, text = "System {}".format(i+1), font = ('Calibri', 10,'italic'))
                self.L3a.grid(row=10+i, column=1, sticky=E)
                self.widget = OptionMenu(dynamic_widgets, self.inverter_types_raw, *inverter_list, command = get_values_0)
                self.widget.grid(row=10+i, column=2,sticky=EW)
                inverter_types.append(self.widget)
                dynamic_widgets.bind("<Configure>", update_scroll_region)

        global inv_type
        inv_type = []
        def get_values_0(value):
            inv_type.append(value)


        button = tk.Button(root, text = "Store Values", font=('Calibri', 10,'italic'), bg = "SlateGray3",command = lambda:[gget_values_0()])

        button.grid(row = 61, column = 2, columnspan=8, sticky = 'nesw')

    # System Type
        self.L1 = Label(root, text = "System Type")
        self.L1.grid(row=4, column=1, sticky=W)
        self.sys_type_raw = StringVar(root)
        types = ['Select', 'Central Inverter', 'String Inverters']
        self.popupMenu6 = OptionMenu(root, self.sys_type_raw, *types)
        self.popupMenu6.grid(row=4, column=2, sticky=EW)


    # Number of Systems
        self.L2 = Label(root, text = "Number of Systems")
        self.L2.grid(row=6, column=1, sticky=W)
        self.system_size_raw = IntVar(root)
        choices = list(range(1,50))
        self.popupMenu2 = OptionMenu(root, self.system_size_raw, *choices)
        self.popupMenu2.grid(row=6, column=2, sticky=EW)
        self.system_size_raw.trace("w", callback)


        vsb = Scrollbar(root, orient="vertical")
        vsb.grid(row=8, column=6, sticky = 'ns')
        canvas = Canvas(root, width = 600, height = 200)
        vsb.config(command = canvas.yview)
        canvas.configure(yscrollcommand=vsb.set)
        canvas.grid(row=8,column=0)



    # SITE ORIENTATION

        self.L12 = Label(root, text="Site Orientation:", font = ('TKDefaultFont', 9, 'bold'))
        self.L12.grid(row=66, column=0, sticky=W)
        self.L13 = Label(root, text = "Module Tilt Angle (degrees)")
        self.L13.grid(row=67, column=1, sticky=W)
        self.modtilt_raw = Entry(master)
        self.modtilt_raw.grid(row=67, column=2, sticky=EW)

        self.L14 = Label(root, text = "Array Azimuth (degrees)")
        self.L14.grid(row=68, column=1, sticky=W)
        self.arraytilt_raw = Entry(master)
        self.arraytilt_raw.grid(row=68, column=2, sticky=EW)


    # SUBMIT INFORMATION

        self.L27 = Label(root, text=" ").grid(row=84,column=1)  # Add row of space    
        self.cbutton = tk.Button(root, text="SUBMIT",command = self.store_user_inputs, bg = "SlateGray3")
        self.cbutton.grid(row=85, column = 0, columnspan=8, sticky = 'ew')

    # STORE USER INPUT

    def store_user_inputs(self):

        self.system_size = np.float(self.system_size_raw.get())


        # save all inputs as global parameters so they can be accessed as variables outside of GUI
        global params
        params = [self.system_type]

root = Tk()
root.configure()
window = Window(root)
root.mainloop()

I would like to place the dynamically generated widgets (Inverter Type, Modules per String, Strings per Inverter, Inverters per System, Module Type, and Max Current per System) into a scrollable frame.

I can post more code if needed.

Malina
  • 13
  • 7
  • I need help figuring out how to add a scrollbar to these dynamically created widgets. – Malina Dec 17 '19 at 16:14
  • Please read the duplicate post mentioned. This question is ask often here and has already been answered many times. – Mike - SMT Dec 17 '19 at 16:16
  • I've adjusted the code in the post. What I've been unable to do looking at other posts regarding this question is if a user selects a system size over 5, the scrollbar will be enacted so only the first 5 widgets are shown in the window and the others can be viewed by scrolling @Mike - SMT – Malina Dec 17 '19 at 17:28
  • That can be handled with a simple if statement. After the user selects size simply do `if sys_size > 5: scrollbar.pack()` should do the job. Or `gird()` or whatever you are using to place it. – Mike - SMT Dec 17 '19 at 18:50
  • I understand that. However, as my code is above the scrollbar just simply expands with an increased number of systems but doesn't actually scroll the window. Once the user selects a number outside the canvas size the scrollbar goes away. Is there a way to fix this? @Mike - SMT – Malina Dec 17 '19 at 19:02
  • Your scrollbar should be outside the frame that is being scrolled. I would have to test your code to see whats actually going on. – Mike - SMT Dec 17 '19 at 19:06
  • I believe I made that adjustment. The gui now limits the view of the number of widgets however, the scrollbar is greyed out. It technically works but the actual bar does not appear, you have to press the greyed out arrows. Is there a fix to this? @Mike - SMT – Malina Dec 17 '19 at 20:11
  • @Yes. typically you need to reset the bounding box each time the frame is updated. Something like: `self.frame.bind("", self.update_scroll_region)` for your bind and then in the function `update_scroll_region` you would do `self.canvas.configure(scrollregion=self.canvas.bbox("all"))`. Take a look at [this post](https://stackoverflow.com/questions/3085696/adding-a-scrollbar-to-a-group-of-widgets-in-tkinter) for an example. – Mike - SMT Dec 17 '19 at 20:22
  • Thank you for the help on this! I was able to get it working properly and updated the code on the post as well. @Mike - SMT – Malina Dec 18 '19 at 13:33

1 Answers1

0

A scrollable frame can be created by creating a Frame widget on a Canvas. An example is as follows:

vsb = Scrollbar(root, orient = VERTICAL)
vsb.pack(fill=Y, side = RIGHT, expand = FALSE)
canvas = Canvas(root, yscrollcommand=vsb.set)
canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
vsb.config(command = canvas.yview)
canvas.config(scrollregion = canvas.bbox("all"))
InverterType = Frame(canvas)
canvas.create_window(0, 0, anchor = NW, window = InverterType)

Now make sure to add all the widgets created in the callback function to this InverterType frame.

(TIP - replace root with InverterType)

FrainBr33z3
  • 1,085
  • 1
  • 6
  • 12
  • What I can't figure out is where to place that in my code (i've added the full code to the post). Do I have to create a second Window or can I do it within the same window? @FrainBr33z3 – Malina Dec 17 '19 at 15:56
  • @Malina Include the canvas in your root window. In the `callback` function create the `Frame` widget and then add your dynamically created widgets in the same function – FrainBr33z3 Dec 17 '19 at 16:03
  • Thank you. I made those edits to the above code. However, now if I select a # systems greater than 10, the scrollbar stops working. I'd like to make it so when the user selects a system size greater than '5' the frame will only show the first five and the scrollbar would allow the user to see any widget greater than 5 @FrainBr33z3 – Malina Dec 17 '19 at 17:21