12

I am building a GUI for a software and want to achieve this:

######################################
#             |      some title      #
# menu upper  |----------------------#
#             |                      #
#             |         CANVAS       #
# menu lower  |                      #
#             |                      #
#------------------------------------#
#              statusbar             #
######################################

Menu upper has some high level functionality, menu lower is changing in dependency of user input. Statusbar changes its contents often.


Unfortunately, Tkinter refuses to work.

Using the grid layout manager I were unable to create a stable design and adding content like labels and buttons to the menu on the left side:

self.root = tk.Tk()
self.root.resizable(width=0, height=0)
self.root.title("some application")

# menu left
self.menu_left = tk.Frame(self.root, width=150, bg="#ababab")
self.menu_left.grid(row=0, column=0, rowspan=2, sticky="ns")

self.menu_left_upper = tk.Frame(self.menu_left, width=150, height=150, bg="red")
self.menu_left_upper.grid(row=0, column=0)

# this label breaks the design   
#self.test = tk.Label(self.menu_left_upper, text="test")
#self.test.pack()

self.menu_left_lower = tk.Frame(self.menu_left, width=150, bg="blue")
self.menu_left_lower.grid(row=1, column=0)

# right area
self.some_title_frame = tk.Frame(self.root, bg="#dfdfdf")
self.some_title_frame.grid(row=0, column=1, sticky="we")

self.some_title = tk.Label(self.some_title_frame, text="some title", bg="#dfdfdf")
self.some_title.pack()

self.canvas_area = tk.Canvas(self.root, width=500, height=400, background="#ffffff")
self.canvas_area.grid(row=1, column=1)

self.root.mainloop()

This design worked without contents in the menu on the left side. Whenever I added something in self.menu_left_upper or self.menu_left_lower, like the test label, my design got broke: the menu frames disappeared. Additionally, even with columnspan, I had to remove the statusbar, because when it was added the menus on the left disappeared, again.


Using pack layout manager I got this:

######################################
#             |      some title      #
#             |----------------------#
# menu upper  |                      #
#             |         CANVAS       #
#             |                      #
# menu lower  |                      #
#             |----------------------#
#             |       statusbar      #
######################################

Since I wanted the menu frame on the left to consume the full y-space I made it grow with pack(side="left", expand=True, fill="both"), but this setup always denies the statusbar to go for the full width.

Besides, the pure pack manager code looks "ugly". I think a design with a grid manager is "clearer". Therefore I thought a grid or a pack layout inside a grid layout would be nice?


Can anyone help? I am stuck in the GUI-hell :/

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
daniel451
  • 10,626
  • 19
  • 67
  • 125
  • Are you asking for help using grid, or for help using pack, or do you not care what is used as long as it gives the desired look? How does `pack` code look ugly? It's just code. – Bryan Oakley Apr 08 '16 at 18:54
  • I do not care what is being used, as long as it is working. However, I would prefer the grid manager, since a grid layout makes more sense to me. It is easy to understand and to expand, while using lots of `pack()`'s with different `side=...` attributes looks kind of confusing to me. – daniel451 Apr 08 '16 at 18:54
  • Do you want "menu upper" and "menu lower" to each share 50% of the area? – Bryan Oakley Apr 08 '16 at 18:55
  • Preferably yes. I tried to play with the `weight=...` attribute, but in my attempts this did not work, too. – daniel451 Apr 08 '16 at 19:00

3 Answers3

26

The key to doing layout is to be methodical, and to use the right tool for the job. It also means that sometimes you need to be creative. That means using grid when laying things out in a grid, and using pack when laying things out top-to-bottom or left-to-right.

The other key is to group your layout code together. It's much, much easier to visualize and modify the layout when it's all in one block of code.

In your case you seem to have three or four distinct areas, depending on how you count. If you want to use grid, it will be easiest to combine "menu upper" and "menu lower" into a frame, and treat that whole frame as a table cell. It looks like you're already doing that, which is good.

So, let's start with those main areas:

self.menu_left.grid(row=0, column=0, rowspan=2, sticky="nsew")
self.some_title_frame.grid(row=0, column=1, sticky="ew")
self.canvas_area.grid(row=1, column=1, sticky="nsew") 
# you don't have a status bar in the example code, but if you
# did, this is where you would put it
# self.status_frame.grid(row=2, column=0, columnspan=2, sticky="ew")

Any time you use grid, you need to give at least one row and one column a positive weight so that tkinter knows where to use any unallocated space. Usually there is one widget that is the "main" widget. In this case it's the canvas. You want to make sure that the row and column for the canvas has a weight of 1 (one):

self.root.grid_rowconfigure(1, weight=1)
self.root.grid_columnconfigure(1, weight=1)

note: using pack instead of grid would save you two lines of code, since pack doesn't require you to set weights the way grid does.

Next, we need to solve the problem of the menu areas. By default, frames shrink to fit their contents, which is why adding the label broke your layout. You weren't telling tkinter what to do with extra space, so the frames shrunk to fit, and extra space went unused.

Since you want "menu_upper" and "menu_lower" to each share 50% of that area, pack is the simplest solution. You can use grid, but it requires more lines of code to add the row and column weights.

self.menu_left_upper.pack(side="top", fill="both", expand=True)
self.menu_left_lower.pack(side="top", fill="both", expand=True)

Here is a functioning version, with statusbar. Notice how it behaves exactly as it should when you resize the window:

import Tkinter as tk

