appleberry

From Apple to Raspberry Pi: How to Make a List Box (Somewhat) Useful

appleberryIn the Popper’s Penguin application, I’d like to take my data, display it, and be able to select it. If I was doing this in Xcode, I’d use an UITableView to do exactly that.

A mockup of Popper's penguins on iPhone. the bottom element is a UITableView.
A mockup of Popper’s penguins on iPhone. the bottom element is a UITableView.

UITableViews are more advanced than what I want, as they respond to gestures. I don’t need the delete button in the above mockup, since a swipe left opens up a delete button, like this example from Interval RunCalc:

Deleting an interval in a table view
Deleting an entry in a table view

If I could just list my penguins and select a row to delete or display them, I’d be a happy camper. There is a list box widget in Tkinter, and that would seem to do the job.

We can add this to the end of view’s loadview() method:


        dataList_listbox = Listbox(self.frame, listvariable= self.dataList, width = 40)
        dataList_listbox.grid(row=5, column = 0, columnspan = 4,rowspan = 4, sticky=NSEW)

It needs a control variable to store the list. This is  a StringVar() type. Add this to the __init__ method

#Add the control variable
        self.dataList = StringVar()
        self.dataList.set ('')

Now add the setters and getters in the view:

#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)

Our model is a list of lists, but the getter for the list box  in the view is a StringVar.  Our view controller will convert the data from one form to another. To make a printable line, we take the sublist, convert it to a string, then print it on a separate line. In the controller, above the delegate, add this to convert the sublist to a string

def elementListToString(self,aList):
        listString = str()
        for  element in aList:
            elementString = element
            listString = listString + '\t' + elementString
        return listString

Now we will use that method to make a string to print out rows of data to the list box.

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.elementListToString(record)
             listString = listString + '\n' + elementString
        return listString

Rewrite the modelDidChangeDelegate() delegate to take the list from the model, and display it in the list box.

#delegate for the model
    def modelDidChangeDelegate(self):
        aList = self.model.getList()
        aListString = self.listToListString(aList)
        self.view.setDataList(aListString)

Build and run. Things start normally, with a new, big widget :

Screenshot 2014-06-03 10.49.20

Add an Adele Penguin that is happy, then add Oswald Cobblepot plotting evil. Our results are not what we wanted.

Screenshot 2014-06-03 10.50.17

It turns out the list box control variable uses any white space character as a delimiter. In short, you can only have one-word selections in a list box. Which doesn’t help us much.

At this point, it is best to find a completely different widget to display rows of data and select rows from that data. Before we do, I’ll mention one hack that will let us  develop other parts of the code while trying to find a better user interface widget. Add the following method just above elementListToString

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

The loop is similar the the elementListToString() method, concatenating the list elements into a string. In line 6 we use the format() method to make a fixed width string. Line 7 replaces all the white spaces with underscores in the string. Line 8 replaces the tab between the entries with a triple underscore.
Change the statement  in listToListString() from this

elementString = self.elementListToString(record)

to this:

elementString = self.spaceToUnderscore(record,20)

Build and run. Add some penguins.

Screenshot 2014-06-03 10.52.38
That looks a lot better than the last effort. It isn’t pretty and certainly would not work for a final application to users. As a place marker, it does the job. We can get other functionality going while we work towards a better solution.

Before we work on that solution, what happens when we have more than a few entries on the list?

Screenshot 2014-06-03 10.53.39

We cannot see them anymore. This needs another widget. Next time, we will introduce the scroll bar.

The Whole Code:

Download the code at github here

# 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
        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,columnspan=8,sticky=NSEW)
 
        add_button = ttk.Button(self.frame,command= self.vc.addPressed,text = 'Add')
        add_button.grid(row = 2, column = 0)
        quit_button = ttk.Button(self.frame, command = self.vc.quitPressed, text = 'Quit')
        quit_button.grid(row = 2, column = 3)
 
        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 = 4)

        dataList_listbox = Listbox(self.frame, listvariable= self.dataList, width = 40)
        dataList_listbox.grid(row=5, column = 0, columnspan = 4,rowspan = 4, sticky=NSEW)

#(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()  

2 thoughts on “From Apple to Raspberry Pi: How to Make a List Box (Somewhat) Useful”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s