Re: FW: RE: [Tutor] On GUI's

Magnus Lycka magnus at thinkware.se
Thu Apr 22 14:41:39 EDT 2004


Chad Crabtree wrote:
> I got started (but didn't finish) a Dietitian tool and thinking it would be
> nice separate the UI from the code I tried first to do this with a menu
> driven text UI.  They code for the UI got way more complex and confusing
> than the program logic.  Since I have lambasted myself for not just
> designing the GUI first then doing the engine part.  I'm doing this now on a
> project I'm doing it's helping me to better understand what needs to be
> done.  Perhaps this is because of my exposure to GUI's that I kind of think
> in them.

If it works for you, it's good. It's important to consider how the system
is to be used of course, and thinking about the user interface might help
you to do that. 

On the other hand, thinking too much about the user interface too early 
might make you a bit narrowminded about the application, and just because
you failed to write a program and tried to write the logic first that time,
it doesn't mean that writing the GUI first is a better solution in general.

The important thing in my mind is to separate the application logic from the
user interface code. 

> However the conversation on pipes and filters and stuff is interesting.  I
> was reading about modularity (where I cannot remember, think it was the guy
> who started bonobo 'GNOME') they author mentioned the UNIX way of doing
> things.  He mentioned COM and how much he liked it and others Like CORBA
> XML-RPC.  I just wanted to comment on this because the UNIX way seems more
> error prone than the remote procedure calls. 

With the unix approach, any power user who is familiar with the general
tools can cook up his own applications without any programming. You can't
do that with remote procedure calls or object request brokers.

Thus, you are back to a situation where the programmers have to predict all
stuff the users will ever want to do, and write more or less monolithic
applications (even if they are distributed and built from components) that
the users use just the way they are.

Unix is like a well equipped workshop where a good craftsman can do really 
great things, while some people are more likely to just injure themselves. 

Windows is more like a vending machine where you enter coins, press buttons 
and (hopefully) get the stuff you select from the limited selection available.

At least when I am in my romantic mode, I much prefer the idea of more and more
people being able to use a good workshop, than the idea of providing better and
better vending machines with more and more buttons...

I wrote a simple application to demonstrate how GUI layer and application layer
can be separated. The program below is now also placed at 
http://www.thinkware.se/cgi-bin/thinki.cgi/GuiSampleApp

I really perfer wxPython to Tkinter, but I don't have access to that right now.

Anyway, with this application, you can click in the list box, enter a name in
the entry field of the new window that popped up, save that etc. By clicking
on names in the list, you open their entries, and you can edit or delete them.
Changes are saved to file when you quit. It's a meaningless little application,
but it shows you the principle. I put it all into one file, but it's trivial
to split this into two files, one with the application logic, and one with
the GUI.

The application logic is very simple in this case. It's basically just an
interface to a little register stored as a shelve. The PersonModel class has
methods for adding, updating, removing, returning and listing persons. It
has a commit method to persist the database, and it supports the Observer
pattern so that Views of the data can be updated when something changes.
(You will see how the listing changes if you delete or save a person for
instance.)

This program is similar to the Model View Controller Pattern used in SmallTalk,
but since GUI tool kits like Tkinter have the system for event handling and
callbacks that they have, it's really little point making a separate Controller
class, so I didn't do that. (The MVC Controllers in SmallTalk we subclasses of
a Control class which handled the interaction with mouse and keyboard.)

Please ask/comment.

# Application logic part
import shelve

ALL_OBJECTS = 'ALL OBJECTS'
DB_FILE_NAME = 'person.db'

class PersonModel:
    def __init__(self):
        self._observers = {}
        self._persons = shelve.open(DB_FILE_NAME)
        self._id = max([int(x) for x in self._persons.keys()]+[0])

    # Application logic methods.
    def addPerson(self, person):
        self._id += 1
        self._persons[str(self._id)] = person
        self.notify(None)
        return self._id
    def updatePerson(self, id, person):
        self._persons[str(id)] = person
        self.notify(id)
    def removePerson(self, id):
        del self._persons[str(id)]
        self.notify(id)
    def getPerson(self, id):
        return self._persons[str(id)]
    def listPersons(self):
        personList = [(int(key),value) for (key, value)
                      in self._persons.items()]
        personList.sort()
        return personList
    def commit(self):
        self._persons.close()
        self._persons = shelve.open(DB_FILE_NAME)
        
    # Observer pattern methods.
    def attach(self, observer, what):
        self._observers[observer] = what
    def detach(self, observer):
        del self._observers[observer]
    def notify(self, what_changed):
        for observer, what_to_observe in self._observers.items():
            if what_to_observe in [what_changed, ALL_OBJECTS]:
                try:
                    observer.update()
                except:
                    pass

# Here comes the GUI!
import Tkinter as Tk

class PersonListView(Tk.Frame):
    def __init__(self, model):
        self.model = model
        model.attach(self, ALL_OBJECTS)
        Tk.Frame.__init__(self, None)
        self.pack()
        self.list = Tk.Listbox(self)
        self.list.insert(0,' ')
        self.list.bind("<ButtonRelease-1>", self.listClick)
        self.list.pack()
        quitButton = Tk.Button(self, text="Quit", command = self.doQuit)
        quitButton.pack()
        self.update()
    def doQuit(self):
        self.model.detach(self)
        self.model.commit()
        self.quit()
    def listClick(self, event):
        text = self.list.get(self.list.curselection()).strip()
        if text:
            id = int(text.split(':')[0])
        else:
            id = self.model.addPerson('')
        PersonEditView(self.model, id)
    def update(self):
        # Clear Listbox
        self.list.delete(0, self.list.size()-1)
        assert self.list.size() == 0
        # Fill listbox
        for i, (id, person) in enumerate(self.model.listPersons()):
            self.list.insert(i, "%i : %s" % (id, person))
        self.list.insert(self.list.size(), " ")

class PersonEditView(Tk.Toplevel):
    def __init__(self, model, id):
        self.model = model
        self.id = id
        model.attach(self, id)
        Tk.Toplevel.__init__(self, None)
        self.label = Tk.Label(self, text = str(id))
        self.label.pack()
        self.person = Tk.StringVar()
        self.entry = Tk.Entry(self, textvariable=self.person)
        self.entry.pack()
        self.saveButton = Tk.Button(self, text='Save', command = selfdoSave)
        self.saveButton.pack()
        self.deleteButton = Tk.Button(self, text='Delete',
                                      command = self.doDelete)
        self.deleteButton.pack()
        
        self.update()

    def update(self):
        try:
            person = self.model.getPerson(self.id)
            self.person.set(person)
        except KeyError:
            self.destroy()

    def doSave(self):
        self.model.updatePerson(self.id, self.person.get())

    def doDelete(self):
        self.model.removePerson(self.id)

if __name__ == '__main__':
    pm = PersonModel()
    app = PersonListView(pm)
    app.master.title("GUI Example")
    app.mainloop()

-- 
Magnus Lycka, Thinkware AB
Alvans vag 99, SE-907 50 UMEA, SWEDEN
phone: int+46 70 582 80 65, fax: int+46 70 612 80 65
http://www.thinkware.se/  mailto:magnus at thinkware.se



More information about the Tutor mailing list