4. Design issues

Upto here, everything was just more or less copying the orignal TCL/Tk functionality in Python. However, where TCL/Tk code stays relatively compact, with Pithon I now have a script that is about the same number of lines but implements only a very small part of the functionality. This is a strategy that is not sustainable.

4.1. Splitting the GUI

In the TCL/Tk version it was one simple GUi; the tree-structure that the GUI has is natural to that language. In Python, it is not. So, in order to get a better and more readable code, we have to create objects that provide a more high-level view of the GUI, instead of the low-level Tk components.

4.1.1. Entryfields

As a first, I have a number of entry fields that are preceeded by a label that tells what information should be in the entry field.

entryframes.png>

So, the combination of a label with an entry field is a good candidate for an object.
class entryfield(object):
    def __init__(self,parent,label='text'):
        self.fieldframe=tk.Frame (parent)
        self.fieldframe.pack(side=tk.TOP)
        self.fieldname=tk.Label(self.fieldframe, text=label ,width=10,anchor='w')
        self.fieldname.pack(side=tk.LEFT)
        self.fieldvalue=tk.Entry(self.fieldframe, width=50)
        self.fieldvalue.pack(side=tk.RIGHT)
   def set(self,value='text'):
       self.fieldvalue.delete(0,tk.END)
       self.fieldvalue.insert(0,value)
   def get(self):
       return(self.fieldvalue.get())

And with that object, creating the part of the GUI in the picture above becomes:
        self.fieldnumber=entryfield(self.fieldframe,'Number')
        self.fielddir=entryfield(self.fieldframe,'Directory')
        self.fieldfilename=entryfield(self.fieldframe,'Filename')
        self.fieldyear=entryfield(self.fieldframe,'Year')
        self.fieldmonth=entryfield(self.fieldframe,'Month')
        self.fielddescr=entryfield(self.fieldframe,'Description')

That is clearly more readable than the endless repeats of the tk.Frame, tk.Label, tk.Entry and their packs.

You will notice, that their are a number of assumptions that make this object rather specific for my GUI. The size of the entry fields and labels are fixed, packing is always TOP etcetera. This makes the entryfield object good for this GUI, but perhaps a bit less re-usable.

4.1.2. Multilist

The object of the next object is to create a more reusable object. The problem is that we have multiple list boxes that must scroll synchronously and with a scrollbar. This functionality existed in the TCL version and in the one-big-gui version, so the main problem is to create a more generally usable object.

 

class multilist (object) :
    def __init__ (self,parent,columns=1,height=50,width=10) :
        self.listframe=tk.Frame (parent)
        self.listframe.pack()
          #     The listframe contains 4 lists and a scrollbar which are synchronized
          self.sb = tk.Scrollbar(self.listframe, orient='vertical')
          self.cols=[]
          self.select=[]
          for i in xrange(columns):
              self.listnr=tk.Listbox (self.listframe, yscrollcommand=self.yscrollnr)
              self.listnr.config(height=height,width=width)
              self.listnr.bind('<<ListboxSelect>>',self.selectlist)
              self.listnr.pack(side=tk.LEFT, expand=True )
              self.cols.append(self.listnr)
          self.sb.config(command=self.yview)
          self.sb.pack(side=tk.RIGHT, fill='y')
    def width(self,column=1,width=10):
        print "set column ",column-1,' to ',width
        self.cols[column-1].config(width=width)
    def yscrollnr(self, *args):
        for i in xrange(len(self.cols)):
            self.cols[i].yview_moveto(args[0])
        self.sb.set(*args)
    def yview(self, *args):
          for i in xrange(len(self.cols)):
             self.cols[i].yview(*args)
    def subscribe (self,function):
         self.select.append(function)
    def selectlist(self,event):
         widget = event.widget
         sel=widget.curselection()
         if sel != ():
             for f in self.select:
                 f(sel)
    def clear(self):
          for i in xrange(len(self.cols)):
              self.cols[i].delete(0,tk.END)
    def add(self, *args):
          i=0
          for val in args:
            if i < len(self.cols):
                self.cols[i].insert(tk.END,val)
                i=i+1
            else:
                 print "No column for $val"

 

Their are still a number of things that make this object a bit specific, but it should be clear that there are a number of choices to generalize its use.

First of all, the size (width and height) and the number of columns are part of the call to __init__. It is also possible to adjust the column width on a per-column basis. Second, the clear and add methods are used to manipulate the listbox contents.

Perhaps more interesting is the callback for a selection in the list. Instead of calling a specific function, external functions/objects/programs can subscribe to the select in the listbox. So multiple functions can be called, just by subscribing to this event.