How to iterate through a frame's children (named or unnamed)

Pretty much the title says it all.

I’ve tried the pairs() iterator but child frames don’t seem to work well in there.

I either get “userdata” types that won’t allow me to go deeper or I get “malformed number” errors.

Surely there is a way to iterate DOWNWARDS through a frame’s “family tree” isn’t there?

for i=1, select("#", frame:GetChildren()) do
	local ChildFrame = select(i, frame:GetChildren())
	-- do whatever
end

for i=1, select("#", frame:GetRegions()) do
	local ChildRegion = select(i, frame:GetChildren())
	-- do whatever
end
1 Like

Bingo. Thanks, Fizzy. I’ve been scouring the interweb for more than an hour looking for that.

1 Like

You can use the Childxxx:GetObjectType() to further refine it.

1 Like
for i=1, select("#", frame:GetChildren()) do
	local ChildFrame = select(i, frame:GetChildren())
	-- do whatever
end

Where it says “frame” - somehow I think I need to be specifying the name of the known parent frame in there.

frame is the frame with the children/regions you want to “get”.

So if I discover a frame using /framestack named “BobsFrame” then this?

for i=1, select("#", BobsFrame:GetChildren()) do
	local ChildFrame = select(i, BobsFrame:GetChildren())
	-- do whatever
end

or this?

for i=1, select("#", _G["BobsFrame"]:GetChildren()) do
	local ChildFrame = select(i, _G["BobsFrame"]:GetChildren())
	-- do whatever
end

Or are those two functionally identical?

Same same…
Frame names are placed in the global table as a reference to the object.

It’s been a while but I beleive first named frame gets the _G entry and others are ignored.

I’ll play and see if I can make that work.

Testing it is a pain because I have to actually queue for a BG to get meaningful data. :slight_smile:

function f.SlashHandler(msg)

	if msg == 'clear' then
		wipe(BGESCT_Data)
	else
		for i = 1, select("#", BattleGroundEnemies:GetChildren()) do
			local iFrame = select(i, BattleGroundEnemies:GetChildren())
			for j = 1, select("#", iFrame:GetChildren()) do
				local jFrame = select(j, jFrame:GetChildren())
				if jFrame:GetName() == 'healthBar' then
					tinsert(e, jFrame)
				end
			end
		end
	end
	BGESCT_Data = CopyTable(e)

end

This is solely for diagnostic and proof-of-concept purposes.

I know that the target frames, the likely-unnamed ones, have a child frame formally named ‘healthBar’ do if the child of the child of the input frame has that then the child is my target frame, the one I want to add an overlay to.

This should at least capture one broken entry per player in the next BG I go into and from that I can derive that I have the correct frame objects stored in “e”.

“e” is defined earlier as an alias of a subtable of the addon data that’s available if you put local addonname, addondata = ... at the beginning of your module.

Not sure what BattleGroundEnemies is but…

If the parent frame (or whatever you’re trying to get the children of) is created in XML, check to see if the child you want has a parentKey attribute

<Frame name="BattleGroundEnemies">
    <Frames>
        <StatusBar parentKey="Health" >
    </Frames>
</Frame>

To get the healthbar child of BattleGroundEnemies you would

local healthBar = BattleGroundEnemies.Health
-- ...

I don’t want the health bar.

The health bar tells me that the immediate parent of health bar is the frame I want. That frame is unnamed so it just comes up jibberish in fstack.

And I am missing one layer of frames in there. Just figured that out. :slight_smile:

Just saying it might be easier to go to the frame declaration instead of repeatedly queueing for BGs or whatever…

As implemented the frames change name every time. There is an unnamed frame on the way DOWN to the one that I’m interested in.

That gibberish between “.Enemies.” and “.healthBar” in my imgur link. That’s the piece I need.

Just realized I had put this all in as a note in the prior thread and didn’t repost the image when I moved it over.

https://imgur.com/a/dG7s650

