Python – Tkinter / Python grid layout with listboxes

pythontkinteruser interfacewidget

I'm having some trouble getting everything to line up properly using a grid with Tkinter and Python. I want to have the menu bar at the very top, and then two listboxes (one on each side), with a couple buttons between them in the middle of the window.

Currently, both listboxes are appearing on the bottom right side of the window, and the buttons are in the upper left corner, overlapping the menu buttons.

How do I deal with the grid layout of a listbox considering their size. Can they occupy more than one grid cell? For example, if I place a listbox in (row=1, column = 1) and another in row (row = 1, column = 2), would Tkinter automatically widen the cell so that it's the width of the listbox, or would it just overlap the listboxes?

def __init__(self,root):
    self.frame = Frame(root, width=500)
    self.frame.pack()
    self.BuildMainWindow(self.frame)
    self.ListboxSet = 0 
    self.frame.grid()
    return

#displays the menu bar and options
def BuildMainWindow(self, frame):
    menubar = Frame(frame,relief=RAISED,borderwidth=1)
    menubar.pack()

    mb_file = Menubutton(menubar,text='file')
    mb_file.menu = Menu(mb_file)
    mb_file.menu.add_command(label='open', command = self.openfile)
    mb_file.grid(row = 1, column = 1)

    mb_edit = Menubutton(menubar,text='edit')
    mb_edit.menu = Menu(mb_edit)
    mb_edit.grid(row=1, column = 3)

    mb_file['menu'] = mb_file.menu
    mb_edit['menu'] = mb_edit.menu

#upon selecting a directory from the menu and listboxes/buttons are created
def BuildListbox(self, directory):
    self.listbox1 = Tkinter.Listbox()
    self.listbox2 = Tkinter.Listbox()
    self.listbox1.grid(row=2, column=1)
    self.listbox2.grid(row=2 ,column=10)
    self.listbox2.bind('<<ListboxSelect>>', lambda e:SortActions.GetWindowIndex(self,e))
    self.listbox2.bind('<B1-Motion>', lambda e:SortActions.MoveWindowItem(self,e))

    i = 0
    for filename in sorted(os.listdir(directory)):
        self.listbox1.insert(i, filename)
        i = i + 1

    self.bAddToListTwo = Button(self.frame, text = "->", command = lambda:SortActions.AddToListTwo(self,self.listbox1.curselection()))
    self.bAddToListTwo.grid(row=5, column = 5)
    self.bAddAll = Button(self.frame, text = "Add All To Playlist", command = lambda:SortActions.AddAllFiles(self))
    self.bAddAll.grid(row=6, column = 5)
    self.bRemoveFromListTwo = Button(self.frame, text = "Remove From Playlist", command = lambda:SortActions.RemoveFromListTwo(self,self.listbox2.curselection()))
    self.bRemoveFromListTwo.grid(row=7, column = 5)
    self.bSavePlaylist = Button(self.frame, text = "Save Playlist", command = lambda:SortActions.SaveList(self))
    self.bSavePlaylist.grid(row=8, column = 5)

update – I got it to work. As I suspected, I had some weird frame configuration during the initialization.

def __init__(self,root):
    self.BuildMainWindow(root)
    self.ListboxSet = 0 
    return

#displays the menu bar and options
def BuildMainWindow(self, root):
    menubar = Menu(root)
    root.config(menu=menubar)

    # Create a menu button labeled "File" that brings up a menu
    filemenu = Menu(menubar)
    menubar.add_cascade(label='File', menu=filemenu)

    # Create entries in the "File" menu
    # simulated command functions that we want to invoke from our menus
    filemenu.add_command(label='Open', command=self.openfile)
    filemenu.add_separator(  )
    filemenu.add_command(label='Quit', command=sys.exit)

#upon selecting a directory from the menu and listboxes/buttons are created
def BuildListbox(self, directory):
    self.listbox1 = Tkinter.Listbox()
    self.listbox2 = Tkinter.Listbox()
    self.listbox1.grid(row=1, column=1, rowspan = 4, columnspan = 5)
    self.listbox2.grid(row=1 ,column=15, rowspan = 4, columnspan = 5)
    self.listbox2.bind('<<ListboxSelect>>', lambda e:SortActions.GetWindowIndex(self,e))
    self.listbox2.bind('<B1-Motion>', lambda e:SortActions.MoveWindowItem(self,e))

    i = 0
    for filename in sorted(os.listdir(directory)):
        self.listbox1.insert(i, filename)
        i = i + 1

    self.bAddToListTwo = Button(text = "->", command = lambda:SortActions.AddToListTwo(self,self.listbox1.curselection()))
    self.bAddToListTwo.grid(row=1, column = 6)
    self.bAddAll = Button(text = "Add All To Playlist", command = lambda:SortActions.AddAllFiles(self))
    self.bAddAll.grid(row=2, column = 6)
    self.bRemoveFromListTwo = Button(text = "Remove From Playlist", command = lambda:SortActions.RemoveFromListTwo(self,self.listbox2.curselection()))
    self.bRemoveFromListTwo.grid(row=3, column = 6)
    self.bSavePlaylist = Button(text = "Save Playlist", command = lambda:SortActions.SaveList(self))
    self.bSavePlaylist.grid(row=4, column = 6)

Best Answer

Tkinter widgets can occupy more than 1 grid-cell, but you need to manage the layout yourself using the columnspan and rowspan keywords to .grid.

For example, to get the layout:

 --------------------------------------------
 |      Button1      |  Button2 |           |
 --------------------------------  Button3  |
 | Label1  |  Label2 |  Label3  |           |
 --------------------------------------------

You'd need to do something like:

Button1.grid(row=0,column=0,columnspan=2)
Button2.grid(row=0,column=2)
Button3.grid(row=0,column=4,rowspan=2)
Label1.grid(row=1,column=0)
Label2.grid(row=1,column=1)
Label3.grid(row=1,column=2)

very minimal script

import Tkinter as tk

root = tk.Tk()
Button1 = tk.Button(root,text="Button1")
Button2 = tk.Button(root,text="Button2")
Button3 = tk.Button(root,text="Button3")
Label1 = tk.Label(root,text="Label1")
Label2 = tk.Label(root,text="Label2")
Label3 = tk.Label(root,text="Label3")

Button1.grid(row=0,column=0,columnspan=2)
Button2.grid(row=0,column=2)
Button3.grid(row=0,column=4,rowspan=2)
Label1.grid(row=1,column=0)
Label2.grid(row=1,column=1)
Label3.grid(row=1,column=2)

root.mainloop()