1

My code basically does this:

my problem

Which is clearly not what I want to try. For further clarification I would like my window to look similar to this:

an ideal solution

from tkinter import *
import tkinter as tk
from tkinter import ttk

root = tk.Tk()

class Encoding(tk.Tk):
    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.mode = StringVar()
##      If I remove the next line it breaks it entirely.
        self.encoding_frame = ttk.Frame(parent)
        self.encrypt = ttk.Radiobutton(self.encoding_frame, text='Encrypt', variable=self.mode, value='encrypt')
        self.decrypt = ttk.Radiobutton(self.encoding_frame, text='Decrypt', variable=self.mode, value='decrypt')
        self.encrypt.grid(column=0, row=0, ipadx=2, sticky=W)
        self.decrypt.grid(column=0, row=1, ipadx=2, sticky=W)
        self.encoding_frame.grid(column=0, columnspan=3, row=2, sticky=S)


class MainApplication(tk.Frame, Encoding):
    # Create a main frame here.
    # Would like frames to be nested within this frame. This seems redundant but nesting with a main
    # frame allows for consistent themes, and gives additional control of layout between subframes.
    # Inheritance is confusing.
    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.main_frame = ttk.LabelFrame(parent, text="Main Window", width=500, height=500)
        self.main_frame['borderwidth'] = 3
        self.main_frame['relief'] = 'raised'
        self.main_frame.grid(column=0, row=0)
        self.encoding = Encoding(self)
##      I wrote the following line hoping that I could have main_frame become the parent frame.
        self.encoding.encoding_frame = ttk.LabelFrame(self.main_frame)


if __name__ == "__main__":
    app = MainApplication(root)
    root.mainloop()

I am clearly not getting something right. The whole reason I rewrote the program is so that I could gain a greater understanding/confidence with object oriented code. I am hoping that I can get better insight with this, so any help would be amazing.

bluedays
  • 43
  • 4
  • ***"# Inheritance is confusing."***: Avoid `multiple inheritance`, you didn't `Encoding.__init__(...`. Read [Why are multiple instances of Tk discouraged?](https://stackoverflow.com/questions/48045401/why-are-multiple-instances-of-tk-discouraged) – stovfl Oct 24 '19 at 23:04
  • Why would I avoid multiple inheritance? Also, I'm not sure that I did create multiple instances of Tk? I'm afraid that your comment is a bit confusing. Could you be more specific? – bluedays Oct 24 '19 at 23:23
  • It is not multiple inheritance should be avoided. Rather, the widgets of tkinter are already inheriting from common base class `Widget`, which also inherits from multiple classes like `BaseWidget`, `Pack`... it complicates your code unnecessarily if you try to mix it up further. – Henry Yik Oct 25 '19 at 02:15
  • Also regarding multiple instances, in your code `root = tk.Tk()` create one instance of `Tk`. Then, your `Encoding` class inherits from `tk.Tk`, which also creates another instance of `Tk` when you call it `MainApplication`. – Henry Yik Oct 25 '19 at 02:16

1 Answers1

1

There are are several problems going on with your code.

Perhaps the biggest problem is that Encoding inherits from tk.Tk, MainApplication inherits from tk.Frame and Encoding (making it both a root window and a frame), and then MainApplication creates an instance of Encoding. Plus, you explicitly create another instance of tk.Tk(), giving you two root windows. That all needs to be untangled.

Inheritance create a "is a" relationship. By having MainApplication inherit from Encoding you are saying that MainApplication is a Encoding object. That is not the case in your code - an Encoding object represents only a small part of the application. For that you want composition, not inheritance, ie: MainApplication has a Encoding object.

So, the first step is to remove Encoding from the list of classes that MainApplication inherits from.

Another thing that can probably be removed is self.encoding_frame. I see no reason to have it since MainApplication itself is a frame. Instead, have MainApplication inherit from ttk.LabelFrame rather than tk.Frame.

The final thing is that since MainApplication creates Encoding, it should be responsible for calling grid or pack on the instance of Encoding.

Altogether, MainApplication can be pared down to this:

class MainApplication(ttk.LabelFrame):
    def __init__(self, parent, *args, **kwargs):
        ttk.LabelFrame.__init__(self, parent, *args, **kwargs)

        self.configure(text="Main Window")
        self['borderwidth'] = 3
        self['relief'] = 'raised'

        self.encoding = Encoding(self)
        self.encoding.grid(row=0, column=0, sticky="ew")

That's not 100% complete, but it's a good place to start. Based on your image I'm guessing you'll have other classes for other parts of the main application -- the message widget, the key widgets, and the transcription window.

For Encoding, much of the same advice applies. Since it's only part of the application, it shouldn't inherit from tk.Tk. Instead, you can inherit from ttk.Frame and then remove self.encoding_frame since the Encoding object itself is already a frame.

With those changes, Encoding should look something like the following. Notice how the radiobuttons have self as their parent. If you're creating proper objects, all widgets inside the class need to be a child of the class itself or one of its descendants. A class like this should never put anything in parent except itself.

class Encoding(ttk.Frame):
    def __init__(self, parent, *args, **kwargs):
        ttk.Frame.__init__(self, parent, *args, **kwargs)

        self.mode = StringVar()
        self.encrypt = ttk.Radiobutton(self, text='Encrypt', variable=self.mode, value='encrypt')
        self.decrypt = ttk.Radiobutton(self, text='Decrypt', variable=self.mode, value='decrypt')

        self.encrypt.grid(column=0, row=0, ipadx=2, sticky=W)
        self.decrypt.grid(column=0, row=1, ipadx=2, sticky=W)

Finally, since MainApplication is now a frame -- instead of inheriting from Encoding which inherits from tk.Tk -- the block of code that creates an instance of MainApplication needs to be responsible for calling pack or grid. Since MainApplication is the only widget directly inside of the root window, pack is the best choice since you don't have to remember to configure row and column weights to get proper behavior when the window is resized.

Also, I recommend creating root in the same block rather than at the very start of the program.

Your bottom block of code should look like this:

if __name__ == "__main__":
    root = tk.Tk()
    app = MainApplication(root)
    app.pack(fill="both", expand=True)
    root.mainloop()
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • This is awesome, thank you so much! I was actually attempting to follow your advice on another thread that was created called "Best way to structure a tkinter application?" but I was not doing a very good job, apparently. Was not expecting to get an answer directly from you! :D I was probably over thinking it, and I should have paired it down to the basics like you did. Thank you for your help. – bluedays Oct 25 '19 at 20:48