Perhaps you've been wondering how the objects know what objects are their parents. In the process of answering that question, we'll pass through lots of interesting or important points along the way.
The short answer to how an object knows what its parent is, is that you tell it. The constructor for binding.Component takes as its very first argument, the object which should be its parent.
In our examples above, however, we never passed a parent into a constructor. We created the Car instance and Transport instance without supplying a parent component. So those instances were created without a parent, which is why when we inspect them, they show up as parentless or root components.
In PEAK, a root component is any object without a parent. Only objects whose classes provide a getParentComponent method can have parents, and even then they are only considered to have a parent if that method returns something other than "None". Classes without such a method, such as Python built-in types, are almost always root components. For example:
>>> print binding.getParentComponent(123456) None >>> print binding.getRootComponent([1,2,3]) [1, 2, 3] >>> print binding.getComponentPath("a string") / >>> print binding.lookupComponent("some string", "upper") <built-in method upper of str object at 0x00F26100>
As you can see, the inspection functions provided by peak.binding will work with just about any kind of object, and will provide results consistent with the interpretation that objects without a getParentComponent method are root components.
Notice also that an object doesn't have to be based on binding.Component to work in a component hierarchy; all it needs is a getParentComponent method defined in its class. For example:
>>> class myGUIControl: def getParentComponent(self): return self.parentWindow >>> aControl = myGUIControl() >>> aControl.parentWindow = "just a demo" >>> print binding.getParentComponent(aControl) just a demo
This, by the way, is one of the reasons why it's better to use peak.binding functions to inspect components instead of calling their getParentComponent or other methods directly. binding.getParentComponent has code to handle the case where an object has no getParentComponent method. Similarly, other inspection functions gracefully handle the absence of appropriate support by the object:
>>> print binding.getComponentName(aControl) None >>> print binding.getComponentPath(aControl) /*
Notice that since we didn't provide any special support in our example GUI control class for component names, the peak.binding system considers it not to have a name. And unknown names show up as "'*'" in component path segments.
Of course, if we wanted to support our hypothetical GUI controls having component names, we would need only to add a getComponentName method to its class.
But how do we know what methods to add, and what values they should accept or return? The peak.binding framework defines an interface, binding.IBindingNode, that defines what methods the binding framework calls on objects that it expects to be nodes in a component tree. The binding.IBindingNode interface looks basically like this:
class IBindingNode(config.IConfigSource): """Minimum requirements to join a component hierarchy""" def getParentComponent(): """Return the parent component of this object, or 'None'""" def getComponentName(): """Return this component's name relative to its parent, or 'None'""" def notifyUponAssembly(child): """Call 'child.uponAssembly()' when component knows its root"""
By convention, interface names in Python are usually begun with a captial ``I", to help distinguish them from regular classes. Also by convention, methods are described from the caller's perspective, so "self"arguments are not included in method definitions.
So, the binding.IBindingNode interface tells us that to implement the Binding Node interface, we need a getParentComponent method and a getComponentName method, neither of which takes any parameters, and whose return values are as documented.
This interface also inherits from config.IConfigSource, which defines some additional methods that a binding component should implement. We'll look at those in the coming chapter on the configuration system. Notice that this does not mean a component has to inherit from IConfigSource! It simply means that a class which promises to implement the binding.IBindingNode interface is also promising to implement the config.IConfigSource interface as well.
Did our example myGUIControl class promise to implement binding.IBindingNode? No. We didn't declare that it does, nor did it inherit from a class that declares support for the interface. So, it's not promising to implement the full IBindingNode interface. This is acceptable because peak.binding verifies the presence of needed methods automatically, and it isn't required that a component implement all of the IBindingNode methods. Most other interfaces however, must be explicitly promised by a class in order for the functionality to work. (See the protocols package documentation for more information about declaring support for an interface.)
The binding.IComponentFactory interface is a good example of this. IComponentFactory is the interface which defines how component constructors work - at least if they're to be supported by peak.binding!
Let's take a look at the binding.IComponentFactory interface in more detail.
class IComponentFactory(Interface): """Class interface for creating bindable components""" def __call__(parentComponent, componentName=None, **attrVals): """Create a new component The default constructor signature of a binding component is to receive an parent component to be bound to, an optional name relative to the parent, and keyword arguments which will be placed in the new object's dictionary, to override the specified bindings. Note that some component factories (such as 'binding.Component') may be more lenient than this interface requires, by allowing you to omit the 'parentComponent' argument. But if you do not know this is true for the object you are calling, you should assume the parent component is required."""
Notice that this interface describes a __call__ method. This doesn't mean that you'll need a __call__ method on your object instances or in your class. It simply means that an object that provides the interface should be callable. Because IComponentFactory is an interface for functions or classes, the __call__ method simply documents what behavior the function (or class constructor) should have.
XXX show how to implement in a class, __init__, __class_implements__, etc.
XXX IComponent interface; setParentComponent