It has been several days since I was on the forum because I was working on documenting what I was seeing which was different from what earlier comments were stating. I wanted to collect it all together and also provide code to demonstrate what I was seeing.
The following are the results of my modifications to the addressingBehaviorsChangesTarget.livecode stack originally created by Bernd on August 12, 2020 and the various modifications of OOPEngine by Mark Wieder and Brian Milby.
Some of what is below is obvious to most of you, but I am being overly descriptive to be certain you know what I did vs assuming I did something, because I may not have, especially since I am new to LiveCode. C++ I know well.
I have tried to provide direct data from the results of the tests that I ran with the attached code, rather than interpretations.
I created a Classes card with the following buttons for my classes:
behavCL2 - base class -- meaning there is no assigned behavior
behavCL1 - subclass of behavCL2 -- meaning its behavior is behavCL2
behaveSee - subclass of behavCL1 -- meaning its behavior is behavCL1
I added at least one script variable for each of the classes along with functions to get and set their values.
Each script variable is initialized to a value in its declaration.
On the main card I have the same UI elements, but I took over the baseButtonDispatch scripts.
The baseButtonDispatch button now has a script variable called sDoIt that contains the instantiated object.
If this were C++, then I would have:
behaveSee *sDoIt; //sDoIt is a pointer to an object whose Class is behaveSee.
One of the attributes that LiveCode has for objects that have a hierarchy of behaviors is that those objects also have an associated data set of script variables, segregated into sections that represent each superClass.
This data set is private to that object and unique to that object AND COMPLETELY SEPARATE from the LiveCode objects that represent the classes.
sDoIt has no script variables and no script code itself. This is important to remember.
When sDoIt is the LiveCode Target, sDoIt's copy of script variables are active, but all the code executed on its behalf belongs to the class objects which will access the sDoIt copy of script variables ONLY as long as sDoIt is the Target.
Going up the class hierarchy to a particular class/superClass command/function involves using "to the behavior of someObject" in the dispatch call. "to the behavior of someObject" changes the Target to that "someObject".
This is why having an objectID parameter is important, it provides access to the data set you actually want to work on.
(sorry to harp on the above so much, but to a C++ programmer the above distinctions are not obvious)
So to put names on things: I create an sDoIt object from the behaveSee Class. I gave it the name sDoIt.
Code: Select all
put newObject("behaveSee", "sDoIt") into sDoIt
The sDoIt object has its own copy of behaveSee script variables, behavCL1 script variables and behavCL2 script variables.
If you make a call to any of the classes themselves without referencing the sDoIt objectID, then you are going to be operating on either the class's own script variables or something else. It depends upon how you made the call.
So I modified the OOPengine's newObject function so that instead of calling a method named constructor when initializing the new object that has been created, the methods are named the same as the class name. (At one point Mark had this in his OOPengine)
In addition, the constructor methods receive the ObjectID of the new object that has just been created.
Code: Select all
# snippet from invokeConstructorsOf pObject command
# call the class constructors in order
repeat for each line tClass in tClassList
--dispatch "constructor" to tClass --initializing with class not instantiated object
dispatch tClass to pObject with pObject --calls super class's constructor with instantiated object.
-- and last iteration through repeat loop calls the constructor of the class of the instantiated object.
end repeat
While it is NOT an object related to a class anymore, I did put a named constructor into the baseButtonDispatch script.
That command is called from the mouseUp handler of setBehaviors.
The sDoIt object is created then.
Clicking on the baseButtonDispatch button (no shift key, no control key) causes the following code to execute:
Code: Select all
if sDoIt is not empty then
dispatch "reportBehav" to sDoIt with sDoIt
deleteObject sDoIt
put empty into sDoIt
else
put "" into field 1
end if
Remember, sDoIt's class is behaveSee, so it has a private copy of all script variables for that class hierarchy and can access all of the commands/functions of that class hierarchy. That dispatch command starts the whole process off.
Key things about that dispatch command:
-"to sDoIt" sends the command to the sDoIt object, which means behaveSee class handlers - there is no sDoIt code.
- "with sDoIt" explicitly sends the whole unique data set assigned to the sDoIt object to the reportBehav handler.
Other KEY THINGS:
In the dispatch above: -"to sDoIt" means that sDoIt is the "Target" during this execution of reportBehav in the behaveSee class.
IF you attempted to get to the same code: reportBehav in the behaveSee class, but used the superCommand command, things would be radically different. Since there is no reportBehav code in the sDoIt button script, you might think you are going to the same place, BUT...
superCommand uses "to the behavior of sDoIt" to specify the destination, which is the behaveSee Class Object.
So now the "Target" is the behaveSee Class object, NOT the sDoIt object.
You got to the same code, but the Target changed to that of the "Class".
So IF at any time you are going to need to access a command/function via a "to the behavior of" means, then that command/function MUST have an ObjectID parameter and it MUST use it to access everything because the "Target" will be the "Class Object" not the instantiated data object that was created via the newObject() function call.
So now we are in behaveSee's reportBehav handler.
At this point, the target is the button object sDoIt, named sDoIt. The target's behavior is behaveSee.
These are the same as the values from the "pid" parameter to the reportBehav handler.
In this handler, "this me" is the behaveSee class. The "this me" behavior is behavCL1, which is its superClass.
Now we want to go to a method of the superClass: behavCL1. In this case I chose to call a function. I encapsulated that call in a function contained in the main card stack: superFunction. In this case, I also added a couple parameters.
The call is:
if superFunction("reportBehav", long id of this me, pid, "SecondParam", "ChickenParm") then end if
Code: Select all
function superFunction pFunction, pClass, pObjectID
if the behavior of pClass is not empty then
// need to yank following put from final code
put "from Stack superFunction: " & "Class getting super of: " & pClass & cr & "method: " & pFunction & cr & "name ID: " & name of pObjectID & cr after field 1
local tParams
--THIS SEEMS THE WAY TO DO THIS, IS IT REALLY?
--params() and param(i) deliver way too much stuff to parse
put empty into tParams
if paramCount() > 2 then
repeat with i = 3 to the paramCount
put param(i) & comma after tParams
end repeat
delete last char of tParams
end if
dispatch function pFunction to the behavior of pClass with pObjectID, tParams
return the result
end if
return "unhandled" --don't know how to really return "unhandled" value
end superFunction
In this case, "behavior of pClass" causes the dispatch to go to behavCL1.
"with pObjectID" guarantees that the behavCL1 method receives the sDoIt object to work with.
Now at the lowest subclass level, you could dispatch to "behavior of pObjectID" and it would be correct and go to the intended superclass method.
BUT...
once you get higher up the chain, behavior of pObjectID is pointing to the wrong class level.
So the correct way to specify the pClass is: long id of this me
That will always go to the next level up the class hierarchy from where the code is.
(we are assuming always calling superFunction from within a Class method (command or function))
In this function, I try a dispatch to "long id of this me" to see where it goes.
behavCL1 does have both a reportBehav command and a reportBehav function.
Code: Select all
dispatch "reportBehav" to the long id of this me
Without an objectID parameter, the target of the reportBehav command changed to the behavCL1 class object, not the sDoIt object.
Next, this function is going to call a command handler in the super class of behavCL1: which is behavCL2, using the stack command: superCommand.
Code: Select all
command superCommand pCommand, pClass, pObjectID
if the behavior of pClass is not empty then
// need to yank following put from final code
put "from Stack superCommand: " & "Class getting super of: " & pClass & cr & "method: " & pCommand & cr & "name ID: " & name of pObjectID & cr after field 1
dispatch pCommand to the behavior of pClass with pObjectID
return the result
end if
return "unhandled" --don't know how to really return "unhandled" value
end superCommand
Once again, objectID is needed.
The target received is behavCL2. But the objectID received is that of sDoIt - you find that when printing the "name of pid".
In this command, it prints its script variable (sB2Var) referencing it directly AND through its accessor function dispatched to the pid (sDoIt) object.
Code: Select all
dispatch function "getSB2Var" to pid
When referencing sB2Var directly, you get the original value initialized by the script.
(you can also call the behavCL2 function getSB2Var() and you get the same value as referencing the script variable directly)
When fetching sB2Var from pid using the above code, you get the value that was set in the reportBehav handler of the behaveSee class - when the target is the sDoIt object.
After returning from the behavCL2 reportBehav, more work is done in the behavCL1 reportBehav and then more is done in behaveSee reportBehav.
So in summary:
You can call superclass methods and have them return to your calling method.
You can have code that gets executed before and after the call to the superclass method.
You use either dispatch or dispatch function and it is critical how you specify the class whose method you want called and that you provide an objectID for the object variables.
You can access superClass fields, there are Class fields and object fields and they are separate.
I suggest putting in getters and setters for all class fields that you wish to access from outside the class.
You call those getters and setters with:
dispatch function "getter" to pid
dispatch "setter" to pid with pValue
In general, if you are using these mechanisms as a C++ equivalent, it is NOT desirable to access the script variables of the "Class objects". That is NOT done in C++ because the Class itself does not exist like a LiveCode object does.
Yes there are static Class fields, available to all their subclasses, but those are for special circumstances since there is only ever one instance of that field.
The superCommand and superFunction handlers are useful because they remind you how to access the superClass and don't eliminate having multiple parameters beyond the objectID.
I hope this was useful detailing some of the differences between LiveCode and C++, and it also shows that there is a very workable, usable means of using C++ concepts in LiveCode (at least for me).
I attached the two LiveCode files that I modified that I referenced at the top of this reply.