PDA

View Full Version : Programmatic reverse traversal of a window's control hierarchy?



jambrose
January 15th, 2017, 00:00
Is there a way to programmatically walk back up a control hierarchy so as to find a particular parent control, with the eventual goal of gettings its database node?

For example, I'm deep in a windowlist control finding that I need to monitor the events of a control that is not a) in this particular windowlist and b) not within the current database node. However, I know that the top level windowclass has what I need...the character sheet database reference. The following is what I'm doing inside of a numberfield control inside of a windowlist. I can manually walk back out to the top but I want to do this generically, sort of a like Interface.findWindow does except not needing the data reference since I don't know it!


Debug.console("bonusField onInit: "..window.windowlist.window.parentcontrol.window.ge tDatabaseNode().getNodeName());

The above will get me where I want to be, but it is fragile. If I change the window hierarchy in any way it breaks. I want a generic way to get to my root node, or whatever node I want that is above the current control.

Alternatively, is there a way to easily access the DatabaseNode of the main root window class for the current window? Or is there way to store its reference in a global variable scoped to the current window so I can just reference it when I need it?

damned
January 15th, 2017, 00:10
You are sooooo much better using the db directly... avoid using this cascading window references whereever you can...
What database node is it in?
What database node are you in?

jambrose
January 15th, 2017, 00:47
The db node of the list item is something like:


<charsheet>
<id-00001>
<skills>
<id-00001>


The db node I'm trying to attach a handler to so this control, which is not data aware, although it certainly could be is:


<charsheet>
<id-00001>
<characteristics>
<characteristicname>
<bonus type="number">-2</bonus>
<current type="number">1</current>
</characteristicname>


The end goal is to update the value of this skill list item to reflect a change in the characteristic bonus based on changes to the characteristic selected, in another control in that list item, and/or upon changes to the base characteristic itself, which updates the bonus value in the db.

I do not have a problem targeting this control at the actual db value, it was getting to it from within this control which is inside another db hierarchy. Additionally it can change frequently as the user can select which characteristic to use for that particular skill check. My goal was to have the bonus field attach to the db node "characteristics.characteristicname.bonus" via a handler then just respond to the onUpdate event as necessary.

It feels like there must be an easier way to find the db node I want to attach a handler to so any suggestions welcome.

damned
January 15th, 2017, 01:05
if <skills> and <characteristics> are both within the same charsheet.id it should be straight forward?

jambrose
January 15th, 2017, 02:50
Wouldn't that mean I'd have to walk back through the database nodes using getParent() and so forth?

I have this dream that I won't have to do that, just in case structure changes. My dream is to have a way to say gimmeTheCurrentCharacterID() regardless of where I am databaseNode wise. Is this possible?

damned
January 15th, 2017, 03:09
Im not sure if Im following or giving you good/bad info:

try: window.getDatabaseNode();
Use Debug.console("Something: ", yourvariable);
liberally throughout your code while testing.

jambrose
January 15th, 2017, 03:20
Here is what I am doing, which now works:



-- walks back to the charsheet.id node
local dbNode = window.getDatabaseNode().getParent().getParent();

-- grabs the value of the selected characteristic for this skill from an adjacent control
local targetCharacteristic = window.characteristic.getStringValue();

-- Sometimes the selected characteristic is nil or empty avoid processing
if (targetCharacteristic ~= nil) and (targetCharacteristic ~="") then
-- Build the actual path to the bonus node I want which is charsheet.id.characteristics
dbBonusNode = window.getDatabaseNode().getParent().getParent().g etNodeName()..".characteristics."..string.lower(targetCharacteristic)..".bonus";

DB.addHandler(dbBonusNode, "onUpdate", onSourceUpdate);
end


Which now works and the handler on bonus now fires. Yay! I had misspelled characteristics to characteristic...which of course was causing some problems. :p

Any thoughts on how to simplify this? Especially with all those getParent() calls. That bothers me, but maybe it is what I have to do.

damned
January 15th, 2017, 03:26
post the content of the db.xml?

Moon Wizard
January 15th, 2017, 04:30
There's not too much to simplify. Here's an alternative:



local sTargetCharacteristic = window.characteristic.getStringValue();
if (sTargetCharacteristic or "") ~= "" then
local nodeChar = window.getDatabaseNode().getChild("...");
local sHandlerPath = DB.getPath(nodeChar, "characteristics." .. sTargetCharacteristic:lower() .. ".bonus");
DB.addHandler(sHandlerPath, "onUpdate", onSourceUpdate);
end


Or:



local sTargetCharacteristic = window.characteristic.getStringValue();
if (sTargetCharacteristic or "") ~= "" then
local aPathNodes = StringManager.split(window.getDatabaseNode().getPa th(), ".");
local sHandlerPath = DB.getPath(aNodes[1], aNodes[2], "characteristics." .. sTargetCharacteristic:lower() .. ".bonus");
DB.addHandler(sHandlerPath, "onUpdate", onSourceUpdate);
end


