Sometimes its nice to be able to know what class defined a property or method. Introspection makes this mostly knowable.
The reason I say “mostly” is that something the introspection metadata does not tell you is if the property definition is local or inherited. So you cant tell for sure if a property in a subclass is one defined in the subclass or the inherited one from the superclass. It would be nice if this were knowable and introspection told you this but I’m not holding my breath that this will be altered as its been this way for a long time.
So lets see what introspection _can_ tell you as sometimes that is sufficient. And for when its not well you’ll have to sign on to a feature request to add metadata to introspection so you can tell.
One thing to know about introspection is that for a subclass you also get all of the supers information. So when you get the list of properties in a subclass all the supers properties will be included in that list as well.
We’ll make use of that tidbit.
For we need to get the TypeInfo for the class of interest. We’ll focus on doing this with something simple to start with – a Window. If you put this code in the open event of a Window you will get the TypeInfo for the window this is in.
// get a list of current class up through all its supers
dim current as Window = self
Dim t As Introspection.TypeInfo = Introspection.GetType ( current )
The TypeInfo is the metadata that tells you
- the class’ name
- if its an array, class enum, interface, or pointer,
- if the item is a primitive type like an integer, color, or double
- if its public or not
- if the item is a value type or not (yes I wrote that article too)
This is all handy info. In addition to telling you all this the TypeInfo item is the one that allows you to access the properties, methods, attributes, and constructors of a class. There are several methods on the TypeInfo instance you can call that gets you a list of items you can then use.
To access the list of properties you call the GetProperties method of the TypeInfo instance
Dim props() As Introspection.PropertyInfo = t.GetProperties
Note you get back an array of PropertyInfo instances. Since any class might have more than one property this makes sense. An array is useful.
If you put a break point on the “Dim Props” line and ran this code you could manually see whats in the Props array. For a Windows you’ll have a length list and it will include things like Handle, Top, Left, Width and all the properties you might expect to see. And for each PropertyInfo object you see whether the item is private, public, protected, computed, readable and writable, shared and another object that tells you about the type of the property.
Introspection if full of objects that use other objects to tell you more information.
Again, this list doesn’t tell you anything about where the property originated – whether its defined IN this class or inherited from a super class.
So to keep track of where the property “originated” as best we can we’ll need to go through this list and mark every property as “from this class”. Then we’ll move up to the super class, get its list of properties, mark all of the ones we already knew about that match, by name, as being from the superclass. And then repeat this until we get all the way to the point where we are at the very base class that has no super.
For Window this is easy as Window has no super
We’ll create the code to do a window and then extend it to do a class that does have a deeper hierarchy of supers.
Start a new desktop project.
Add a listbox and pushbutton to the Window. Make sure the listbox has 2 columns.
Then create a new method on Window1 – GetSuperPropertyList( w as window)
In there lets put code similar to what was mentioned above.
// get a list of properties on Window
Sub GetSuperPropertyList(current as Window)
Dim t As Introspection.TypeInfo = Introspection.GetType ( current )
Dim allprops As New dictionary
Dim props() As Introspection.PropertyInfo = t.GetProperties
For j As Integer = 0 To props.ubound
Dim oneProp As Introspection.PropertyInfo
oneProp = props(j)
listbox1.addrow t.name, oneProp.Name
Next
End Sub
We start by getting the TypeInfo for the Window passed in. And then from that we get the list of properties on a Window and put those in a listbox.
In the pushbuttons action event put
GetSuperPropertyListCount( self )
And run.
Press the pushbutton.
You should see in the listbox a list of about 30 or so items. In the first column everything will say “Window1” and the second column are the names of all the properties on a Window.
Quit the running application.
Lets adjust the code so it can handle more than just a Window.
Fortunately this change is quite simple. Every object type in Xojo inherits from “Object” and so we can pass ANY class instance to a methd that uses “Object” as the type of the parameter.
Change the declaration of GetSuperPropertyList from
Sub GetSuperPropertyList(current as Window)
to
Sub GetSuperPropertyList(current as Object)
And now in the pusbutton’s action event you can write
GetSuperPropertyList(self)
GetSuperPropertyList(me)
Run and again press the button. This time you see a listing of the Window’s properties (self) and one from the pushbutton (me).
Now the only trick is to make the method list the properties of the immediate class and those of the supers. In order to do that we’ll need to :
- go through the current class and make note of the class name and property name
- get the super class
- go through its properties making note of the class name and property name
- lather rinse & repeat until we get to the point there is no super class any more
- print out the list
Now as we go thought teach class and its super we want to note the property name and the class name, and then when we go through the super if the property exists we need to alter the name we recorded. A perfect place for a dictionary.
So lets alter the code to
// get a list of current class up through all its supers
Dim t As Introspection.TypeInfo
// get the type info for the current item
t = Introspection.GetType ( current )
// a place to keep all the property info were about to gather up
Dim allprops As New dictionary
// as long as we have a class' typeinfo
While t <> Nil
// get all the properties on the class
Dim props() As Introspection.PropertyInfo = t.GetProperties
// and save them in a dictionary
// using the NAME of the property as the key
// and the name of the class we founf it in as the value
For j As Integer = 0 To props.ubound
Dim oneProp As Introspection.PropertyInfo
oneProp = props(j)
allprops.value( oneProp.Name ) = t.Name
Next
// get the immediate superclass of the class we're looking at now
t = t.BaseType
Wend
// now we have a dict fill Of keys (propNames)
// And values (the Class the define the prop)
// so list them all out
For i As Integer = 0 To allprops.Count - 1
Dim key As String = allprops.Key(i)
listbox1.addrow allprops.value(key).stringValue, key
Next
You can see that what we have added is code that does what we were doing before (getting the type info then the list of properties) and simply saves that list in a dictionary.
The thing that makes the code walk “up” the class hierarchy is the line
// get the immediate superclass of the class we're looking at now
t = t.BaseType
Every TypeInfo object knows which one is its superclass – this is the BaseType. And by using the BaseType each time in the while loop we move from the current type to its super type and so on until there is no super type.
In each iteration around the while loop we update any existing properties to say they cam from the super type (see note above about not being to actually tell if this is true or not). For most purposes this is adequate.
If you run this now & press the button you will see that instead of just Window & PushButton we see a bunch of properties from Control & RectControl. These are from the superclass of PushButton.
And there you go. A handy method to walk up the hierarchy and get which properties came from where.