4
votes

I have a device that understands multiple serial protocols. During development I created simple Tkinter UIs to play with the protocols. Each protocol got a new UI. Since the protocols have lots of commands, I implemented the entire UI within a scrollable canvas to permit it to be scrolled when used on smaller displays. The separate UIs worked fine, and I'm now trying to combine the separate UIs into a tabbed UI.

The common elements of each UI are the serial port selector, which I separated out and put into a separate top frame. I then implemented a notebook, and put each protocol UI into a frame for each tab.

But I'm unable to properly control sizing: I want the root window width to be fixed at the maximum width of any of the protocol frames or serial selector frame, with horizontal resizing disabled. I want the serial selector to always be present, and not be affected when the window is vertically resized (only the notebook is resized/scrolled).

Below is what I have so far. All the pieces are present, but the notebook doesn't fill the complete window width, and the notebook doesn't resize when the window is resized (resizing just adds blank space).

def main():
    ## Main window
    root = Tkinter.Tk()
    root.title("Multi-Protocol UI")

    ## Grid sizing behavior in window
    root.grid_rowconfigure(0, weight=0)
    root.grid_rowconfigure(1, weight=1)
    root.grid_columnconfigure(0, weight=1)
    root.grid_columnconfigure(1, weight=0)

    ## Window content
    upper = ttk.Frame(root)   # Serial port selector
    upper.grid(row=0)
    upper.grid_rowconfigure(0, weight=0)
    upper.grid_columnconfigure(0, weight=1)
    upper.grid_columnconfigure(1, weight=0)
    lower = ttk.Frame(root)   # For protocols
    lower.grid(row=1)
    lower.grid(row=1, sticky='nswe')
    lower.grid_rowconfigure(0, weight=1)
    lower.grid_columnconfigure(0, weight=1)
    lower.grid_columnconfigure(1, weight=0)

    # Setup serial control frame
    serial = SerialFrame(master=upper)  # Serial port selector widget + Open button

    ## Protocol GUIs are large: Use a form within a scrollable canvas.
    cnv = Tkinter.Canvas(lower)
    cnv.grid(row=0, column=0, sticky='nswe')
    cnv.grid_rowconfigure(0, weight=1)
    cnv.grid_columnconfigure(0, weight=1)
    cnv.grid_columnconfigure(1, weight=0)
    # Scrollbar for canvas
    vScroll = Tkinter.Scrollbar(
        lower, orient=Tkinter.VERTICAL, command=cnv.yview)
    vScroll.grid(row=0, column=1, sticky='ns')
    cnv.configure(yscrollcommand=vScroll.set)
    # Frame in canvas
    window = Tkinter.Frame(cnv)
    window.grid()
    # Put the frame in the canvas's scrollable zone
    cnv.create_window(0, 0, window=window, anchor='nw')

    # Setup the notebook (tabs) within the scrollable window
    notebook = ttk.Notebook(window)
    frame1 = ttk.Frame(notebook)
    frame2 = ttk.Frame(notebook)
    notebook.add(frame1, text="ProtoA")
    notebook.add(frame2, text="ProtoB")
    notebook.grid(row=0, column=0, sticky='nswe')

    # Create tab frames
    protoA = ProtoAFrame(master=frame1)
    protoA.grid()
    protoA.update_idletasks()
    protoB = ProtoBFrame(master=frame2)
    protoB.grid()
    protoB.update_idletasks()

    ## Update display to get correct dimensions
    root.update_idletasks()
    window.update_idletasks()
    ## Configure size of canvas's scrollable zone
    cnv.configure(scrollregion=(0, 0, window.winfo_width(), window.winfo_height()))

    # Not resizable in width:
    root.resizable(width=0, height=1)

    ## Go!
    root.mainloop()

How do I lock down the top serial frame, expand the notebook to full width, and force window resizing to only affect the notebook frame? I'm a Tkinter newbie, so please be gentle if I've missed something "obvious".

TIA!

1
Partial progress: In the root window, I need to set the rowconfigure weight for the top frame to 0, and the bottom frame to 1. Source above updated. But the notebook still doesn't expand to fill the lower frame. Grrr. - BobC
I've added rowconfigure and columnconfigure to all frames and have achieved much of the desired behavior. But the default window width is that of the rather narrow top frame, not that of the widest notebook tab. - BobC
My current kluge to the width issue is to pad the top window. Not pretty, but it works well enough. - BobC

1 Answers

6
votes

You have widgets nested inside widgets. Even if you have the notebook set to expand properly in it's container, you need to also make sure that every container upwards also expands properly. You haven't done that. For example, ask yourself whether the canvas has been set up to properly grow inside of lower.

Since you are starting out, here is what I recommend. Instead of trying to get everything right at once, choose a "divide and conquer" approach. First, create frames for the major areas of your GUI. Do nothing but those frames, and get their resize behavior to be exactly what you want. It helps at this stage to give each its own color so you can clearly see where the widgets are. You can always change the color later. Also, if you only have a couple widgets, or all your widgets are oriented horizontally or vertically, pack is often easier to use than grid.

For example:

upper.pack(side="top", fill="x", expand=False)
lower.pack(side="bottom", fill="both", expand=True)

Once you have these major pieces resizing appropriately it's time to solve the problem for just one of those areas. Pick an area, and do the same: add in its children widgets, or subdivide into frames if you have areas within an area. Once you have this working, lather, rinse, repeat.