krusty
krusty

Reputation: 350

frames in frames layout using tkinter

I want to create tkinter grid layout 5 frames. It looked the way I wanted before replacing Frame content.

self.Frame1 = tk.Frame(self.parent, bg="grey")

with

self.Frame1 = LeftUpperWindow(self.parent)

When I maximize the window I see everything in LeftUpperWindow frame in messed up, layout incorrectly. A picture is worth a thousand words so I have this

enter image description here

and would like to have this

enter image description here

import sys
import os

import Tkinter as tk
import ttk as ttk

class LeftUpperWindow(tk.Frame):

    def __init__(self, master=None):
        self.parent = master
        tk.Frame.__init__(self, self.parent, bg='#ffffff', borderwidth=1, relief="sunken")
        self.__create_layout()

    def __create_layout(self):

        self.parent.grid()

        for r in range(3):
            self.parent.rowconfigure(r, weight=1)
        for c in range(2):
            self.parent.columnconfigure(c, weight=1)

        self.editArea = tk.Text(self.parent, wrap=tk.NONE, undo=True,
                           relief=tk.SUNKEN, borderwidth=5, highlightthickness=0, insertbackground="white")

        self.editArea.config(font=('courier', 10, 'normal'))
        self.editArea.configure(bg="black", fg="white")
        self.editArea.grid(row=0, column=0, sticky=(tk.N, tk.S, tk.W, tk.E))

        self.scrollbarY = tk.Scrollbar(self.parent, orient=tk.VERTICAL)
        self.scrollbarY.grid(row=0, column=1, sticky=(tk.N, tk.S), rowspan=2)

        self.scrollbarX = tk.Scrollbar(self.parent, orient=tk.HORIZONTAL)
        self.scrollbarX.grid(row=1, column=0, sticky=(tk.W, tk.E))

        self.status = tk.Label(self.parent, text="Status label", bd=1, relief=tk.SUNKEN, anchor=tk.W,  bg='lightgray')
        self.status.grid(row=2, column=0, sticky=(tk.N, tk.S, tk.W, tk.E), columnspan=2)

    def __config_window(self):
        pass

    def close_quit(self, event=None):
        self.parent.destroy()

    def dummy_func(self):
        print("dummy")
        pass

class MainWindow(tk.Frame):
    def __init__(self, master=None):
        self.parent = master
        tk.Frame.__init__(self, self.parent, bg='#ffffff', borderwidth=1, relief="sunken")
        self.__create_layout()
        self.__create_menu()
        self.__config_mainWindow()

    def __create_layout(self):
        self.parent.grid()

        for r in range(6):
            self.parent.rowconfigure(r, weight=1)
        for c in range(10):
            self.parent.columnconfigure(c, weight=1)

        self.Frame1 = LeftUpperWindow(self.parent)  #tk.Frame(self.parent, bg="grey")
        self.Frame1.grid(row=0, column=0, rowspan=4, columnspan=8, sticky=(tk.N, tk.S, tk.W, tk.E))

        self.Frame2 = tk.Frame(self.parent, bg="blue")
        self.Frame2.grid(row=0, column=8, rowspan=4, columnspan=2, sticky=(tk.N, tk.S, tk.W, tk.E))

        self.Frame3 = tk.Frame(self.parent, bg="green")
        self.Frame3.grid(row=4, column=0, rowspan=2, columnspan=5, sticky=(tk.N, tk.S, tk.W, tk.E))

        self.Frame4 = tk.Frame(self.parent, bg="brown")
        self.Frame4.grid(row=4, column=5, rowspan=2, columnspan=5, sticky=(tk.N, tk.S, tk.W, tk.E))

        self.Frame5 = tk.Frame(self.parent, bg="pink")
        self.Frame5.grid(row=5, column=0, rowspan=1, columnspan=10, sticky=(tk.N, tk.S, tk.W, tk.E))

    def close_quit(self, event=None):
        self.parent.destroy()

    def __config_mainWindow(self):
        self.parent.config(menu=self.menubar)

    def __create_menu(self):
        self.menubar = tk.Menu(self.parent)

        self.filemenu = tk.Menu(self.menubar, tearoff=0)
        self.filemenu.add_command(label="Open", command=self.dummy_func)
        self.filemenu.add_separator()

        self.filemenu.add_command(label="Exit", command=self.close_quit)
        self.menubar.add_cascade(label="File", menu=self.filemenu)

        helpmenu = tk.Menu(self.menubar, tearoff=0)
        helpmenu.add_command(label="About...", command=self.dummy_func)
        self.menubar.add_cascade(label="Help", menu=helpmenu)

    def dummy_func(self):
        print("dummy")
        pass

