Creating Controls dynamically using variables

Ok so I feel like I’m close on this but I’m obviously doing something wrong. As an example if you wanted to create say a control for every member in a raid it makes more sense to do that dynamically so if there are only ten people in raid you create 10 controls not 40. So is there a way to create controls using variables? I currently have this;

local MyAddonTexs = {};
local MyAddonFS = {};

for i = 1,8,1
do
	-- Create eight groups
	for j = 1,5,1
	do
		-- Create five players per line
		-- Create the textures for each player
		MyAddon.texture = MyAddonTexs["MyAddonTexsG"..i.."P"..j];
		MyAddonTexs["MyAddonTexsG"..i.."P"..j]:SetAllPoints();
		MyAddonTexs["MyAddonTexsG"..i.."P"..j]:SetColorTexture(1,1,0.5);
		MyAddonTexs["MyAddonTexsG"..i.."P"..j]:SetAlpha(1.0);
		
		-- Create each player in the group
		MyAddon.font = MyAddonFS["MyAddonFSG"..i.."P"..j];
		MyAddonFS["MyAddonFSG"..i.."P"..j] = MyAddon:CreateFontString();
		MyAddonFS["MyAddonFSG"..i.."P"..j]:SetFont("Fonts\\FRIZQT__.TTF", 24);
		MyAddonFS["MyAddonFSG"..i.."P"..j]:SetPoint("TOP", 60*j , -100*i);
		MyAddonFS["MyAddonFSG"..i.."P"..j]:SetTextColor(0,0,0);
		MyAddonFS["MyAddonFSG"..i.."P"..j]:SetText("Name"..i..j);
		MyAddonTexs["MyAddonTexsG"..i.."P"..j]:SetAllPoints(MyAddonFS["MyAddonFSG"..i.."P"..j]);
		print("Group "..i.." Player "..j);
	end
end

The logic was that I’d create two control arrays, one for the fontstrings and one for the textures. Then each FS and tex would be dynamically created in the loops. I know I can tidy this up more by creating a template and inheriting but I’m just trying to get the basics working first.

Any ideas what I’m doing wrong?

edit: As far as I can tell they’re being created fine. But it’s falling over on the line where I try to bind each texture to a FontString.

In this instance, it would probably be easier to create a frame for each “unit” and then add the texture/fontstring to that. This would allow you set a fixed size for the unit frames to make alignment easier while the text and it’s backgound can remain evenly spaced (quick and dirty example)

local Units = {}
local topOnLine

local function CreateUnit(parent, id)
	local f = CreateFrame("Frame", "CanackiRaidUnit"..id, parent)
	Units[id] = f
	f:SetSize(80, 20)
	f.Texture = f:CreateTexture()
	f.Texture:SetTexture("Interface/BUTTONS/WHITE8X8")
	f.Texture:SetVertexColor(1,1,0.5)
	f.Text = f:CreateFontString()
	f.Text:SetFont("Fonts\\FRIZQT__.TTF", 12)
	f.Text:SetTextColor(0, 0, 0)
	f.Texture:SetAllPoints(f.Text)
	f.Text:SetPoint("CENTER")
	if id == 1 then
		f:SetPoint("TOPLEFT", 5, -5)
		topOnLine = f
	elseif mod(id-1, 5) == 0 then
                f:SetPoint("LEFT", topOnLine, "RIGHT")
                topOnLine = f
	else
		f:SetPoint("TOP", Units[id-1], "BOTTOM")
	end
	f.Text:SetText("Raid"..id)
end

local f = CreateFrame("Frame", "CanackiAddonFrame", UIParent)
f:SetSize(500, 120)
f:SetPoint("TOPLEFT", 20, -20)
f.Texture = f:CreateTexture()
f.Texture:SetAllPoints()
f.Texture:SetTexture("Interface/BUTTONS/WHITE8X8")
f.Texture:SetVertexColor(0, 0, 0)

for i=1, 25 do -- add 25 "units"
	CreateUnit(CanackiAddonFrame, i)
end