My goal is to be able to table up the pointer to the frames themselves (right above healthBar and then establish parentage with the frame I want to tack on to it.

D’ot!

If I get BattleGroundEnemies.Enemies.<no name> I’m there. That’s the one I want.

Forget about “healthBar” - I think that’ll work.

If frame:GetFrameName() comes back nil, that’s my target frame.

It’s probably the parent frame you’re looking for

Frame.Enemies = {}

local f = CreateFrame("Frame")
f.HealthBar = CreateFrame("xxx", "yyy, f)

tinsert(Frame.Enemies, f)
for k, v in pairs(Frame.Enemies) do
   v.HealthBar:Whatever()
   -- or possibly k.HealthBar if the frame was added as the key rather than a tinsert etc.
end

Open the BGE config, turn on a test options and

/run for k, v in pairs(BattleGroundEnemies.Enemies.Players) do
	print(k, v, v.healthBar)
end

…Players I can see in the dump. I don’t care about Players. I need access to the unnamed frames just below Enemies.

Please look at the snipped screenshot. The frame names are there.

I do not care about the health bar at all. It was just a way of knowing that the frame above it, which is unnamed, was the one I really wanted.

That said, I’m still not iterating through them correctly.

I’m sorry I ever mentioned “healthBar” - it’s irrelevant to what I’m doing other than as a way of identifying its PARENT.

The XML schema doesn’t reflect what’s actually doing on in the game.

Whether that’s a reflection on how f-awful XML is as a tool for this or on the coding skills of the gentleman who wrote BGE I can’t begin to determined.

The frame BattleGroundEnemies.Enemies is not specific to a single player. It’s a frame that contains (has as children) the frames I want.

THOSE frames are not named (I assume that’s why there’s gibberish where the names would otherwise be).

THOSE frames are the ones I need.

I got distracted by something shiny for a bit (Torghast). I’ll be back to testing my “how do I get a list of frames out of BattleGroundEnemies.Enemies when the frames aren’t named” sub-project in a bit.

Get them when they are created and hook up your “stuff” if you haven’t already
(it took me an age to realise BGE was an addon so I spent a while scratching my head).

local function OnShow(self)
	self.Text:SetText(self:GetParent().Name:GetText())
end
local function CreateText(self)
	local f = CreateFrame("Frame", nil, self)
	f:SetPoint("TOPRIGHT")
	f:SetPoint("BOTTOMLEFT", self, "BOTTOMRIGHT", -5, 0)
	local l, s = self.Name:GetDrawLayer()	
	f.Text = f:CreateFontString(l, s and s+2 or 2)
	f.Text:SetFontObject(GameFontNormal)
	f.Text:SetPoint("RIGHT")
	f.Text:SetJustifyH("RIGHT")
	f:SetScript("OnShow", OnShow)
	return f
end
local f = CreateFrame("Frame")
f:RegisterEvent("PLAYER_ENTERING_WORLD")
f:SetScript("OnEvent", function(self)
	self:UnregisterAllEvents()
	hooksecurefunc(BattleGroundEnemies.Enemies, "SetupButtonForNewPlayer", function(self) -- Enemy frames only
		for k, v in pairs(self.Players) do
			if not v.MyFrame then
				v.MyFrame = CreateText(v) --v:CreateFontString() -- Instead of true, set MyFrame to Create your frame/fontstring etc.
			end
		end
	end)
end)

I’ve just duplicated the name in gold on the right.

OnShow might not be what you want to use to update your “stuff” but…

It’s not MY addon. That’s the problem.

I never leave frames unnamed for just this reason.

I’m attempting to access those frames through global space so I can anchor other frames to them.

Oh, this is hurting my brain.

Just the globals in that program, below Enemies node is 25,000 lines long in the dump.

Experimentation is rough because I have to be IN a BG to do so and doing that is problematic because it messes up other people’s play if I don’t participate fully.

Bugger. Test mode adds the frames outside BG.

Don’t get old. Seriously. :slight_smile:

Wait!

Won’t this work

e = {BattleGroundEnemies.Enemies:GetChildren()}

Shouldn’t that give me a list of all of the frames underneath it?

Try my previous code to hook the frames as they are created. Otherwise you will have to keep checking to see if there are any new ones.

I need to check for new ones, about once per second - it’s not an issue in rated, but in unrated people drop and add all the time (no idea why).

And that did work.

e = {BattleGroundEnemies.Enemies:GetChildren()}

I can’t dump it because I get a stack overflow.

But I can get data from those frames, or I think I can. I’m experimenting now.

Pssshhht. Test mode. Sometimes I’m uh idjit.

Confirmed. I ran a loop through the table of frames printing each frame’s height and width and got good data.

Now I just have to plug in that code you gave me in the last question, set it up to refresh and add/drop as needed and I should be golden.

Thanks a ton Fizzy. Sorry I wasn’t clear in a few places. :slight_smile: