pysim wrote:
Hi, I have a couple of general requests for pointers to python
examples and design advice.
I'm looking for examples of MVC-based GUI controls done in python
(model-view-controller).
Also, examples where something like a simulation model running in its
own thread sends fast updates to a GUI in near real-time. The only
examples I've seen, such as SimPy, wait until the model is finished
running before outputting data.
Most MVC controls I see in java for example are only designed for
infrequent user-driven changes, like resizing a window or mouse
clicks, not cases where the underlying model is changing itself over
time.
Here's a baroque example. Maybe it will inspire others to respond
with something more concise :)
The attached example app has a text-based "GUI", as I wanted it to
be more or less self-contained. The app prints a sinusoidal
line of "#"s, until you type "q" and hit return. E.g.
#
#
#
#
#
#
#
#
#
#
#
#
....
The classes in the example include:
Observable -- maintains a set of observers, notifying them whenever
the value of the Observable changes.
Model -- runs a simple computation in a background thread. The
Model's state is an Observable.
View -- displays a model's state as shown above. Also processes
keyboard input, looking for a line starting with 'Q' or 'q' as
a shutdown request.
App -- acts like a controller, but also includes the application's
main event loop, which blocks until keyboard input is available.
The Model and View know nothing about each other. The
App glues them together; it tells the View to update whenever
the Model state changes. It also routes user input (keyboard
input, in this case) to the View. This is a common way
to build MVC applications, and it addresses your concern about
controls which are designed only for user-driven changes.
There's a lot of synchronization overhead in updating the view.
If you have lots of views on the same model, or if your views
take a long time to pain, performance and responsiveness will
suffer. In such cases it might be good to have the model publish
its state only at regular intervals -- store its current state
in a protected attribute and only occasionally copy that state
into an Observable.
Or, if you want to write really baroque code (like me,
apparently :) you could use a "delayed" Observable which
propagates model state changes at low rates. For example, the
model might publish its state using an instance of this
(untested) class:
class DelayedObservable(Observable):
...
def __init__(self, notifyInterval=1.0):
self._tLastChange = time.time()
self._notifyInterval = notifyInterval # Seconds
def _notify(self, force=0):
dt = time.time() - self._tLastChange
if force or (dt >= self._notifyInterval):
for observer in self._observers:
...
self._tLastChange = time.time()
This is all pretty verbose, but maybe it will be helpful.
For better ideas on limiting the rate of propagation of model
state changes, it might be good to look at the documentation
for NSNotificationQueue. It's part of the Cocoa framework of
Mac OS X.
--
Mitch
#!/usr/bin/env python
"""Demo a basic MVC app structure in which
The model is a simulation running in its own thread.
The model undergoes frequent, View-able state changes
"""
import sys, sets, threading, select, math
class Observable(object):
"""An Observable notifies its observers whenever its value changes.
Observers are just Python callables having the signature
callMe(sender)
Lots of MT-safe overhead, here. So...
TO DO: Demonstrate asynchronous, coalesced notifications."""
def __init__(self):
self._observers = sets.Set()
self._lock = threading.RLock()
self._value = None
def addObserver(self, newObserver):
self._observers.add(newObserver) # This oughtta be locked...
def removeObserver(self, anObserver):
self._observers.remove(anObserver) # This oughtta be locked...
def _notify(self):
for observer in self._observers: # This oughtta be locked...
try:
observer(self)
except:
pass # Don't let one broken observer gum up everything
def _getValue(self):
self._lock.acquire()
result = self._value
self._lock.release()
return result
def _setValue(self, newValue):
self._lock.acquire()
self._value = newValue
self._lock.release()
self._notify()
value = property(_getValue, _setValue, None, "The observable value")
class Model(threading.Thread):
"""Computes new values asynchronously. Notifies observers whenever
its state changes."""
def __init__(self, **kw):
threading.Thread.__init__(self, **kw)
self._stopped = 0
self._state = Observable()
def onStateChange(self, observer):
self._state.addObserver(observer)
def removeStateChange(self, observer):
self._state.removeObserver(observer)
def run(self):
"""Run the model in its own thread."""
self._stopped = 0
i = 0.0
di = math.pi / 8.0
while not self._stopped:
self._state.value = math.sin(i)
i += di
def stop(self):
self._stopped = 1
class View:
"""Dummy 'view' just prints the model's current value whenever
that value changes, and responds to keyboard input."""
def __init__(self):
self._onQuitCB = None
def modelStateChanged(self, modelState):
valueBar = " " * int((1 + modelState.value) * 10)
print "%s#" % valueBar
def onQuit(self, newOnQuitCB):
self._onQuitCB = newOnQuitCB
def handleInput(self, userInput):
if userInput.lower().startswith("q"):
if self._onQuitCB:
self._onQuitCB(self)
class App:
"""This sample application computes and displays garbage, at
a high rate of speed, until the user quits."""
def __init__(self):
# Yep, this is really a controller and not just an app runner.
self._model = Model()
self._view = View()
self._terminated = 0
self._model.onStateChange(self._view.modelStateCha nged)
self._view.onQuit(self._quitApp)
def run(self):
self._model.start()
self._terminated = 0
while not self._terminated:
ins, outs, errs = select.select([sys.stdin], [], [])
if ins:
self._view.handleInput(raw_input())
self._model.join()
def _quitApp(self, *args):
self._terminated = 1
self._model.stop()
self._model.removeStateChange(self._view.modelStat eChanged)
def main():
"""Module mainline (for standalone execution)"""
theApp = App()
theApp.run()
if __name__ == "__main__":
main()