Adding tabs to a Notebook at runtiime with a function - I can create the tab and its inner frame, but cannot add components to the frame

Viewed 22

I am starting with a notebook defined in the original code as having 4 tabs, each of which has an associated Frame (Tab1 has a Frame named page1, Tab2 has page2, and so on). for the first two tabs, I have functions that place a canvas in each frame and draw a graph on that canvas. So I can place items on these pre-defined tab pages at runtime in code.

My goal is to be able to place additional, unplanned tabs into the notebook, using functions, and then place information on the pages associated with the new tabs.

I have a function Command3() - see below - in which I create the tab on the fly.

'''

def command3():
    #command invoked by button 3 - add a tab to the notebook
    tab_caption = simpledialog.askstring('Tab Text','Enter Text for Tab')
    ntn = len(nb.tabs()) + 1
    if tab_caption == '':
        tab_caption = 'Tab ' + str(ntn)
    var_name = 'page' + str(ntn)
    command_string = var_name + ' = tk.Frame(nb)'
    exec(command_string)
    command_string = 'nb.add('+var_name+', text = tab_caption)'
    exec(command_string)

'''

The above code works just fine. When I execute this code (by clicking on Button3) I get a new tab with the text I supply in the dialog box which has an inner frame named "page 5". I can click on the tab and select it (it shows nothing because I haven't put anything there), and I can even change the name of the tab in code.

My problem is that, when I try to place anything on "page 5" nothing appears. The function Command4 below is intended to just place a label anywhere on the page.

'''

 def command4():

    #command invoked by button 4 - add label
    last_tab = len(nb.tabs())
    last_tab_index = last_tab-1
    page_name = 'page' + str(last_tab)
    nb.tab(last_tab_index, text = 'Repeat Graph')
    print(last_tab)
    print(last_tab_index)
    print(page_name)

    labelTest = tk.Label(page5, text = 'Something')
    labelTest.pack()

''' When this code executes, the tab name changes, the print statements all work, and the last two lines appear to execute without error, but nothing appears in the frame for Tab5 (Frame is page5).

Python seems to know that page 5 exists (when I terminate the program and type >>> page 5 in Idle, I get <tkinter.Frame object .!frame2.!notebook.!frame5>. So command 3 seems to have worked and created the appropriate Frame object.

My intent was to compute the "page" name so I could do this repeatedly and use code as follows:

'''

 def command4():

    #command invoked by button 4 - add label
    last_tab = len(nb.tabs())
    last_tab_index = last_tab-1
    page_name = 'page' + str(last_tab)
    nb.tab(last_tab_index, text = 'Repeat Graph')
    print(last_tab)
    print(last_tab_index)
    print(page_name)

    command_string = "labelTest = tk.Label(" + page_name + ", text = 'Something')"
    exec(command_string) 
    labelTest.pack()

'''

When I execute this code, the exec(command_string) produces no errors, but, when the software executes the labelTest.pack() command, I get an error that the name labelTest is not defined.

Is this a scope problem? Is there another way to handle this problem? Is there some way to use the internal ".!frame2.!notebook.!frame5" notation to accomplish the same thing?

1 Answers

You don't need to use exec, you can simply use a local variable. Using exec in this matter is almost never the right thing to do.

For example:

def command3():
    tab_caption = simpledialog.askstring('Tab Text','Enter Text for Tab')
    ntn = len(nb.tabs()) + 1
    if tab_caption == '':
        tab_caption = 'Tab ' + str(ntn)
    tab_frame = tk.Frame(nb)
    nb.add(tab_frame, text=tab_caption)

    # inside the function you can add other widgets
    label = tk.Label(tab_frame, text="Hello, world")
    label.pack()

If you want to add items to the frame outside of the function, have the function return the frame.

def command3():
    ...
    return tab_frame

Then, in the code that calls command3 you can add widgets via the returned frame:

new_tab_frame = command3()
label = tk.Label(new_tab_frame, text="Hello, world")

From just about anywhere, you can use the values returned by the tabs method. However, that method returns the name of the internal widget which must be converted to the actual widget before it becomes usable:

last_frame = nb.nametowidget(nb.tabs()[-1])
label = tk.Label(last_frame, ...) 

If you prefer to reference the tabs symbolically (ie: by name rather than by index) you can store the frame in a dictionary:

frames = {}
def command3():
    #command invoked by button 3 - add a tab to the notebook
    tab_caption = simpledialog.askstring('Tab Text','Enter Text for Tab')
    ntn = len(nb.tabs()) + 1
    if tab_caption == '':
        tab_caption = 'Tab ' + str(ntn)
    tab_frame = tk.Frame(nb)
    frames[tab_caption] = tab_frame
    nb.add(tab_frame, text=tab_caption)
...
# add a label to the tab named "Tab 1":
tab_frame = frames["Tab 1"]
label = tk.Label(tab_frame, ...)
Related