[TransWarp] first experiences with Transwarp

Phillip J. Eby pje at telecommunity.com
Mon Mar 25 23:38:53 EST 2002


At 08:16 PM 3/24/02 +0100, ML wrote:
>Hi Phillip,
>
> > I would advise holding off for a bit on digging too deep into the use of
> > Elements and Features, however, as I'm about to redo their API
> > entirely.  After that's done, it should be possible to make some simple
>.. <snip>
>
>i watch your progress with Element/Features ... looks promising :)

It's pretty much done.  I need to document the fact that '__proceed__' 
isn't of any real use in features' method templates, but it's basically done.


>I have worked on a concept for the GUI with Transwarp .. this is my
>way to evaluate Transwarp for our project ..
>
>precondition is that one uses wxWindows and the new XML-Resource-system.
>
>I have come up with a similar system to RecordManager->RecordType:
>
>all Gui-Objects derive from baseclass GUIObject(SEF.Base) that has a 
>class-init
>method that finds it Child-Objects (only classes/instances of 
>GUIObject-Interface)
>and stores it in newClass.guiObjects.

May I suggest you try using ActiveDescriptors?  I'm trying to get rid of 
__class_init__, if I can.  I'm finding it better to use active descriptors 
which register themselves, in favor of things which are searched for by a 
class-init.  Partly, this is because having lots of class-init loops slows 
things down.  Another is that the class-init mechanism is quite 
clunky.  I'm thinking it's probably better to use __init__ on a metaclass 
than class-init.  Now that I have all the bugs worked out of metaclass 
generation and module inheritance, I want to go back and factor out the few 
uses of __class_init__ that I have.

If you take a look at how ActiveDescriptors, ActiveDescriptor, and 
MethodExporter work, you'll see that if you make a class that derives from 
(ActiveDescriptor, type), you can make a metaclass that will have its 
"activate()" class method called when a containing class is defined around 
it.  At that point, it can modify the containing class, instantiate 
something, register itself in an attribute of the containing class, etc.

I would recommend this approach in favor of __class_init__, because it 
means all the "active" components of a class can be found and activated 
with a single loop in the ActiveDescriptors metaclass.


>a <root-class>.setup() method set's up the whole GUI-Object-Tree recursivly.
>by invoking <child>.setup(). this is actually needed because i make heavy 
>use of
>SEF-Bindings that don't exist at __init__  and some wxPython issues.

Hm.  Keep in mind that if you use either Service or App as a base class for 
an item, it will be instantiated upon reference.  That means, for example, 
if you need to do something like this:

class aFrameClass(someGUIClassBasedOnSEF):

     class myTextField(GUIFieldClassBasedOnSEF_Service):
         ...

     class myButton(GUIButtonClassBasedOnSEF_Service):
         ...

     subwindows = SEF.bindToNames('myTextField','myButton')

     def __init__(self):
         # this will automatically create the sub-items
         for subwindow in self.subwindows:
             ...


It should work.  Now, if the issue here is that at __init__, the component 
doesn't know who its 'getSEFparent()' is, then I understand.  Still, you 
can use SEF.bindToNames() and SEF.Service to do most of the above, you 
would just move the loop to the setup method.

I probably should take a look and see if I can just let DynamicBinding 
objects take their parent as an __init__ parameter, as I've thought before 
this might be an issue.  At one time, though, I had Service being used as a 
mixin with classes that needed a different init signature...  but I think 
it might be safe now to require a parent object to be passed in.


>I created BaseClasses for Window,Panel,Control,Event,Menu,... and some 
>specializations.
>Most of them are Services.
>
>a Menu-Specification would look like:
>-----------------------------------------------------------------------------
>
>[cool stuff snipped]

Very neat.


>- There is an ordering problem .. which Panel will be first in 
>newClass.__dict__.items() ?
>   i will probably use the attribute guiObjects =['TreePanel','ListPanel'] 
> to fix this.

Use SEF.bindToNames('TreePanel','ListPanel').  This is a Once attribute 
that maps the names to a tuple of the actual objects.  It keeps you from 
needing an extra level of lookups at runtime.



>a MainWindow example:
>-----------------------------------------------------------------------------
>from TW.API import *
>from EMF.GUI.Basic import *
>from EMF.GUI.Window import MainWindow,loadResource
>
>class AppWindow(MainWindow):
>
>  Resources = loadResource('emfgui_wdr.xrc')
>  from MyMenu import MainMenu
>  from MyPanels import MainPanel
>  from EMF.GUI.Event import EventDispatcher
>
>
>setupModule()
>-----------------------------------------------------------------------------
>Notes:
>- loadResource is again a specialization of Once that loads the XML-Resource