Cheers,
JPG

Zhern
January 15th, 2017, 05:37
Jambrose, when you say walk back, are you thinking of the database nodes as the branches of a tree? You don't have to traverse anything to get back to another part of the window, you can query the database node directly, as damned mentioned. I guess maybe I'm not following what you are trying to do. Moon Wizard provided an alternative, which I understand, but I'm still curious (for my own learning) on your thought process.

Thanks,

Patrick

Bidmaron
January 15th, 2017, 05:42
I think he doesn't like the fact that any of the solutions proposed so far rely upon the depth of the node in his control being a non-changing value relative to the parent character node. He wanted something that was a little more bullet-proof if the data model ever changed (which I can understand, but a whole lot of code would break if that ever happened).

Trenloe
January 15th, 2017, 06:21
I do not have a problem targeting this control at the actual db value, it was getting to it from within this control which is inside another db hierarchy. Additionally it can change frequently as the user can select which characteristic to use for that particular skill check. My goal was to have the bonus field attach to the db node "characteristics.characteristicname.bonus" via a handler then just respond to the onUpdate event as necessary.

It feels like there must be an easier way to find the db node I want to attach a handler to so any suggestions welcome.
The code Moon Wizard presents uses the relative node references of ... to ascend the database hierarchy - essentially doing the same as multiple getParents - this is described here: https://www.fantasygrounds.com/modguide/database.xcp

But, as you say, if the hierarchy ever changed, your code would break. To be honest, if your hierarchy changed you'd probably have to change a few other things anyway - so you might be best coding for a fixed database hierarchy anyway. However, from the db.xml hierarchy for <charsheet>, you can see that the high level node for a specific character is always charsheet.id-XXXXX where XXXXX is a 5 digit number, 00001 for the first character, 00002 for the second, etc.. So the first 18 characters of window.getDatabaseNode().getNodeName(), in a window that is dealing with a character sheet, will always return the character sheet reference. So local nodeChar = string.sub(window.getDatabaseNode().getNodeName(), 1, 18) will return the character sheet database reference (as a string), for example: charsheet.id-00001

Info on string.sub here: https://www.lua.org/manual/5.1/manual.html#pdf-string.sub
Info on <databasenode>.getNodeName here: https://www.fantasygrounds.com/refdoc/databasenode.xcp#getNodeName

jambrose
January 15th, 2017, 06:25
Moon Wizard:
What is this ellipsis (...) notation in .getChild? It seems like a function declaration that allows a variable number of parameters? What is going on here? At any rate thank you for the improvements to my code!

My intent was to get to the spot where I knew the data node information existed all the way up on the char sheet. Hence my attempt to traverse the window hierarchy, which I quickly realized...sucked. I wasn't a lot more pleased about traversing the campaign data in reverse either, what if the data structure changed? My functions would break, as Bidmaron states. Now if I re-design this property and put the code in a single module at least maintenance is easier if something changes. Additionally, the data structure is less likely to change than the window model so meh I'll live with it for now. Besides, as Bidmaron points out, if the data model changes...pretty much everything breaks anyhow.

jambrose
January 15th, 2017, 06:31
Trenloe: Thank you! I was scouring the site looking for what that ellipsis notation did! I presumed it was going back three levels since that was the result when I implemented it. Can you do more than three periods to go more levels?

Thank you for the references to string.sub as well. I recognized that the first 18 characters were the character sheet reference, but I wasn't familiar with the string functions in Lua yet. Hmmm. Now that I understand this I can probably rewrite a few of my other functions that tortiously get the character identifier.

Trenloe
January 15th, 2017, 06:35
Can you do more than three periods to go more levels?
You can use one or more periods - as many as the DB levels exists.

More info in the "Node identifiers" section at the bottom of this page: https://www.fantasygrounds.com/modguide/database.xcp "Adding further dot characters after the first to the beginning of the identifier will cause a corresponding number of steps upwards in the tree structure. Steps deeper into the tree can be specified by separating successive nodes with the dot character."

Scripting resources are available here: https://www.fantasygrounds.com/modguide/scripting.xcp

damned
January 15th, 2017, 07:57
Moon Wizard:
What is this ellipsis (...) notation in .getChild? It seems like a function declaration that allows a variable number of parameters? What is going on here? At any rate thank you for the improvements to my code!

My intent was to get to the spot where I knew the data node information existed all the way up on the char sheet. Hence my attempt to traverse the window hierarchy, which I quickly realized...sucked. I wasn't a lot more pleased about traversing the campaign data in reverse either, what if the data structure changed? My functions would break, as Bidmaron states. Now if I re-design this property and put the code in a single module at least maintenance is easier if something changes. Additionally, the data structure is less likely to change than the window model so meh I'll live with it for now. Besides, as Bidmaron points out, if the data model changes...pretty much everything breaks anyhow.

The database wont change its structure without you changing it. And its far more likely to remain static than your windows.