Modifying things with Automatic inheritance (Quick HowTo)

Post

Posted
Rating:
#1 (In Topic #890)
Guru
BruceSteers is in the usergroup ‘Guru’
A recent discovery of mine that is very very useful is a thing called "Automatic Inheritance"
This is massively useful for simply adding properties to existing gambas classes or to override some of a built in classes functions.
Also massively useful for backward compatibly as you can auto add or override things (read on for more info)

Note: an "override" as discussed here is only relevant to built in gambas classes or component classes that you otherwise do not have in your own program.

How you do it…

Lets show you a simple example…
Say you want the Timers in your application to also have a .Tag property.

So in your application make a new class and name it Timer.class
Add the following code to it…

Code (gambas)

  1. ' Gambas class file
  2. Public Const _Properties As String = "*,Tag"
  3. Property Tag As Variant Use $vTag
  4.  

That's it.  Now you will find all your Timer objects also have a Tag property.

Automatic inheritance will automatically make your Timer.class inherit the existing Timer.class as it has the same name.
As Timer can be a virtual IDE object i also added Tag to the _Properties string constant making it visible in the IDE form designer.

[hr]

You can go much further than adding new properties/methods to existing classes, you can also override class methods by making your own versions.

Take this example where I want the Timer to have a Stopped event raised if the timer gets disabled. (again the class is just called Timer.class)

Code (gambas)

  1. ' Gambas class file
  2.  
  3.  
  4.  
  5. Event Stopped
  6.  
  7. Private Sub Enabled_Write(Value As Boolean)
  8.  
  9.    ' Set the inherited Timer.class Enabled property.
  10.   Super.Enabled = Value
  11.  
  12.   ' Raise the Stopped event if not enabled
  13.   If Not Value Then Raise Stopped
  14.  
  15.  
  16. Private Function Enabled_Read() As Boolean
  17.  
  18.   Return Super.Enabled
  19.  
  20.  
  21.  
  22.   Me.Enabled = False
  23.  
  24.  

In that example we are overriding the Enabled property and the Stop() method.

Setting Timer.Enabled is now handled by our class, it sets the inherited Timer.Enabled property using Super.Enabled and raises the Stopped event if set to false.
The Stop() method is also overridden to set our own Enabled property and trigger the Stopped event.

[hr]

This can also be very useful for backward compatibility.
I had a problem with one of my applications once where i had used the Collection.Keys property not realizing that Keys is new to gambas 3.17. so anyone using an older gambas got an error.

The solution…

I create a class in my application called Collection.class
add the following code..

Code (gambas)

  1. ' Gambas class file
  2.  
  3.  
  4.  
  5. Private Function Keys_Read() As String[]
  6.  
  7.   Dim sKeys As New String[]
  8.     sKeys.Add(Me.Key)
  9.   Next
  10.  
  11.   Return sKeys
  12.  
  13.  

Now I am using my own Keys property with Collection.
If an older gambas the Keys property will be added to Collection, if a new gambas then it will be overridden.

[hr]

Note:
Properties and methods you override in a class will block the inherited classes properties/methods.  
Use the Super keyword to access the inherited class things.
As i have in the second example where i set Super.Enabled, this sets the Enabled property of the inherited Timer.class not my one.

Happy coding.
Bruce
Online now: No Back to the top

Post

Posted
Rating:
#2
Guru
BruceSteers is in the usergroup ‘Guru’
A note regarding backward compatibility.

you could make one of the above examples work more accurately to not just override but use either your added property/method if the users gambas is older and does not have it or using an existing thing if it exists using Try and Error.

eg.  (File named Collection.class)

Code (gambas)

  1. ' Gambas class file
  2.  
  3.  
  4.  
  5. Private Function Keys_Read() As String[]
  6.  
  7.   Dim sKeys As New String[]
  8.  
  9.   Try sKeys = Super.Keys  ' try to get Keys but don't trigger an error if it fails
  10.  
  11.   If Error Then  ' Keys did not exist so a bit of DIY.
  12.     For Each Me
  13.       sKeys.Add(Me.Key)
  14.     Next
  15.  
  16.   Return sKeys
  17.  
  18.  