#
#   MAIN
#
def main():
    root = tk.Tk()

    root.title("Frames")
    root.geometry("550x300+525+300")

    root.configure(background="#808080")
    root.option_add("*font", ("Courier New", 9, "normal"))

    window = MainWindow(master=root)
    root.protocol("WM_DELETE_WINDOW", window.close_quit)
    root.mainloop()

if __name__ == '__main__':
    main()

Upvotes: 1

Views: 2447

Answers (1)

Bryan Oakley
Bryan Oakley

Reputation: 386362

Overview

There is no quick fix for your problem. You are doing several things in your code that stack the odds against you, and which makes it really hard to debug your code. A rewrite is the best solution.

Here are things I've found that make the problem more difficult than they need to be, and which can be solved by a rewrite.

First, don't have a widget be responsible for placing itself in its parent. In your case you're going one step further by having a widget place it's parent into its grandparent. That's simply the wrong way to do it. A containing window should be responsible for layout out it's children, and nothing more

Second, put as much of your layout code together as possible. By "all" I mean "all for a given container". So, for example, instead of this:

x=Label(...)
x.grid(...)
y =Label(...)
y.grid(...)

... do it like this:

x = Label(...)
y = Label(...)
x.grid(...)
y.grid(...)

This makes it much easier to visualize your layout in the code.

Third, don't try to solve multiple layout problems at once. Solve one problem at a time. Since you posted your whole program, I assume you have a problem with the whole program and not just the upper-left widgets. When I ran your code I observed resize behavior that seemed off to me, reinforcing that belief.

Fourth, if you create a class like MainWindow that is designed to contain other widgets, all of these other widgets shild be children of the window, not children of the parent.

Fifth, as a general rule of thumb you want only a single row and single column with a container to have a non-zero weight. This is only a guideline, because there are often times when you want more than one widget to expand or contract. In the case of scrollbars and statuslabels, you typically don't want them to be given extra space. Part of the problem in your code is that you give everything a weight, so extra space is allocated to every widget.

The solution

The solution is work on one section at a time. Get it working, then move on to the next section. In your case I see the effort breaking down into four phases. First, get the main window created and laid out. Next, add the widgets that are immediate children of the main window. After that, create the container for the upper-left window. Finally, add the widgets to the upper left window. After each phase, be sure to run the code and verify that everything looks right.

The main window

The first step is to start with the main window. Get it to look right, and behave properly when you resize the main window. When I ran your code the bottom and right windows would vanish when the window became small, and the bottom frames grew when the window was large. I'm guessing that's not the correct behavior. Regardless, start with what you think is the correct behavior and make it work before adding any more widgets.

Start with the following code. Verify that when you resize the window, everything expands and contracts the way you expect. Notice that I made a couple of simple changes. The main thing to notice is that the function that creates the main window is responsible for making that window visible. I use pack since it's the only widget in the root window, and it means I can do it all in one line without having to worry about row and column weights with grid.

import sys
import os

import Tkinter as tk
import ttk as ttk

class MainWindow(tk.Frame):
    def __init__(self, master=None):
        self.parent = master
        tk.Frame.__init__(self, self.parent, bg='bisque', borderwidth=1, relief="sunken")
        self.__create_layout()

    def __create_layout(self):
        pass

#
#   MAIN
#
def main():
    root = tk.Tk()

    root.title("Frames")
    root.geometry("550x300+525+300")

    root.configure(background="#808080")
    root.option_add("*font", ("Courier New", 9, "normal"))

    window = MainWindow(master=root)
    window.pack(side="top", fill="both", expand=True)
    root.mainloop()

if __name__ == '__main__':
    main()

The widgets inside of MainWindow

Once you have the MainWindow shell behaving properly, it's time to add more widgets. It looks like you have five frames, though one of them is a custom class. For now we'll use a regular frame to keep things simple. It's good that you give each one a color, that's a great way to visualize the layout as you build up the window.

