Refactoring question

K

Kevin Walzer

I currently have a GUI application (using Tkinter) in which all the code
is placed in a single script. This means that the GUI bits are a bit too
tightly bound to the "under-the-hood" logic of the app. Here's an
example snippet:

def installPackage(self):

self.package = self.infotable.getcurselection()
if not self.package:
showwarning('Error', 'Please select a package name.')
return
else:
self.packagename = self.package[0][1]
self.status.set('Installing %s' % self.packagename)
self.showProgress()
self.file = os.popen('echo %s | sudo -S /sw/bin/fink -y
install %s' % (self.passtext, self.packagename), 'r', os.O_NONBLOCK)
for line in self.file:
self.textdisplay.configure(state='normal')
self.textdisplay.insert(END, line)
self.update()
self.textdisplay.see(END)
self.textdisplay.configure(state='disabled')
self.endProgress()
self.categorytree.selection_set('All')
self.listCategoryPackages()

I'd like to refactor and separate the GUI code from the back-end code;
for instance, to move the "self.file = os.popen" bits into a separate
category. I want to do this so that I can redesign the GUI using another
toolkit, if I so choose, such as wxPython.

What's the best way to do this? Can anyone point me in the right
direction? How could, for instance, the top snippet be rewritten to
separate the Tkinter parts from the generic stuff?
 
G

ginstrom

On Behalf Of Kevin Walzer
What's the best way to do this? Can anyone point me in the
right direction? How could, for instance, the top snippet be
rewritten to separate the Tkinter parts from the generic stuff?

I like to use the broadcaster/broker recipe at
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81983

I added a few features (such as decorators for listener functions --
see below sig).
There are other recipes out there, such as PyDispatcher.

Basically, I have GUI events translated into broadcaster events (by
passing lambdas that call broadcaster.Broadcast() to the event
binders), which are received by controller functions/classes.

Feedback (status/progress) is also communicated via broadcaster
events.

Something I tried on my current project, which is going fairly well,
is first writing a command-line version, then writing a GUI version,
with the same controller back-end used in each case, and the
controller communicating progress/results via the same interface. This
approach has made me keep presentation and logic very loosely
coupled.

So for instance, the view class would send request for processing,
which the controller gets.
The controller performs the requested action, sending broadcasts of
progress (note that the controller can start a worker thread for the
processing, but the broadcasts should be made on the GUI thread...)

broadcaster.Broadcast( "progress", "start", (100, "Doing your
bidding now..." ) ) # number of items we will process

# ...
broadcaster.Broadcast( "progress", "progress", (i, "Working on
item %i" % i ) ) # current item
# ...

broadcaster.Broadcast( "progress", "end", (100, "Done!") )

Depending on who is showing the progress, this might go onto a status
bar, progress dialog, the console, a log file, and so on, or some
combination thereof -- the controller doesn't know or care.

When the controller is finished, it asks the broker for a view, and
calls show results on the view

view = broker.Request( "view" )
view.ShowResults( results )

That could have been done equally with the broadcaster, but for some
reason I like the broker here (it makes the view "dumber").

Regards,
Ryan

--
Ryan Ginstrom

==================
# listener decorators

def BrokerRequestHandler( title ):
"""A decorator for broker listeners

@param title: the title to provide

The decorated function must take no arguments
(it can retrieve them using CurrentData())
"""

def decorator(func):
broker.Register( title, func )
return func
return decorator

def BroadcasterEventHandler( source, title ):
"""A decorator for broadcaster event handlers

@param source: the broadcast source
@param title: the title of the broadcast

The decorated function must take no arguments
(it can retrieve them using CurrentData())
"""

def decorator(func):
broadcaster.Register( func, source, title )
return func
return decorator

# example ...
@BrokerRequestHandler( "meaning of life" )
def getMeaningOfLife():
return 42

## A little more complicated for class methods. I stole this technique
from WCK

# Lifted shamelessly from WCK (effbot)'s wckTkinter.bind
def EventHandler( source, title ):
"""Dectorator for event-handling methods"""

def decorator(func):
func.BroadcasterEvent = (source, title)
return func
return decorator

class FrameController:
"""Controller for the main frame window"""

def __init__( self ):

for key in dir(self):
method = getattr(self, key)
if hasattr(method, "BroadcasterEvent") and
callable(method):
source, title = method.BroadcasterEvent
broadcaster.Register( method,
source=source,
title=title )


@EventHandler( "event", "onExport" )
def onExport( self ):
"""Handles the onExport broadcast by exporting the database to
the requested format"""

format = broadcaster.CurrentData()
# Perform export...
 
K

kyosohma

I like to use the broadcaster/broker recipe athttp://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81983

I added a few features (such as decorators for listener functions --
see below sig).
There are other recipes out there, such as PyDispatcher.

Basically, I have GUI events translated into broadcaster events (by
passing lambdas that call broadcaster.Broadcast() to the event
binders), which are received by controller functions/classes.

Feedback (status/progress) is also communicated via broadcaster
events.

Something I tried on my current project, which is going fairly well,
is first writing a command-line version, then writing a GUI version,
with the same controller back-end used in each case, and the
controller communicating progress/results via the same interface. This
approach has made me keep presentation and logic very loosely
coupled.

So for instance, the view class would send request for processing,
which the controller gets.
The controller performs the requested action, sending broadcasts of
progress (note that the controller can start a worker thread for the
processing, but the broadcasts should be made on the GUI thread...)

broadcaster.Broadcast( "progress", "start", (100, "Doing your
bidding now..." ) ) # number of items we will process

# ...
broadcaster.Broadcast( "progress", "progress", (i, "Working on
item %i" % i ) ) # current item
# ...

broadcaster.Broadcast( "progress", "end", (100, "Done!") )

Depending on who is showing the progress, this might go onto a status
bar, progress dialog, the console, a log file, and so on, or some
combination thereof -- the controller doesn't know or care.

When the controller is finished, it asks the broker for a view, and
calls show results on the view

view = broker.Request( "view" )
view.ShowResults( results )

That could have been done equally with the broadcaster, but for some
reason I like the broker here (it makes the view "dumber").

Regards,
Ryan

--
Ryan Ginstrom

==================
# listener decorators

def BrokerRequestHandler( title ):
"""A decorator for broker listeners

@param title: the title to provide

The decorated function must take no arguments
(it can retrieve them using CurrentData())
"""

def decorator(func):
broker.Register( title, func )
return func
return decorator

def BroadcasterEventHandler( source, title ):
"""A decorator for broadcaster event handlers

@param source: the broadcast source
@param title: the title of the broadcast

The decorated function must take no arguments
(it can retrieve them using CurrentData())
"""

def decorator(func):
broadcaster.Register( func, source, title )
return func
return decorator

# example ...
@BrokerRequestHandler( "meaning of life" )
def getMeaningOfLife():
return 42

## A little more complicated for class methods. I stole this technique
from WCK

# Lifted shamelessly from WCK (effbot)'s wckTkinter.bind
def EventHandler( source, title ):
"""Dectorator for event-handling methods"""

def decorator(func):
func.BroadcasterEvent = (source, title)
return func
return decorator

class FrameController:
"""Controller for the main frame window"""

def __init__( self ):

for key in dir(self):
method = getattr(self, key)
if hasattr(method, "BroadcasterEvent") and
callable(method):
source, title = method.BroadcasterEvent
broadcaster.Register( method,
source=source,
title=title )

@EventHandler( "event", "onExport" )
def onExport( self ):
"""Handles the onExport broadcast by exporting the database to
the requested format"""

format = broadcaster.CurrentData()
# Perform export...

I usually put all my GUI code into their own methods(s) and call those
methods from the __init__(). I do the same with the logic (where
applicable). This makes the code easier to manipulate and/or import.

In wxPython, you can also use XRC to define most of the common
elements of you GUI. I've found this method to be very helpful in
keeping my code short and easy to read, for GUI code.

Mike
 
S

Steve Holden

[lots of code that was pretty irrelevant to the reply]
I usually put all my GUI code into their own methods(s) and call those
methods from the __init__(). I do the same with the logic (where
applicable). This makes the code easier to manipulate and/or import.

In wxPython, you can also use XRC to define most of the common
elements of you GUI. I've found this method to be very helpful in
keeping my code short and easy to read, for GUI code.
While talking about keeping things short and easy to read, maybe you
could be a bit more considerate in your quoting practices. We really
didn't need to see all the code quoted to hear what you had to say ...

regards
Steve
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,175
Latest member
Vinay Kumar_ Nevatia
Top