So in that example we Try to get the Keys property from Super (the inherited Collection.class)
If gambas is 3.17+ then it will use the existing Collection.Keys property.
If an older gambas it will see the error and do it itself.

Similar methods can be used in many cases to either use a classes existing property/method or use your own if the gambas version is missing it.
Online now: No Back to the top

Post

Posted
Rating:
#3
Avatar
Regular
thatbruce is in the usergroup ‘Regular’
Very good. But one observation (of course  :twisted: ), AFAIK you cannot override event handlers. If for example you try to override the (self)_Timer event handler then both yours and the native timer events will be called and (AFAIK) there is no way to determine which is called first. Just a caveat.

Your thoughts on this however are probably worth a help howto!  You've got the time haven't you :D

Now, just to get serious (and don't call me Shirley!) one thing I am currently having a minor problem is this and your thoughts are welcome. I have used this approach in the past and sometimes I get caught up in a total mind block when I don't realize that I am using a local class override and can't understand why the class does not behave as I expect from the native class. Obscure, yes, but… it would be "nice" to know somehow whether the current instance of an object is the native version, an override in the current project or more importantly an override introduced in some intervening library or component… there are security implications…

Any thoughts?
b (t'other b)

Online now: No Back to the top

Post

Posted
Rating:
#4
Guru
BruceSteers is in the usergroup ‘Guru’

thatbruce said

Very good. But one observation (of course  :twisted: ), AFAIK you cannot override event handlers. If for example you try to override the (self)_Timer event handler then both yours and the native timer events will be called and (AFAIK) there is no way to determine which is called first. Just a caveat.

Your thoughts on this however are probably worth a help howto!  You've got the time haven't you :D

Now, just to get serious (and don't call me Shirley!) one thing I am currently having a minor problem is this and your thoughts are welcome. I have used this approach in the past and sometimes I get caught up in a total mind block when I don't realize that I am using a local class override and can't understand why the class does not behave as I expect from the native class. Obscure, yes, but… it would be "nice" to know somehow whether the current instance of an object is the native version, an override in the current project or more importantly an override introduced in some intervening library or component… there are security implications…

Any thoughts?
b (t'other b)

Hmm, i though you could override events just fine.

like this (file named Button.class)

Code (gambas)

  1. ' Gambas class file
  2.  
  3.  
  4.  
  5. Event MyClick
  6.  
  7. Public Sub _new()
  8.  
  9.   $hObs = New Observer(Me) As "This"
  10.  
  11.  
  12. Public Sub This_Click()
  13.  
  14.   Raise MyClick
  15.  
  16.  

that works okay, the button will not fire a Click event as overidden to fire the Myclick event.

I'm not sure i get the issue but my experience is to say try to override as little of the class as possible (just the methods you need)

I've had some troubles where i cannot access the class data needed to override properly. and trying to copy a whole class into a project as an override often get issues not being able to find various attached objects, sometimes importing the whole component is the way to go there.

But sure Events can be overridden with Stop Event and methods/ properties can all be overridden.

Security wise i'm not sure i understand?
maybe you could do some sneaky stuff somehow or other but i guess you could do that without inheritance , the openness of your source will be your undoing ;)

There probably is a way to see if a class has overrides (Ben would be the guy to ask about the easiest way to do that)
maybe by checking out the object/class hierarchy ?

Respects
English Bruce
Online now: No Back to the top

Post

Posted
Rating:
#5
Guru
BruceSteers is in the usergroup ‘Guru’

thatbruce said

Very good. But one observation (of course  :twisted: ), AFAIK you cannot override event handlers. If for example you try to override the (self)_Timer event handler then both yours and the native timer events will be called and (AFAIK) there is no way to determine which is called first. Just a caveat.

okay i had a play and it seems the Timer_Timer() event is a good example there of what not to override. you cannot so much "override" the timer event because if you stop the event the inherited timer misses it's trigger and does not trigger again? odd

But you CAN intercept the Timer_Timer() event and do stuff in it like raise other events, set properties, etc but gotta let it trigger it's internal event :)

Code (gambas)

  1. ' Gambas class file
  2.  
  3.  
  4.  
  5. Private $TriggerCount As Integer
  6.  
  7. Event ImTriggered(Count As Integer)
  8.  
  9. Public Sub _new()
  10.  
  11.   $hObs = New Observer(Me) As "This"
  12.  
  13.  
  14. Public Sub This_Timer()
  15.  
  16.   Inc $TriggerCount
  17.   Raise ImTriggered($TriggerCount)
  18.  
  19.  

If you use that Timer.class and use both the Timer and MyTimer event handlers then you find the intercept MyTimer triggers before Timer

Code (gambas)

  1. ' Gambas class file
  2.  
  3. Public Sub Timer1_Timer()
  4.  
  5.   Print "timer event"
  6.  
  7.  
  8. Public Sub Timer1_ImTriggered(count As Integer)
  9.  
  10.   Print "trigger event No";; count
  11.  
  12.  

produces this…

Code

trigger event No 1
timer event
trigger event No 2
timer event
trigger event No 3
timer event


Respects.
Online now: No Back to the top

Post

Posted
Rating:
#6
Avatar
Enthusiast
PJBlack is in the usergroup ‘Enthusiast’
I overload the "TextLabel" class with my own properties. What works so far programmatically, but in the IDE I can not enter new values.

Code (gambas)

  1. ' Gambas class file
  2.  
  3.  
  4. Public Const _Properties As String = "*,IsHeader,IsList"
  5.  
  6. Private $IsHeader As Boolean = False
  7.  
  8.  
  9. Private Function IsHeader_Read() As Boolean
  10.  
  11.     Return $IsHeader
  12.  
  13.  
  14. Private Sub IsHeader_Write(Value As Boolean)
  15.  
  16.     $IsHeader = Value
  17.  
  18.  
  19. Private Function IsList_Read() As Boolean
  20.  
  21.     Return $IsList
  22.  
  23.  
  24. Private Sub IsList_Write(Value As Boolean)
  25.  
  26.     $IsList = Value
  27.  
  28.  
What am I doing wrong?
Online now: No Back to the top

Post

Posted
Rating:
#7
Guru
BruceSteers is in the usergroup ‘Guru’
The code looks good , have you presses the "compile all" button?
Online now: No Back to the top

Post

Posted
Rating:
#8
Guru
BruceSteers is in the usergroup ‘Guru’
 I tried your code and got the variables in the IDE but could not change them.

Seems like a bug

If you rename the class to TextLabel2.class and add "Inherits TextLabel" then it all works as expected.
Online now: No Back to the top

Post

Posted
Rating:
#9
Avatar
Enthusiast
PJBlack is in the usergroup ‘Enthusiast’

BruceSteers said

I tried your code and got the variables in the IDE but could not change them.

same here …

BruceSteers said

If you rename the class to TextLabel2.class and add "Inherits TextLabel" then it all works as expected.
I know … but thats nt what I want

BruceSteers said

Seems like a bug
:D  ;)  then fix it  :P
Online now: No Back to the top

Post

Posted
Rating:
#10
Guru
BruceSteers is in the usergroup ‘Guru’

PJBlack said

BruceSteers said

I tried your code and got the variables in the IDE but could not change them.

same here …

BruceSteers said

If you rename the class to TextLabel2.class and add "Inherits TextLabel" then it all works as expected.
I know … but thats nt what I want

BruceSteers said

Seems like a bug
:D  ;)  then fix it  :P


Haha

I have asked the experts for a reason as to why it does not work……..
Online now: No Back to the top

Post

Posted
Rating:
#11
Avatar
Enthusiast
PJBlack is in the usergroup ‘Enthusiast’

BruceSteers said

[I have asked the experts for a reason as to why it does not work……..

have read …

the property did not show up in the .form because false is the default value for it … having a textlabel2.class then the property show up in .form only if changed to true
Online now: No Back to the top

Post

Posted
Rating:
#12
Online now: No Back to the top

Post

Posted
Rating:
#13
Regular
JumpyVB is in the usergroup ‘Regular’
Thank you for sharing. I just used this to add dotTag to the Image class (to hold metadata extracted by shelling with exiftool).
Online now: No Back to the top

Post

Posted
Rating:
#14
Regular
chrisRoald is in the usergroup ‘Regular’
Hi,
     I have created a class file named Collection which contains an additional public function (method) named 'Exists'…
(I tried to override the class method 'Exist', but got an error "incorrectly overridden" at runtime, so went for Exists function instead)!??

Code (gambas)

  1. ' Gambas class file
  2.  
  3. Public Function Exists(searchValue As Variant, Optional ByRef targetX As Variant) As Boolean
  4.    
  5.     If Super.Exist(searchValue) Then
  6.         targetX = Super[searchValue]
  7.     End If
  8.  
  9.     Return Super.Exist(searchValue)
  10.    

A simple test of the method, with a key:value added to my declared Collection data-type, worked fine,
but at runtime another bit of code (which normally works ok) that reads a json file into a Collection variable failed with the attached error
Image

(Click to enlarge)


Am I missing a bit more code/or a setting in my Collections class file?

Thanks,
C
Online now: No Back to the top

Post

Posted
Rating:
#15
Guru
BruceSteers is in the usergroup ‘Guru’
To override a function the arguments have to match the original

hard to tell what your problem is without seeing more code,

seems it does not like ^Collection which i think means Pointer to Collection

Note: you ARE using ByRef on both sides of the call yes?

Ie.

Code (gambas)

  1.  
  2. If Not MyCollection.Exists("sKey", ByRef vVar) then Return
  3.  
  4.  
Online now: No Back to the top

Post

Posted
Rating:
#16
Regular
chrisRoald is in the usergroup ‘Regular’
Hi Bruce,
     Was using byRef correctly - both sides - but this error is raised even when my Exists method calls are commented-out, ie. unused!

Image

(Click to enlarge)


The line of code highlighted blue, works fine if I take out my overriding Collection class file.  So if you're correct, something about using an overridden data-type class: Collection is causing a data-type confusion where a variable 'as Collection' is declared, OR, the internal output of my JSON.Decode statement is ending up as a pointer rather than a Collection???

I don't know enough about using pointers to get my code back on track  :?
Anything you can see that might be added/altered or tried instead?
Ta,
C
Online now: No Back to the top

Post

Posted
Rating:
#17
Guru
BruceSteers is in the usergroup ‘Guru’
 Very odd

I have no problem overriding Collection.class here , i just ried your Exists method and no problems.

make a sample program and post it, then we can see all that might be causing the error.

or post the file it is loading (filespec)
i suspect a json decode error
Online now: No Back to the top

Post

Posted
Rating:
#18
Avatar
Regular
thatbruce is in the usergroup ‘Regular’

chrisRoald said

but this error is raised even when my Exists method calls are commented-out, ie. unused!
The overridden class is still loaded and replaces the native reference one in the symbol table even if your inserted method is not used.

Suggest, check the typeof the return value from the JSON.Decode. It should be a variant, which I think in this case means it is a pointer. It looks like the Collection class bit that converts a variant to a real collection is failing.
Maybe use a JSONCollection instead? (not tried).

b

Online now: No Back to the top

Post

Posted
Rating:
#19
Guru
BruceSteers is in the usergroup ‘Guru’
The Collection.Exists() method probably should check before trying to set the Optional targetX in case it IsMissing
like this…

Code (gambas)

  1. ' Gambas class file
  2.  
  3. Public Function Exists(searchValue As String, Optional ByRef targetX As Variant) As Boolean
  4.      
  5.     If Super.Exist(searchValue) And If Not IsMissing(targetX) Then targetX = Super[searchValue]
  6.     Return Super.Exist(searchValue)
  7.      
  8.  

But i found a fix to convert the ^Collection to Collection by simply using Copy() on the result,

 try this…

Code (gambas)

  1.  
  2. If Exist(filespec) Then
  3.   loadedMetaData = JSON.Decode(File.Load(filespec)).Copy()    '  a Copy of the ^Collection pointer is a Collection :)
  4.   loadedMetaData = New Collection
  5.  
  6. Return loadedMetaData
  7.  
  8.  
Online now: No Back to the top

Post

Posted
Rating:
#20
Guru
BruceSteers is in the usergroup ‘Guru’
 And another note Chris , be careful overriding built in classes and default methods.

Like for example Collection.class is not just used in your application , many of the loaded components use Collection too,
you can break things by changing the way built in classes original methods work. that's probably why you have to match the arguments and types.

It would be better in your case I think to add an Exists() method rather than modify the existing Collection.Exist() as that could cause unforseen problems.

Good luck
Online now: No Back to the top

Post

Posted
Rating:
#21
Guru
BruceSteers is in the usergroup ‘Guru’
Staying on topic you could probably override the JSON.module Decode method to always return a Copy() if its a ^Collection

by saving this as JSON.module

Code (gambas)

  1. ' Gambas module file
  2.  
  3. Public Sub Decode(JSONString As String, Optional UseNull As Boolean) As Variant
  4.      
  5.   Dim hVar As Variant = Super.Decode(JSONString, UseNull)
  6.   If Object.Type(hVar) = "^Collection" Then Return hVar.Copy()
  7.   Return hVar
  8.      
  9.  

hehe  :D
Online now: No Back to the top

Post

Posted
Rating:
#22
Regular
chrisRoald is in the usergroup ‘Regular’
Thanks Everyone, great detection work 8-)  :)
.Copy was the one idea I didn't get to  :roll:  Thanks for that.
I'll bear in mind the future possible conflicts issue with overriding.  I have however learnt how to create my own datatype - class - as I renamed Collection to Collectionb and used Inherits which could prove more useful/flexible in future. :idea:

