3

I wrote a tkinter application that had widgets displayed on two frames, similar to this example, which successfully runs.

from tkinter import *
from tkinter import ttk

root = Tk()
root.title("Example")

notebook = ttk.Notebook(root)
frame1 = ttk.Frame(notebook)
frame2 = ttk.Frame(notebook)
notebook.add(frame1, text="Frame One")
notebook.add(frame2, text="Frame Two")
notebook.pack()

#(The labels are examples, but the rest of the code is identical in structure). 

labelA = ttk.Label(frame1, text = "This is on Frame One")
labelA.grid(column=1, row=1)

labelB = ttk.Label(frame2, text = "This is on Frame Two")
labelB.grid(column=1, row=1)

root.mainloop()

I decided that I should try to restructure the program to use a class (which I'm admittedly not very familiar with). However, I'm unsure what I should do to allow the widgets to appear on different frames (everything else works okay). For instance, the following produces a "TypeError: init() takes from 1 to 2 positional arguments but 3 were given." So presumably I'd need to initialise with an extra argument, but I'm not sure how the notebook would be worked into that, or if that's even the approach I should be taking. (The program will run if the "frame1" and "frame2" arguments are removed from the labels, it will, however, display the same thing on both frames).

from tkinter import *
from tkinter import ttk

class MainApplication(ttk.Frame):
    def __init__(self, parent):
        ttk.Frame.__init__(self, parent)

        self.labelA = ttk.Label(self, frame1, text = "This is on Frame One")
        self.labelA.grid(column=1, row=1)

        self.labelB = ttk.Label(self, frame2, text = "This is on Frame Two")
        self.labelB.grid(column=1, row=1)

root = Tk()
root.title("Example")
notebook = ttk.Notebook(root)
frame1 = ttk.Frame(notebook)
frame2 = ttk.Frame(notebook)
notebook.add(frame1, text="Frame One")
notebook.add(frame2, text="Frame Two")
notebook.pack()
MainApplication(root).pack()
root.mainloop()

I'm interested in a solution, but I'm also interested in learning what the class is doing differently compared to the standalone widgets.

Plato's Cave
  • 99
  • 1
  • 1
  • 10
  • Please provide a [mcve]. Showing blocks of code out of context isn't very useful. – Bryan Oakley Jun 25 '17 at 11:58
  • @Bryan Oakley modified – Plato's Cave Jun 25 '17 at 23:54
  • I just watched a youtube video which I think describes exactly what your looking for. Here is a link to the series [link](https://www.youtube.com/watch?v=HjNHATw6XgY&list=PLQVvvaa0QuDclKx-QpC9wntnURXVJqLyk) I think it is covered in the first couple videos. this guy creates a class which will essentially create multiple tabs for your multiple windows. – Joe Jun 27 '17 at 15:20
  • @Joe That seems like it'd work, although it's relatively convoluted and doesn't use a notebook. Surely there should be a simple way to convert my first example into a class. – Plato's Cave Jun 29 '17 at 22:47

4 Answers4

7

This would be one way to generalize the application as a class. You want to eliminate the repeated code.

from tkinter import *
from tkinter import ttk

class Notebook:

    def __init__(self,title):
        self.root = Tk()
        self.root.title(title)
        self.notebook = ttk.Notebook(self.root)

    def add_tab(self,title,text):
        frame = ttk.Frame(self.notebook)
        self.notebook.add(frame,text=title)
        label = ttk.Label(frame,text=text)
        label.grid(column=1,row=1)
        self.notebook.pack()

    def run(self):
        self.root.mainloop()

nb = Notebook('Example')
nb.add_tab('Frame One','This is on Frame One')
nb.add_tab('Frame Two','This is on Frame Two')
nb.run()
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
3

I suggest you split the different Frames within notebook into separate files. I used from tab2 import * because I wanted this to remain in the namespace without adding the file/class prefix ie. I didn't want to write: tab1.Tab1(notebook)

main.py

import tkinter as tk
from tkinter import ttk

from tab1 import *
from tab2 import *    

class MainApplication(tk.Frame):
  def __init__(self, parent, *args, **kwargs):
    tk.Frame.__init__(self, parent, *args, **kwargs)

    notebook = ttk.Notebook(parent)

    Tab1frame = Tab1(notebook)
    Tab2frame = Tab2(notebook)

    notebook.add(Typ1frame, text='TAB1')
    notebook.add(Typ2frame, text='TAB2')
    notebook.pack()

if __name__ == "__main__":
    root = tk.Tk()
    MainApplication(root).pack(side="top", fill="both", expand=True)
    root.mainloop()

tab1.py

import tkinter as tk
from tkinter import ttk

class Typ14(tk.Frame):
  def __init__(self, parent, *args, **kwargs):
    tk.Frame.__init__(self, parent, *args, **kwargs)
    shell_frame=tk.LabelFrame(self, text="Sample Label Frame", padx=5,pady=5)
    shell_frame.grid(row=0,column=0,padx=5,pady=5)
machaniv
  • 91
  • 1
  • 3
2

To ensure that your code is clear and purposeful, you should create a class for your main application, and a class for each tab. You don't need to separate tab classes into separate files, but if you're working with other developers it would be in your best interest.

The code below shows your code reformatted to have 3 classes: 1 for the main app (MainApplication), and 2 for each tab (Frame1 and Frame2). In addition, I've imported tkinter as tk for referential clarity.

import tkinter as tk
from tkinter import ttk

class MainApplication(tk.Tk):
    def __init__(self):
        super().__init__()

        self.title("Example")
        self.geometry('300x300')

        self.notebook = ttk.Notebook(self)

        self.Frame1 = Frame1(self.notebook)
        self.Frame2 = Frame2(self.notebook)

        self.notebook.add(self.Frame1, text='Frame1')
        self.notebook.add(self.Frame2, text='Frame2')

        self.notebook.pack()

class Frame1(ttk.Frame):
    def __init__(self, container):
        super().__init__()

        self.labelA = ttk.Label(self, text = "This is on Frame One")
        self.labelA.grid(column=1, row=1)

class Frame2(ttk.Frame):
    def __init__(self, container):
        super().__init__()

        self.labelB = ttk.Label(self, text = "This is on Frame Two")
        self.labelB.grid(column=1, row=1)

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

As you can imagine, this will allow you to create additional classes to add frames to your tab classes. The code below shows an alteration to class Frame1 above, and the addition of class Frame1FrameA which does just this.

class Frame1(ttk.Frame):
    def __init__(self, container):
        super().__init__(container)

        self.labelA = ttk.Label(self, text = "This is on Frame One")
        self.labelA.grid(column=1, row=1)

        self.frame = Frame1FrameA(self)
        self.frame.grid(row=1, columnspan=2)

class Frame1FrameA(ttk.Frame):
    def __init__(self, container):
        super().__init__(container)

        self.LabelA = ttk.Label(self, text="LabelA in FrameA in tab Frame1")
        self.LabelA.grid(column=0, row=0)

        self.LabelB = ttk.Label(self, text="LabelB in FrameA in tab Frame1")
        self.LabelB.grid(column=1, row=0)
0

The program will run if the "frame1" and "frame2" arguments are removed from the labels, it will, however, display the same thing on both frames

Error:

line 8, in __init__
    self.labelA = ttk.Label(self, frame1, text = "This is on Frame One")
TypeError: Label.__init__() takes from 1 to 2 positional arguments but 3 were given

The problem can be fix.

  • In line 8 and 11, change this ttk.Label(self,.. to ttk.Label(frame1,..

Code:

self.labelA = ttk.Label(frame1, text = "This is on Frame One")
self.labelA.grid(column=1, row=1)

self.labelB = ttk.Label(frame2, text = "This is on Frame Two")
self.labelB.grid(column=1, row=1)

Screenshot:

enter image description here

toyota Supra
  • 3,181
  • 4
  • 15
  • 19