2

I try to put the widgets like this:

enter image description here

I don't understand why my code doesn't do that, tried to look for examples online but didn't find a solution and nothing I tried brought me closer to the requested result.

This is my code so far(if you have any comments about anything in the code feel free to tell me because it's my first try with and GUIs in general):

from Tkinter import *


class box(object):

    def __init__ (self, colour,s):
        self.root = root
        self.listbox = Listbox(self.root, fg = colour, bg = 'black')
        self.s = s
        self.place_scrollbar()
        self.listbox.pack(side = self.s)


    def place_scrollbar(self):
        scrollbar = Scrollbar(self.root)
        scrollbar.pack(side = self.s, fill = Y)
        self.listbox.config(yscrollcommand = scrollbar.set)
        scrollbar.config(command = self.listbox.yview)

    def write(self, contenet):
        self.listbox.insert(END, contenet)

root = Tk()
root.resizable(False, False)
boxs = Frame(root)
boxs.pack()
box.root = boxs
server = box("red", LEFT)
client = box("green", RIGHT )
bf = Frame(root)
bf.pack(side = BOTTOM)
entry = Entry(bf,bg ='black', fg = 'white')
entry.pack()
root.mainloop()
Nae
  • 14,209
  • 7
  • 52
  • 79
  • What are you trying to do? Perhaps provide an image? – Nae Feb 09 '18 at 19:55
  • @Nae Is there a particular way to create an image to show you, or should I use a basic image editing softwere ? –  Feb 09 '18 at 19:57
  • Any sketch should be enough. – Nae Feb 09 '18 at 19:57
  • See [here](https://stackoverflow.com/posts/48713075/revisions) for revision history. I may have over-edited the question. – Nae Feb 09 '18 at 22:00

2 Answers2

3

You can't do this without using an additional frame to contain the box objects while still using pack, while still maintaining resizability.

But it is more organized in some cases to: use an additional frame to contain your box objects, by initializing it with a parent option.

Right now the widgets inside the box class are children to global root object. Which isn't really a good practice. So let's first pass and use a parent object to be used for widgets inside.

Replace:

def __init__ (self, colour,s):
    self.root = root
    self.listbox = Listbox(self.root, ...)
    ...


def place_scrollbar(self):
    scrollbar = Scrollbar(self.root)
    ...

with:

def __init__ (self, parent, colour,s):
    self.parent= parent
    self.listbox = Listbox(self.parent, ...)
    ...


def place_scrollbar(self):
    scrollbar = Scrollbar(self.parent)
    ...

This makes it so that you now need to initialize the object like the following:

server = box(root, "red", LEFT)
client = box(root, "green", RIGHT )

Now that we can pass a parent widget, let's create a parent frame to contain them. Actually, there's an un-used frame already, boxs let's use that by passing it as the parent as opposed to root:

server = box(boxs, "red", LEFT)
client = box(boxs, "green", RIGHT )

Now everything looks fine, optionally if you want to make it so that entry occupies as much left space as possible currently add fill='x' as an option to the pack of both the entry and the frame that contains it:

bf.pack(side = BOTTOM, fill='x')
...
entry.pack(fill='x')

Your whole code should look like:

from Tkinter import *


class box(object):

    def __init__ (self, parent, colour,s):
        self.parent = parent
        self.listbox = Listbox(self.parent, fg = colour, bg = 'black')
        self.s = s
        self.place_scrollbar()
        self.listbox.pack(side = self.s)


    def place_scrollbar(self):
        scrollbar = Scrollbar(self.parent)
        scrollbar.pack(side = self.s, fill = Y)
        self.listbox.config(yscrollcommand = scrollbar.set)
        scrollbar.config(command = self.listbox.yview)

    def write(self, contenet):
        self.listbox.insert(END, contenet)

root = Tk()
root.resizable(False, False)
boxs = Frame(root)
boxs.pack()
box.root = boxs
server = box(boxs, "red", LEFT)
client = box(boxs, "green", RIGHT )
bf = Frame(root)
bf.pack(side = BOTTOM, fill='x')
entry = Entry(bf,bg ='black', fg = 'white')
entry.pack(fill='x')
root.mainloop()

Or: use grid instead of pack (with columnspan=2 option for entry).


General Answer

More generally putting a widget beneath two widgets that are side-by-side can be done by:

Encapsulating the side-by-side widgets with a frame, and then simply putting the frame above the other widget:

try:                        # In order to be able to import tkinter for
    import tkinter as tk    # either in python 2 or in python 3
except ImportError:
    import Tkinter as tk


def main():
    root = tk.Tk()
    side_by_side_widgets = dict()
    the_widget_beneath = tk.Entry(root)
    frame = tk.Frame(root)
    for name in {"side b", "y side"}:
        side_by_side_widgets[name] = tk.Label(frame, text=name)
        side_by_side_widgets[name].pack(side='left', expand=True)
    frame.pack(fill='x')
    the_widget_beneath.pack()
    root.mainloop()


if __name__ == '__main__':
    main()

Using grid:

try:                        # In order to be able to import tkinter for
    import tkinter as tk    # either in python 2 or in python 3
except ImportError:
    import Tkinter as tk


def main():
    root = tk.Tk()
    side_by_side_widgets = dict()
    the_widget_beneath = tk.Entry(root)
    for index, value in enumerate({"side b", "y side"}):
        side_by_side_widgets[value] = tk.Label(root, text=value)
        side_by_side_widgets[value].grid(row=0, column=index)
    the_widget_beneath.grid(row=1, column=0, columnspan=2)
    root.mainloop()


if __name__ == '__main__':
    main()

Without using additional frames, by calling pack for the_widget_beneath with side='bottom' as the first pack call, as in Bryan's comment:

try:                        # In order to be able to import tkinter for
    import tkinter as tk    # either in python 2 or in python 3
except ImportError:
    import Tkinter as tk


def main():
    root = tk.Tk()
    side_by_side_widgets = dict()
    the_widget_beneath = tk.Entry(root)
    the_widget_beneath.pack(side='bottom')
    for name in {"side b", "y side"}:
        side_by_side_widgets[name] = tk.Label(root, text=name)
        side_by_side_widgets[name].pack(side='left', expand=True)
    root.mainloop()


if __name__ == '__main__':
    main()

You can more easily notice reliability to global objects by creating a global main method, and add main-body of your script there and call:

...
def main():
    root = Tk()
    root.resizable(False, False)
    boxs = Frame(root)
    boxs.pack()
    box.root = boxs
    server = box(boxs, "red", LEFT)
    client = box(boxs, "green", RIGHT )
    bf = Frame(root)
    bf.pack(side = BOTTOM, fill='x')
    entry = Entry(bf,bg ='black', fg = 'white')
    entry.pack(fill='x')
    root.mainloop()

if __name__ == '__main__':
    main()
Community
  • 1
  • 1
Nae
  • 14,209
  • 7
  • 52
  • 79
  • 1
    Well, I tried using the additional frame to contain the box objects,(boxs ) but it still dosent work. –  Feb 09 '18 at 20:09
  • 1
    I actualy meant to use the boxs as the frame for the boxes but I had a little bug :D. The line "self.root = root" was from a previous try, and overide the box.root = boxs, that did the same as you. Thanks a lot for the help. –  Feb 09 '18 at 20:32
  • 1
    Actually, you _can_ get the layout the user wants without any other frames (based on the image provided). However, they code the user provided includes more than just three widgets, and that's when your advice is appropriate. – Bryan Oakley Feb 09 '18 at 21:38
2

How to put a widget beneath two widgets that are side-by-side using pack?

For a very simple layout like in your diagram, you simply need to pack the thing on the bottom first. That is because pack uses a "cavity" model. Each widget is organized in an unfilled cavity. Once that widget has been placed, that portion of the cavity is filled, and is unavailable for any other widgets.

In your case, you want the bottom cavity to be filled with the entry widget, so you should pack it first. Then, in the remaining upper cavity you can place your two frames side-by side, one on the left and one on the right.

For example:

import Tkinter as tk

root = tk.Tk()

entry = tk.Entry(root)
frame1 = tk.Frame(root, width=100, height=100, background="red")
frame2 = tk.Frame(root, width=100, height=100, background="green")

entry.pack(side="bottom", fill="x")
frame1.pack(side="left", fill="both", expand=True)
frame2.pack(side="right", fill="both", expand=True)

root.mainloop()

In the body of your question things get a bit more complicated, as you don't just have three widgets like your title suggests, you have several, with some being packed in the root and some being packed elsewhere, with pack statements scattered everywhere.

When using pack, it's best to group widgets into vertical or horizontal slices, and not mix top/bottom with left/right within the same group. It almost seems like you're trying to do that with your boxes, but then you don't -- your "box" is actually two widgets.

Bottom line: be organized. It also really, really helps if all of your pack (or place or grid) statements for a given parent are all in the same block of code. When you scatter them around it makes it impossible to visualize, and impossible to fix. Also, make sure that widgets have the appropriate parents.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Please note that I edited the title to make it more readable, it may be that it caused too much deviation from the actual question. – Nae Feb 09 '18 at 22:01