Thanks again,
C.
Online now: No Back to the top

Post

Posted
Rating:
#23
Avatar
Regular
thatbruce is in the usergroup ‘Regular’

BruceSteers said


Code (gambas)

  1. ' Gambas module file
  2.  
  3. Public Sub Decode(JSONString As String, Optional UseNull As Boolean) As Variant
  4.      
  5.   Dim hVar As Variant = Super.Decode(JSONString, UseNull)
  6.   If Object.Type(hVar) = "^Collection" Then Return hVar.Copy()
  7.   Return hVar
  8.      
  9.  

hehe  :D

Is hVar freed when it goes out of scope at line 7?
b

Online now: No Back to the top

Post

Posted
Rating:
#24
Regular
ocoquet is in the usergroup ‘Regular’
 Hi Bruce,

A long time for me with no post.

I try to override a class in my project and it's seam to be not possible.

For exemple in my project i've

FMain
Class1

I whant to override Class1 but I can't create a new class1 because IDE said me that the name already exist.

What do you thinq about this ????


Regards
Olivier

Olivier Coquet
Gambas Dev
Le Forum développeur Gambas
Online now: No Back to the top

Post

Posted
Rating:
#25
Guru
BruceSteers is in the usergroup ‘Guru’
You can only "override" built in classes not your own ones that you have added to your project.

If the Class1.class is in your project then you have the Class1.class ,  it does not need to be overridden?, you can just change it.

So i think it does not make sense what you are trying to do.

Maybe you are looking to Inherit ?
You could make a Class2.class and make it Inherit Class1.class then it would be the same as Class1 but with your modifications.
Online now: No Back to the top
1 guest and 0 members have just viewed this.