Units[25].Text:SetText("Last Unit") -- change the name of the last unit
CreateUnit(CanackiAddonFrame, #Units + 1)
Units[#Units].Text:SetText("No I am") -- change the name of the last unit

You could make the fontstrings a fixed size without the frames but this approach would probably be easier for starting off.

Thanks again for your help. Splitting out the code into it’s own function is a good idea that was on my list of things to do - once I got the initial design working.

Can you tell me whether there’s much (if any) benefit trying to do it the way I was with just textures and fontstrings? I assumed not creating the extra frames would reduce overhead, but I guess if it’s minimal then maybe it’s not worth the effort.

It was mainly to do with spacing. If you are creating background texture the same size as the fontstring then they will be different sizes unless the text is always the “length” so you have to juggle alignment unless, you make something a fixed size (the texture or fontstrings or in the above case the frame).

You can do it any way that works for you like changing the function to just use textures/fontstrings and say make the texture a fixed size.

local function CreateUnit(parent, id)
	local f = parent:CreateTexture()
	Units[id] = f
	f:SetSize(75, 18)
	f:SetTexture("Interface/BUTTONS/WHITE8X8")
	f:SetVertexColor(1,1,0.5)
	f.Text = parent:CreateFontString()
	f.Text:SetFont("Fonts\\FRIZQT__.TTF", 12)
	f.Text:SetTextColor(0, 0, 0)
	f.Text:SetPoint("CENTER", f)
	if id == 1 then
		f:SetPoint("TOPLEFT", 5, -5)
		topOnLine = f
	elseif mod(id-1, 5) == 0 then
                f:SetPoint("LEFT", topOnLine, "RIGHT", 5, 0)
                topOnLine = f
	else
		f:SetPoint("TOP", Units[id-1], "BOTTOM", 0, -5)
	end
	f.Text:SetText("Raid"..id)
end

You could make the fontstring a fixed size or jump through whatever hoops to get everything to align (or not), it’s up to you.

1 Like

Good point about the spacing. Thanks for the clarification. I’m just trying to learn a bit about good practice with Lua in WoW as well.

My initial test design was using XML and Lua and it seems like the current practice is to not use an XML file for the layout anymore. So I was trying to learn “the right way” to do it in Lua (if there is one).

What works for you is the “right way”.

XML or all lua or a bit of both, WoW doesn’t care and at the end of the day, you will have to read it to maintain it.

About the only things you NEED XML for is inheriting secure temeplates (or any other XML templates) or creating intrinsics, neither of which you seem to be doing atm.

I want to retouch on this because I’ve been silly in my naming conventions. And before I ask, yes I should turn the saved values into a table. But the issue bugs me so I’d like an answer…

Assuming I have saved variables along these lines;

ValueOneText = "my name"
ValueTwoText = "another name"
ValueThreeText = "yet another name"

So if I want to display them in fontstrings on in a grid I would try to loop through the values and create matching fontstrings. I’d like to name my Fontstrings based on the variables so I can find them again later if I need to change or reference them (like for positioning). So what I want to be able to do is refer to;

CarnFrameValueOneTextFS:SetText(ValueOneText)

and when I get to positioning;

CarnFrameValueTwoTextFS:SetPoint("LEFT", "CarnFrameValueOneTextFS", "RIGHT", 0, 0)

I know the code from earlier in this thread deals perfectly with sequential items, but it references them as item[1] and I was wondering if it’s possible to create variable and object names from dynamic strings.

Ultimately I will change the way I store my code to something more like;

ValueText = {[1]="My name", [2]="another name", [3]="yet another name"}
CarnFrameValueTextFS[i]:SetText(ValueText[i])
CarnFrameValueTextFS[i]:SetPoint("LEFT", "CarnFrameValueTextFS["..i-1.."]", "RIGHT", 0, 0)

I’d just really like to know if I can construct “CarnFrameValueOneTextFS” and actually use it to create a Fontstring and reference it.

When creating widgets you can use $parent in the widgets name to prepend the name of the parent frame(s). Because the name is added to the global table as a reference you can then use that to “find” the widget ie.

local f = CreateFrame("Frame", "CarnFrame", UIParent)
f.Fontrstring1 = f:CreateFontString("$parentText", "OVERLAY", "GameTooltipText")

The “$parentText” being a string can be created from your variables list/table.

You can now use the following for getting/setting the text of f.Fontrstring1

f.Fontrstring1:SetText("Blah") -- use the local reference
CarnFrame.Fontrstring1:SetText("Blah") -- Global frame reference + widget key
CarnFrameText:SetText("Blah") -- global reference to the fontstring
_G["CarnFrameText"]:SetText("Blah") -- As above but useful "building" widget names from string values (like in Saved Variables etc. eg. _G["CarnFrame"..SomeVariableWithTheRestOfTheName])

The $parent will subsitute ALL parent names of ALL parent frames which is why some names in /fstack can get pretty long and convoluted.

Hmm ok. I think I follow that. If I understand correctly then every object you name that way winds up being “ParentName”…something. So you literally can’t create a fontstring called “ThisName” if the main frame is “SomethingElse”. The closest you’d get is “SomethingElseThisName”.

I think that’s only going to confuse me more. I’ll just stick with saving the values in tables and dealing with them (and widgets) in tables.

So you literally can,

local T = xxx:CreateFontString("ThisName")
ThisName:SetText("Blah")

But because the name “ThisName” is added to the global table along with the names of all the other widgets from all the other addons including the entire Blizzard UI plus their global variables, global functions etc. etc., your names need to be competely unique. Using $parent just makes that easier by say using the addon name as the base frame name and not having to re-type it for every child frame/widget.

It’s also why storing children as keys of the parent instead of long names becomes convenient.

Ok, but all this goes back to creating variable names programmatically.

So if I have;

str1 = "MyCrazyHopefullyUniqueComboOfWords"
str2 = "EditBox"
i = 3  -- (a value from a loop)

I can put them together to create my widget like so;

local EB = MainFrame:CreateFrame("EditBox", str1..i..str2, Mainframe, "InputBoxTemplate")
[str1..i..str2]:SetText("Blah")

And that’ll create an Edit Box named “MyCrazyHopefullyUniqueComboOfWords3EditBox” and then set it’s text value to “Blah”?

And can I then do;

[str1..i..str2]:SetPoint("LEFT", [str1..i-1..str2], "RIGHT", 0, 0)

Which should position “MyCrazyHopefullyUniqueComboOfWords3EditBox” to the right of “MyCrazyHopefullyUniqueComboOfWords2EditBox”?

edit: And the local “EB” is basically a throwaway that’s not really used again?

You would have to get the reference from the global table (which is _G as in _G = {}

_G[str1..i..str2]:SetPoint("LEFT", _G[str1..i-1..str2], "RIGHT", 0, 0)

EB is the local reference and can be used to access the widget while it (the EB variable) is in scope (so long as you don’t overwrite it).

EB:SetPoint("LEFT", _G[str1..i-1..str2], "RIGHT", 0, 0)

Ok I think I understand that better then.

Although isn’t the name parameter in SetPoint a string? So shouldn’t the second value work without having to refer to the global table?

And if you define you own namespace could you use NS[str1…i…str2] rather than _G[str1…i…str2]?

The second parameter in SetPoint is a reference to the widget you are anchoring to (all widgets are tables). Using _G[…] allows you to get that reference using the frame name just like getting any string key from a table

NS.SomeFrame = CreateFrame("Frame")

is the same as

NS["SomeFrame"] = CreateFrame("Frame")

so

local x = NS.SomeFrame

is the same as

local x = NS["SomeFrame"]

The dot notation is an easier form when accessing known string key values. The [] is more flexable and also used for numeric key values.

But if you add a name to a widget, it will be placed in the global table as a reference regardless of any other place you store that referece (the addon private table (NS in this case) or some local variable or table or as a key on some other widget etc.)

The reference is essentially pointing to the widget (table) in memory and all the references will point to the same place (there it is, go get it!)

Ahh sorry I was confusing setpoint and createframe (it’s late and I’m getting tired).

On a closer inspection I see it’s SetPoint(“LEFT”, MainFrame) compared to CreateFrame(“Frame”, “MainFrame”). Sorry about that.

I guess with the naming I’m trying to understand what will and won’t work. So if you use the dot notation trying to build an object name dynamically from strings isn’t going to work. But if you use [] it should be possible.

As for the idea about using NS, I was thinking that would be less likely to encounter name collisions than using _G. So if instead of using “MyCrazyHopefullyUniqueComboOfWords3EditBox” I used “MainEditBox1” I should get my widget not one from another addon that happens to be named the same. Or am I understanding that completely wrong.

Using “MainEditBox1” as the name in Createxxx() will put MainEditBox1 in the global table regarless of where you store other references. NS.MainEditBox is certainly easier to type than MyCrazyHopefullyUniqueComboOfWords3EditBox or _G["MyCrazyHopefullyUniqueComboOfWords3EditBox"]

but

NS.MainEditBox = CreateFrame("EditBox",  "MyCrazyHopefullyUniqueComboOfWords3EditBox", UIParent)

You can use either (NS.MainEditBox and MyCrazyHopefullyUniqueComboOfWords3EditBox both point to the same EditBox).

If you used say

local x = CreateFrame("Frame", nil, UIParent)

with no name then x is your only reference to the frame other than going through the list of UIParents child frames (GetChildren) and somehow trying to figure out which one it is.

The ones with no names that show up un /fstack as as just random looking numbers and letter.s

So maybe I’m not understanding the creation of an addon namespace properly. I’d assume if I use NS.MyName then sure MyName will be in both NS and _G, but if have another addon installed that used MyName it’d be in their namespace and _G, but not mine.

So me using NS.MyName will get “Carn’s” MyName not “OtherAddon” MyName. Or does the fact they’re all ultimately in _G mean that I can make NS.MyName but someone elses MyName will literally overwrite it anyway?

The “NameSpace” table is private and exclusive to each addon. Each addon gets it’s own private table passed to its .lua files via the local AddonsName, NS = ....

Think of the lua file itself as a function and the … are the parameters passed to that function (first parameter is a string with the addon, second parameter is the table that’s exclusive to the addon and others may be added in future)

In your case, NS and _G are two completely different tables _G is common to every addon, NS is private to your addon.

CreateFrame adds the refernce to _G ujsing the name but you can store a copy of that reference in NS as anything you like.

1 Like

Something I did when I was working on an ugly (and ultimately abandoned) button bar replacement was like this:


local frameList = {}

function f.MakeSpecialFrame(frameInternalName, frameParent)

    local x = CreateFrame("Frame", nil, frameParent)
    frameList[frameInternalName] = x

end

That’s an oversimplification and the scope of frameList was not really local to the module (it was local to the addon).

But tracking frame relationships in a table for that purpose was simpler for me than dealing with _G space or using the anonymous frame names that you get if you don’t name them there.

2 Likes

This is where I’m stumbling though. So I’ve created a Frame and it’s in NS and _G so I can refer to it either way. But any addon on my machine can access _G right? So if I’ve used a super common name then another addon can say change the Frame colour if they reference it using _G?