In our last post we found that the list box was a waste of time for its task, unless you want one-word selections. We hacked our way around it for the meantime, using the underscore character. We left off needing a scroll bar to scroll through our survey results. There was also a bug in the layout. The window looked like this:
The combo boxes with the type and action are on the bottom of the window. Our code reads:
penguinType_combobox.grid(row =1, column = 0) penguinAction_combobox.grid(row=1, column = 4)
They are supposed to be between the buttons and the label, but are on the bottom instead. The penguin action Combobox
is also out in right field all by itself. We really need to clean up this layout.
Adding the Scroll Bar
The Scrollbar
widget is a bit tricky to add, though it is only two lines. Add this just above the code for the Listbox
in our view’s loadView()
method:
dataList_yScroll = Scrollbar(self.frame, orient=VERTICAL) dataList_yScroll.grid(row=5,column=1,sticky = NS)
In line one, we set the orientation to vertical on our scroll bar. In line two we set the bar to a north-south stickiness. Always remember to make a scroll bar sticky in the proper direction. If you don’t, it will collapse itself into a little ball and won’t work. Save and run, and you see nothing new.
It turns out we have redundant code causing problems:
dataList_listbox = Listbox(self.frame, listvariable= self.dataList, width = 40) dataList_listbox.grid(row=5, column = 0, columnspan = 4,rowspan = 4, sticky=NSEW)
In line 1 the code defines the Listbox
attribute width
as 40 characters wide. In line 2, the columnspan
attribute extends the column to four columns wide. This extends the list box over several columns, including the one with the scroll bar. We set the width when we instantiated the list box. We only need one column. Remove the spans:
dataList_listbox = Listbox(self.frame, listvariable= self.dataList, width = 40) dataList_listbox.grid(row=5, column = 0, sticky=NSEW)
Save and Run:

We now have a scroll bar that does nothing. Bind it to the list box. Add this code after the code for the list box:
dataList_listbox.configure(yscrollcommand = dataList_yScroll.set) dataList_yScroll.configure(command= dataList_listbox.yview)
If working with a horizontal scroll bar, change all the y’s for x’s. Save and run this code. Fill the list box with more entries than you can see. Now click the scroll bar and scroll to the end.