Modify __create_layout to look like the following. Note two important changes: every widget is a child of self rather than self.parent, and I've grouped all of the layout code together.

The resize behavior looks off to me, but I don't know exactly what you intend. For me it seems odd to have the green, red and pink frames resize the way they do, though maybe that's what you want. Regardless, now is the time to get it right.

def __create_layout(self):
    self.Frame1 = tk.Frame(self, bg="grey")
    self.Frame2 = tk.Frame(self, bg="blue")
    self.Frame3 = tk.Frame(self, bg="green")
    self.Frame4 = tk.Frame(self, bg="brown")
    self.Frame5 = tk.Frame(self, bg="pink")

    self.Frame1.grid(row=0, column=0, rowspan=4, columnspan=8, sticky=(tk.N, tk.S, tk.W, tk.E))
    self.Frame2.grid(row=0, column=8, rowspan=4, columnspan=2, sticky=(tk.N, tk.S, tk.W, tk.E))
    self.Frame3.grid(row=4, column=0, rowspan=2, columnspan=5, sticky=(tk.N, tk.S, tk.W, tk.E))
    self.Frame4.grid(row=4, column=5, rowspan=2, columnspan=5, sticky=(tk.N, tk.S, tk.W, tk.E))
    self.Frame5.grid(row=5, column=0, rowspan=1, columnspan=10, sticky=(tk.N, tk.S, tk.W, tk.E))

    for r in range(6):
        self.rowconfigure(r, weight=1)
    for c in range(10):
        self.columnconfigure(c, weight=1)

The LeftUpperWindow widget

Next, let's define the LeftUpperWindow class, and make sure we haven't broken anything. Add a stub for the class, give the window a distinct color, and make sure the window still looks ok when it appears and when it is resized.

class LeftUpperWindow(tk.Frame):
    def __init__(self, master=None):
        self.parent = master
        tk.Frame.__init__(self, self.parent, bg='bisque', borderwidth=1, relief="sunken")
        self.__create_layout()

    def __create_layout(self):
        pass

Remember to modify MainWindow.__create_layout to create an instance of this class rather than frame:

self.frame1 = LeftUpperWindow(self)

The widgets in LeftUpperWindow

Finally, it's time to add the widgets in LeftUpperWindow. Like we did in MainWindow, the widgets are all children of self, and the widget creation and widget layout are done in separate groups. Also, it's usually best to only give a single row and a single column a non-zero weight. This is usually the row and column that a text or canvas widget is in.

You can do what you want, of course. Just be mindful of the fact that because you gave each row and column a weight in your original code, that contributed to all of the space between widgets.

Also, because you're giving the GUI an explicit size (via root.geometry(...)), you want to give the text widget a small size. Otherwise, the default width and height will push out the other widgets since tkinter tries to give each widget its default size. Since you're constraining the overall window size you want the text widget to be as small as possible, and then let grid expand it to fill the area it's been given.

Here's what __create_layout should look like:

def __create_layout(self):
    self.editArea = tk.Text(self, wrap=tk.NONE, undo=True,
                            width=1, height=1,
                            relief=tk.SUNKEN, borderwidth=5, 
                            highlightthickness=0, insertbackground="white")
    self.editArea.config(font=('courier', 10, 'normal'))
    self.editArea.configure(bg="black", fg="white")

    self.scrollbarY = tk.Scrollbar(self, orient=tk.VERTICAL)
    self.scrollbarX = tk.Scrollbar(self, orient=tk.HORIZONTAL)
    self.status = tk.Label(self, text="Status label", bd=1, relief=tk.SUNKEN, 
                           anchor=tk.W,  bg='lightgray')

    self.editArea.grid(row=0, column=0, sticky=(tk.N, tk.S, tk.W, tk.E))
    self.scrollbarY.grid(row=0, column=1, sticky=(tk.N, tk.S), rowspan=2)
    self.scrollbarX.grid(row=1, column=0, sticky=(tk.W, tk.E))
    self.status.grid(row=2, column=0, sticky=(tk.N, tk.S, tk.W, tk.E), columnspan=2)

    self.rowconfigure(0, weight=1)
    self.columnconfigure(0, weight=1)

Upvotes: 6

Related Questions