Just a suggestion, I'd call it something like "ResourceFile()" or 
"ResourceLoader()" if it doesn't load the file right away, just to avoid 
confusion for somebody looking at the code later.  Or maybe even 
"bindToResourceFile()" to imply the similarity to the SEF binding operations.


>.. that's it .. ;-)

Very nice.


>after coming that far .. I'ld like to hear your comment.

I like what I've seen so far.  I'd be *very* interested in looking at your 
code as a possible starting point for TW's "official" GUI framework at some 
point in the future, although I don't have a lot of time for GUI stuff 
right now.


>i build the framework without use of the (Data)Elements and Features .. 
>because as
>far as i understand they are provided by the specialists of my Application.
>right now there is no connection between the controls and the features of
>an Element and i have not yet found a satisfying solution for this problem.
>
>Do you have a hint, how I could "link" the setter/getter - methods of my 
>control
>with an Elements-Feature somewhere up in the object-tree: make use of an
>"Element" Service that provides the current Element or some better idea??
>then you would be able to map the controls-values to the Elements features
>and most of standard-typing would be gone :)
>
>what do you think ??

Well, I have some ideas for this, but they're not real specific as 
yet.  Here's what I do know.

First, windows can be Elements.  They're views onto Elements, and have the 
same dynamic of being created and destroyed by the application, rather than 
being part of the "application object" the way the main window and app 
components like Specialists are.  A view element needs to know its data 
element.  Field bindings can use standard SEF binding elements and 
capabilities to access features of an element, using a trick something like 
this:

class aField(whatever):

     DataOwner = SEF.bindToParent()

     def fieldChanged(self):
         element = self.DataOwner.Element
         getattr(element.__class__, self.featureName).getMethod(element, 
'set')(self.value)

This assumes that 'aField' is placed directly inside an item that has an 
'Element' attribute which is the element the form is bound to.

In practice, writing this example shows me that the framework really needs 
some kind of "more than once" attribute that is bound each time you use it, 
or that can be partially bound and leave some part of it dynamic.  This 
would provide the necessary flexibility for something like a GUI where 
things "move around" more than in other application types.

To provide that level of binding sophistication, I'll probably ultimately 
end up with something like the Smalltalk "ValueModel" framework, which 
includes notification when a value is changed.  There is a Java framework 
called "XMLTalk" which does data binding in XML in a way that's heavily 
based on Smalltalk ValueModels, and I intend to look at it for ideas when I 
get around to true ValueModel-like sophistication in TW.

However, until such time as I add those notions into TW proper, you can 
roll your own quite easily.

First, dynamic binding.  Just use a Python 2.2 property object, e.g.:

def computedBinding(self):
     # compute it and return

computedBinding = property(computedBinding)

And to do a partially dynamic binding, just define an intermediate Once 
attribute for the static part, and then use a property to get it the rest 
of the way.

So that leaves notification.  For that, you have to have an object that can 
hold a value, such as a form holding a data Element.  You could define it 
as a Feature, adding "subscribeTo" and "unsubscribeFrom" verbs to the 
Feature class, which would give the form "subscribeToElement" and 
"unsubscribeFromElement" methods.  You could then create a Once binding 
which subscribes to its targeted feature, with a callback that updates or 
deletes the binding when its targeted feature changes.

It's a bit more work, but the result is quite nice.  Heck, at this point 
I'm tempted to add an "SEF.ObservableFeature" class to the framework to 
support observable features like this in general...  it would really come 
in handy for GUI frameworks.  You could make your data elements have 
observable features themselves, so that your GUI would update whenever the 
underlying objects change.

It almost makes me dizzy sometimes, thinking about the power of the 
TransWarp meta-framework, now that it's so close to being usable.  I knew 
in principle that all these things were possible with it, but it seems now 
like the reality is going to be so much more powerful than I really 
believed was possible.

This evening, Ty and I were talking about implementing a reporting criteria 
system based on SEF patterns; it's similarly mind-boggling in its 
applications to the GUI stuff you're working with.  We have a concept for 
expressing OQL-like queries using a filter class hierarchy that mimics the 
Element class hierarchy of the application domain, using only two basic 
kinds of features: value ranges and relationships.  Something based on it 
will probably replace the current Queries module.  It's way too early to 
say much about it, however.




More information about the PEAK mailing list