Fix the Combo Boxes
Why are the combo boxes in the wrong place? The code for the combo boxes is this:
penguinType_values = ['Adele','Emperor','King','Blackfoot','Humboldt','Galapagos','Macaroni','Tux','Oswald Cobblepot','Flippy Slippy'] penguinType_combobox = ttk.Combobox(values = penguinType_values, textvariable = self.penguinType) penguinType_combobox.grid(row =1, column = 0) penguinAction_values = ['Happy','Sad','Angry','Standing','Swimming','Eating','Sleeping','On Belly','Plotting Evil','Singing','Dancing','Being Cute'] penguinAction_combobox = ttk.Combobox(values = penguinAction_values, textvariable = self.penguinAction) penguinAction_combobox.grid(row=1, column = 1)
Looking carefully, I find my mistake. I forgot to add self.frame
to both combo boxes. It’s placing the combo box on the window, not the frame. That’s an easy fix:
penguinType_combobox = ttk.Combobox( self.frame,values = penguinType_values, textvariable = self.penguinType)
For the second one, change:
penguinAction_combobox = ttk.Combobox( self.frame, values = penguinAction_values, textvariable =
Save and run:

More about Grids and Sizing
The grid()
method has a simple way of sizing the gird. It finds the biggest vertical and horizontal dimension for the row and column, and sets it to those values. That, and some bad layout is messing us up. Remove the columnspan
on the label.
status_label.grid(row=0,column=0,sticky=NSEW)
Save and run. Add Oswald Cobblepot Plotting Evil, which is the longest phrase we have:

Let’s take note of our layout problems.
- The quit button moves over the accommodate the extra size.
- The Add button centers itself with the list box
- The Combo Boxes spread out too much
- Getting to the first combo box, we can accidentally press Add instead
The two buttons and the two combo boxes would work better in a frame the width of the listbox, then organized in a grid within that frame. Add this code just under def loadView()
:
buttonFrame = ttk.Frame(self.frame) buttonFrame.grid(row = 1, column = 0, sticky = NSEW)
Now change the widgets to use this as their frame, changing their grid locations as indicated:
add_button = ttk.Button(buttonFrame,command= self.vc.addPressed,text = 'Add') add_button.grid(row = 0, column = 0,sticky = NSEW) quit_button = ttk.Button(buttonFrame, command = self.vc.quitPressed, text = 'Quit') quit_button.grid(row = 0, column = 1,sticky = NSEW) penguinType_values = ['Adele','Emperor','King','Blackfoot','Humboldt','Galapagos','Macaroni','Tux','Oswald Cobblepot','Flippy Slippy'] penguinType_combobox = ttk.Combobox(buttonFrame,values = penguinType_values, textvariable = self.penguinType) penguinType_combobox.grid(row =1, column = 0,sticky = EW) penguinAction_values = ['Happy','Sad','Angry','Standing','Swimming','Eating','Sleeping','On Belly','Plotting Evil','Singing','Dancing','Being Cute'] penguinAction_combobox = ttk.Combobox(buttonFrame, values = penguinAction_values, textvariable = self.penguinAction) penguinAction_combobox.grid(row=1, column = 1,sticky = EW)
Save and run:

The frame button frame is a 2×2 grid, and with everything sticky, lines up perfectly. — almost. The scroll bar is out in its own column. Let’s make another frame and place the list box and scroll bar inside of it.
listFrame = ttk.Frame(self.frame) listFrame.grid(row =2,column = 0, sticky = NSEW) dataList_yScroll = Scrollbar(listframe, orient=VERTICAL) dataList_yScroll.grid(row=0,column=1,sticky = NS) dataList_listbox = Listbox(listframe, listvariable= self.dataList, width = 40) dataList_listbox.grid(row=0, column = 0, sticky=NSEW) #bind the listbox and scrollbar dataList_listbox.configure(yscrollcommand = dataList_yScroll.set) dataList_yScroll.configure(command= dataList_listbox.yview)
We changed the grid so instead of row 5 we are in row 0 of the new frame. Save and run:

We have scroll bars and have lined everything up correctly. We’ll revisit that annoying list box and use something else to get a better list box. That will be next time.
The Whole Code
The code is also available for download at Github.
# poppers_penguins_MVC_03 #MVC Version 2014 May 28 #Change: Implements the Model according to the MVC template from tkinter import * from tkinter import ttk from tkinter import messagebox class MyViewController(): def __init__(self,parent): self.parent = parent; self.view = MyView(self) self.model = MyModel(self) #Handlers -- target action def addPressed(self): self.view.setLabelText(self.view.getPenguinType()+ ' Penguin '+ self.view.getPenguinAction() + ' Added') self.model.addRecordToList(self.view.getPenguinType(),self.view.getPenguinAction()) def quitPressed(self): self.view.setLabelText('Quitting') answer = messagebox.askokcancel('Ok to Quit','This will quit the program. \n Ok to quit?') if answer==True: self.parent.destroy() #Add list to string processing for the scrollbox. def spaceToUnderscore(self,aList, width): # convert spaces to underscores in list items # with a triple underscore between list items listString = str() for element in aList: elementString = '{:{width}}'.format(element, width=width) elementString = elementString.replace(' ','_') listString = listString + '___' + elementString return listString def elementListToString(self,aList): listString = str() for element in aList: elementString = element listString = listString + '\t' + elementString return listString def listToListString(self,aList): #a temporary method for making lists compatible #with listbox till I find something better #returns a string we can place in a StringVar listString = str() for record in aList: elementString = self.spaceToUnderscore(record,20) # elementString = self.elementListToString(record) listString = listString + '\n' + elementString return listString #delegate for the model def modelDidChangeDelegate(self): aList = self.model.getList() aListString = self.listToListString(aList) self.view.setDataList(aListString) class MyView(Frame): #Change parent to vc and add a frame 2014 May 26 a def __init__(self,vc): self.frame=Frame() self.frame.grid(row=0,column=0) self.vc = vc #properties of the view self.labelText = StringVar() self.labelText.set("Popper's Penguins Ready") self.penguinType = StringVar() self.penguinType.set('Penguin Type') self.penguinAction = StringVar() self.penguinAction.set('Penguin Action') #Add the control variable self.dataList = StringVar() self.dataList.set ('') self.loadView() # self.makeStyle() #Setters and getters for the properties 2014-May 26 def setLabelText(self,newText): self.labelText.set(newText) def getLabelText(self): return self.labelText.get() def setPenguinType(self,newType): self.penguinType.set(newType) def getPenguinType(self): return self.penguinType.get() def setPenguinAction(self,newAction): self.penguinAction.set(newAction) def getPenguinAction(self): return self.penguinAction.get() #Add setters and getter for data list def getDataList(self): #returns a string return self.dataList.get() def setDataList(self,listString): #needs a list string delimtied by a space self.dataList.set(listString) #Style Sheet def makeStyle(self): self.s = ttk.Style() self.s.configure('TFrame',background = '#5555ff') self.s.configure('TButton',background = 'blue', foreground = '#eeeeff', font = ('Sans','14','bold'), sticky = EW) self.s.configure('TLabel',font=('Sans','16','bold'),background = '#5555ff', foreground = '#eeeeff') self.s.map('TButton', foreground = [('hover','#5555ff'), ('focus', 'yellow')]) self.s.map('TButton', background = [('hover', '#eeeeff'),('focus','orange')]) self.s.configure('TCombobox',background = '#5555ff',foreground ='#3333ff',font = ('Sans',18)) #loading the view #changed the command= to refer to the view controller 2014-May-26 # self.addPressed now is self.vc.addPressed # self.quitPressed now is self.vc.quitPressed def loadView(self): #label buttonFrame = ttk.Frame(self.frame) buttonFrame.grid(row = 1, column = 0) status_label = ttk.Label(self.frame, textvariable = self.labelText) #status_label.configure(font=('Sans','16','bold'),background = 'blue', foreground = '#eeeeff') status_label.grid(row=0,column=0,sticky=NSEW) add_button = ttk.Button(buttonFrame,command= self.vc.addPressed,text = 'Add') add_button.grid(row = 0, column = 0,sticky = NSEW) quit_button = ttk.Button(buttonFrame, command = self.vc.quitPressed, text = 'Quit') quit_button.grid(row = 0, column = 1,sticky = NSEW) penguinType_values = ['Adele','Emperor','King','Blackfoot','Humboldt','Galapagos','Macaroni','Tux','Oswald Cobblepot','Flippy Slippy'] penguinType_combobox = ttk.Combobox(buttonFrame,values = penguinType_values, textvariable = self.penguinType) penguinType_combobox.grid(row =1, column = 0,sticky = EW) penguinAction_values = ['Happy','Sad','Angry','Standing','Swimming','Eating','Sleeping','On Belly','Plotting Evil','Singing','Dancing','Being Cute'] penguinAction_combobox = ttk.Combobox(buttonFrame, values = penguinAction_values, textvariable = self.penguinAction) penguinAction_combobox.grid(row=1, column = 1,sticky = EW) listFrame = ttk.Frame(self.frame) listFrame.grid(row =2,column = 0, sticky = NSEW) dataList_yScroll = Scrollbar(listframe, orient=VERTICAL) dataList_yScroll.grid(row=0,column=1,sticky = NSEW) dataList_listbox = Listbox(listframe, listvariable= self.dataList, width = 40) dataList_listbox.grid(row=0, column = 0, sticky=NSEW) #bind the listbox and scrollbar dataList_listbox.configure(yscrollcommand = dataList_yScroll.set) dataList_yScroll.configure(command= dataList_listbox.yview) #(1)adding the model 2014-May-28 with convenience method class MyModel(): def __init__(self,vc): self.vc = vc self.myList = [] self.count = 0 #(2)Delegate goes here. Model would call this on internal change def modelDidChange(self): self.vc.modelDidChangeDelegate() #Setters and getters for the model def getList(self): return self.myList def addToList(self,item): myItem = item myList = self.myList myList.append(myItem) self.myList=myList self.modelDidChange() def getItemAtIndex(self,index): return myList[index] #other methods def addRecordToList(self,penguin,action): self.addToList([penguin,action]) def main(): root = Tk() #(8) Set up the main loop 2014 May 26 frame = Frame(root, background="#5555ff", takefocus = 0) root.title("Popper's Penguins") app = MyViewController(root) root.mainloop() if __name__ == '__main__': main()
Leave a Reply