class Example():
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("some application")

        # menu left
        self.menu_left = tk.Frame(self.root, width=150, bg="#ababab")
        self.menu_left_upper = tk.Frame(self.menu_left, width=150, height=150, bg="red")
        self.menu_left_lower = tk.Frame(self.menu_left, width=150, bg="blue")

        self.test = tk.Label(self.menu_left_upper, text="test")
        self.test.pack()

        self.menu_left_upper.pack(side="top", fill="both", expand=True)
        self.menu_left_lower.pack(side="top", fill="both", expand=True)

        # right area
        self.some_title_frame = tk.Frame(self.root, bg="#dfdfdf")

        self.some_title = tk.Label(self.some_title_frame, text="some title", bg="#dfdfdf")
        self.some_title.pack()

        self.canvas_area = tk.Canvas(self.root, width=500, height=400, background="#ffffff")
        self.canvas_area.grid(row=1, column=1)

        # status bar
        self.status_frame = tk.Frame(self.root)
        self.status = tk.Label(self.status_frame, text="this is the status bar")
        self.status.pack(fill="both", expand=True)

        self.menu_left.grid(row=0, column=0, rowspan=2, sticky="nsew")
        self.some_title_frame.grid(row=0, column=1, sticky="ew")
        self.canvas_area.grid(row=1, column=1, sticky="nsew") 
        self.status_frame.grid(row=2, column=0, columnspan=2, sticky="ew")

        self.root.grid_rowconfigure(1, weight=1)
        self.root.grid_columnconfigure(1, weight=1)

        self.root.mainloop()

Example()

On an unrelated note: I would strongly encourage you to not remove the ability for the user to resize the window. They know better than you what their requirements are. If you use grid and pack properly, the GUI will resize perfectly.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
1

Adding the following code right before self.root.mainloop() achieves what you're looking for

self.some_status = tk.Label(self.root, text="status bar", bg="#dfdfdf")
self.some_status.grid(row=3, column=0, columnspan=2, sticky="we")
Kidus
  • 1,785
  • 2
  • 20
  • 36
  • True, but it does not resolve the odd error that the menu bar on the left disappears when I add content there. If I do this `self.test = tk.Label(self.menu_left_upper, text="test")` and `self.test.pack()` or `self.test.grid()` the menu upper / lower on the left disappears. – daniel451 Apr 08 '16 at 18:28
  • did you try giving the `self.test` some padding (`padx` and `pady`)? – Kidus Apr 08 '16 at 19:20
1

By putting in the line:

menu_left_upper.grid_propagate(False) 

In between your menu_left_upper Frame and menu_left_upper.mainloop()

This works as:

By default, a container widget expands or collapses to be just big enough to hold its contents. Thus, when you call pack, it causes the frame to shrink. This feature is called geometry propagation.

For the vast majority of applications, this is the behavior you want. For those rare times when you want to explicitly set the size of a container you can turn this feature off. To turn it off, call either pack_propagate or grid_propagate on the container (depending on whether you're using grid or pack on that container), giving it a value of False.

See link to another question where this came from

To get your status bar just implement another frame and grid method:

status_bar_frame = Frame(root, bg="#dfdfdf")
status_bar_frame.grid(row=3, column=0, columnspan=2, sticky="we")

status_bar = Label(status_bar_frame, text="status bar", bg="#dfdfdf")
status_bar.pack()

Then your plan works. Hope it helps :)

PS. Also why all the self attributes?

EDIT: TO work you need to do:

menu_left_upper = Frame(menu_left, width=225, height=225, bg="red")
menu_left_upper.grid_propagate(False)
menu_left_upper.grid(row=0, column=0)

# this label breaks the design   
test = Label(menu_left_upper, text="test", bg='red')
test.grid()

My result

Community
  • 1
  • 1
Benno_S
  • 69
  • 1
  • 8
  • Why the downvote? Slightly confused as to why someone's done that... answers the question and gets the plan :( – Benno_S Apr 08 '16 at 18:45
  • Thanks, will test this. The downvote wasn't me. So constructing Frames and managing them on the top level with grid manager, while manage a frame's content with a pack manager, is legal? – daniel451 Apr 08 '16 at 18:50
  • I tried every possible combination of `.grid_propagate(False)`: assigning this to `menu_left` only, to `menu_left_upper` only, to `menu_left_lower` only or combinations of these. None has worked. The menu and the sub-frames display correctly **without** content, but as soon as e.g. the `test` label is added to `menu_left_upper` the frame disappears. – daniel451 Apr 08 '16 at 18:59
  • Formatting is rubbish see edit. You need to put it before the `menu_left_upper.grid` method – Benno_S Apr 08 '16 at 19:05
  • 2
    @ascenator: _"So constructing Frames and managing them on the top level with grid manager, while manage a frame's content with a pack manager, is legal?"_ Not only legal, _preferable_. Or more correctly, use the right tool for the job. Use `pack` when `pack` makes sense, use `grid` when `grid` makes sense. The only key is you can't use them together for widgets that share a common parent. – Bryan Oakley Apr 08 '16 at 19:07
  • `grid_propagate` is rarely the right answer, and isn't needed to solve this problem. – Bryan Oakley Apr 08 '16 at 19:08
  • Oh, alright. How would you do it? (please bear in mind I'm trying to help and genuinely interested, no sarcasm intended) – Benno_S Apr 08 '16 at 19:10
  • @Benno_S: in this case, the two menu items are in a frame; either use pack and have them each expand and fill, or use grid and give both rows the same weight. That's all it takes. – Bryan Oakley Apr 08 '16 at 19:15