Creating Controls dynamically using variables

All widgets can be accessed by other addons either by name (if it’s been used) or by hunting for it. If I remember correctly, if you use a name that has already been created by another addon then CreateFrame ignores adding it to _G to preserve the original (Blizzard UI (mostly) loads before addons so they get first crack at naming things).

The widget is a thing in memory, variables just hold addresses on how to get to them quickly.

1 Like

Haha ok clear as mud. I might specifically make two test addons that reference the same control and see what happens. Thanks again.

ElvUI and some other addons have a completely separate addon that manages control definitions from the actual addon.

They go through the ACE2 framework (something I’m not fond of) but in principal, you can create an object in one addon and manipulate it in a completely different addon.

A lot of addons are just moving stock UI elements around, or theming them or whatever, which are just widgets (mostly with names) created by Blizzard.

1 Like

I’m not trying to muddy the waters, Carnacki. I just see a lot of the same questions I got answered here, on this board, and I’ve been where you are.

The documentation is spotty, the rules seem capricious (sometimes), there is inconsistency in how things are implemented, but mostly I play WoW so I can code addons.

I don’t actually enjoy the game all that much.

It’s the coding platform I come for.

Nothing I’m doing at the moment touches blizzard UI. Maybe something in the future, but I’m too scared the break something and have it play up in the middle of M+ or raid. At least until I’m a bit more confident about what I’m doing.

The first thing I’m working on is mostly for amusement (mine and guildies) and to gain a better understanding.

Seems like a good plan.

I think my first foray into touching the Blizzard world was my nameplate addon for PVP that puts the class/spec in text on the nameplates.

I had a heck of a time getting that to work correctly, but it was worth it.

No. I take that back. I had a bag resizing addon and a map addon that I got significant help for here.

This is one of the biggest problems I’ve had. Even though it might not look like it from the number of posts here but I google and look up WoWpedia before asking for help. The number of articles/examples that seem to be incomplete, or too simple or conversely too complex is crazy. I went looking for a good basic dropdown example and wound up finding one on facebook, not any of the more “regarded” sources. And a ton of links on WoWpedia just don’t exist. Found a settextcolor for something, but there is no link. And when I set it in my addon it didn’t throw an error, but it didn’t do anything visible either… so who knows 0_o

townlong-yak and this forum were my two greatest tools when I was actively developing addons.

You can actually download the entire framexml structure to a working library somewhere and play with it - see how Blizzard does things.

Sometimes you’ll run up against protected or restricted code in there, but it’s eye opening how much you can glean from just that site.

https://www.townlong-yak.com/framexml/live

1 Like
1 Like

I’ll have to take another look at Townlong Yak. I haven’t been there since they stopped work on VenturePlan. Thanks for the reminder.

That might be it Fizzlemizz, though I couldn’t find that page with the nice concise explanation of why it didn’t do anything.

I’m late to this thread, and I admittedly didn’t read it all, so apologies if this derails the discussion. (Anything Fizzlemizz says I’d consider 100% correct so anything I suggest that disagrees with her is just a different approach and one method isn’t better than another)

That said, one advice I have in structuring your controls is to use the unordered table-ness of frames and regions. Tables are the heart and soul of Lua and the hierarchical parent-child relationship of frames and regions is the heart of soul of XML. Embrace it.

Here is a little dialog that creates one global, MyMainFrame, with two panels that alternate with an Ok button. Everything built off it has a relationship to the main parent:

local f = CreateFrame("Frame","MyMainFrame",UIParent,"BasicFrameTemplate")
f:SetSize(300,300)
f:SetPoint("CENTER",-200,0)

f.TitleText:SetText("Test Dialog")

f.Panel1 = CreateFrame("Frame",nil,f,"InsetFrameTemplate")
f.Panel1:SetPoint("TOPLEFT",4,-24)
f.Panel1:SetPoint("BOTTOMRIGHT",-7,6)
f.Panel1.Bg:SetDrawLayer("BACKGROUND",1)

f.Panel1.Instructions = f.Panel1:CreateFontString(nil,"ARTWORK","GameFontNormal")
f.Panel1.Instructions:SetPoint("TOP",0,-32)
f.Panel1.Instructions:SetText("Enter your name and click Ok")

f.Panel1.EditBox = CreateFrame("EditBox",nil,f.Panel1,"InputBoxTemplate")
f.Panel1.EditBox:SetSize(200,24)
f.Panel1.EditBox:SetPoint("TOP",f.Panel1.Instructions,"BOTTOM",0,-16)
f.Panel1.EditBox:SetAutoFocus(false)

f.Panel1.Button = CreateFrame("Button",nil,f.Panel1,"UIPanelButtonTemplate")
f.Panel1.Button:SetSize(120,22)
f.Panel1.Button:SetText("Ok")
f.Panel1.Button:SetPoint("TOP",f.Panel1.EditBox,"BOTTOM",0,-16)

-- when Ok button clicked in Panel1, hide Panel1, show Panel2, and say Hello <name>
f.Panel1.Button:SetScript("OnClick",function(self)
    f.Panel1:Hide()
    f.Panel2:Show()
    f.Panel2.Results:SetText("Hello "..f.Panel1.EditBox:GetText())
end)

-- if [enter] key hit in editbox, click the Ok button
f.Panel1.EditBox:SetScript("OnEnterPressed",function(self)
    f.Panel1.Button:Click()
end)

f.Panel2 = CreateFrame("Frame",nil,f,"InsetFrameTemplate")
f.Panel2:SetPoint("TOPLEFT",4,-24)
f.Panel2:SetPoint("BOTTOMRIGHT",-7,6)
f.Panel2.Bg:SetDrawLayer("BACKGROUND",1)
f.Panel2:Hide()

f.Panel2.Results = f.Panel2:CreateFontString(nil,"ARTWORK","GameFontNormal")
f.Panel2.Results:SetPoint("TOP",0,-48)

f.Panel2.Button = CreateFrame("Button",nil,f.Panel2,"UIPanelButtonTemplate")
f.Panel2.Button:SetSize(120,22)
f.Panel2.Button:SetText("Ok")
f.Panel2.Button:SetPoint("TOP",f.Panel2.Results,"BOTTOM",0,-16)

-- when Ok button clicked in Panel2, hide Panel2, show Panel1, and reset editbox
f.Panel2.Button:SetScript("OnClick",function(self)
    f.Panel2:Hide()
    f.Panel1:Show()
    f.Panel1.EditBox:SetText("")
    f.Panel1.EditBox:SetFocus(true)
end)

I use f as a local reference as reflexive habit (less typing!), but f is also equal to MyMainFrame, so if you can use MyMainFrame also. If you want to update the panel 1 instructions, you can do

MyMainFrame.Panel1.Instructions:SetText("Not that name!")

Everything is reference-able in the heiarchy:

And because everything has a relationship this way, you can go up and down the tree too. The OnClick for Panel1’s button could be:

f.Panel1.Button:SetScript("OnClick",function(self)
    self:GetParent():Hide()
    self:GetParent():GetParent().Panel2:Show()
    self:GetParent():GetParent().Panel2.Results:SetText("Hello "..self:GetParent().EditBox:GetText())
end)

If you wan to dynamically create a list/grid of frames, create a table and put your frames in it:

f.Panel3.GridButtons = {}
for i=1,20 do
    local button = CreateFrame("Button",nil,f.Panel3,"UIPanelButtonTemplate")
    tinsert(f.Panel3.GridButtons,button)
end

And also remember that once instantiated, these are references to the objects. For your themes, if you want to store buttons in an easy-to-iterate list:

MyMainFrame.Themes = {
    Buttons = {},
    FontStrings = {},
    EditBoxes = {}
}

Then somewhere you can just insert them:

tinsert(MyMainFrame.Themes.Buttons,MyMainFrame.Panel1.Button)
tinsert(MyMainFrame.Themes.Buttons,MyMainFrame.Panel2.Button)
tinsert(MyMainFrame.Themes.FontStrings,MyMainFrame.Panel1.Instructions)
tinsert(MyMainFrame.Themes.FontStrings,MyMainFrame.Panel2.Results)
tinsert(MyMainFrame.Themes.EditBoxes,MyMainFrame.Panel1.EditBox)

This is not saying to avoid names. It’s very courteous to name stuff, especially anything that will be on screen that you or a user may want to /fstack.

But I think trying to give everything a global name so you can reference by that name will give you a one-dimensional abstract of your UI and make it difficult to maintain, especially as you start getting elements name like MyMainFramePanel1EditBoxSearchIconOverlay.

2 Likes

This. Definitely this.

No derailing and no disagreement. Most of the last part of the thread has been about getting away from the concept that one variable or another IS the widget to how they’re pointers TO the widget so you can have more than one, all able to “access” the same widget, and stored wherever you like which is explained nicely in Gello’s guide.

Thanks Gello. We could do with some more guides like that on WoWpedia.

For the record I’d been creating my dynamic widgets using loops and leaving them anonymous. Which worked fine when creating them but became problematic when I tried to do something with them outside the loop.

Even using:

self:GetParent():GetParent().Panel2.Results:SetText("Hello "..self:GetParent().EditBox:GetText()

I needed to work out the using dynamic variables as widget names issue anyway because I wanted to do

self:GetParent():GetParent().["Panel"..i-1].Results:SetText("Hello "..self:GetParent().EditBox:GetText()

A lot of what I’ve learned from this thread comes down to be smarter about your naming conventions and arrays are your friend. So no more five variables named “ValueOne”, “ValueTwo” etc or even worse “BlueValue” and “RedValue” and “ValueGreen”. Because even if I don’t think I’ll be looping through them when I first create them… there’s a chance I might have to later.

That and [“Combining”…"this] works but not “Combining”…“that” when referencing a widget.

A lot of what I learned in 30+ years of mainframe systems development was the exact same thing.

One thing I learned from my early training was this:

  • Work out a naming scheme early in your design process
  • Stick with it
  • If you missed something and find that it has become unworkable or unwieldy, don’t patch it - redo it (top to bottom)

That last one seems excessive, but I’ve not one time in my career had that be a waste of time because if I’ve misunderstood the project enough that the naming scheme wouldn’t work, I’ve also misunderstood the project itself enough that a review of its structure will produce significant improvements.

This particularly resonates with me because I was working on logic inside some nested loops and after multiple attempts at fixing it I think I was actually making things worse. So I literally scrapped what I was doing and redid it in LibreOffice Calc using Basic. Took about an hour then I just had to convert that code back to Lua.

If I was stuck on a project, my go-to thing was to copy what I had to a library somewhere and free-code recode it from memory.

Inevitably, I’d find the problem.

Normally, I’d go back to the existing project and make the changes there, but occasionally I’d stumble onto a solution to a problem that wouldn’t work that way (too many structural changes).

I never, ever counted it as lost time.

The resulting work was inevitably more elegant, more maintainable, more readable, and more efficient.

The brain is a wonderful tool, but we often misuse it. It solves complex problems best (at least mine does) when you let it go off and do it without pushing.

I think that’s because you’ve done the work and made mistakes on the original. So when you start from scratch somewhere else you don’t make those same mistakes and more importantly have little leftover pieces of them littering up your code. All the “do I still even need that line” or “Am I even using this variable” or “Why was I converting that from string to int back to string again…” type stuff.

I like Calc (or excel if you do the MS thing) because a spreadsheet is great for visualising loops. Literally write out values back to Cell(i,j) and you’re seeing exactly what’s happening in the code. And with some simple conditional formatting rules you can have the spreadsheet highlight weird values like negatives or nulls (nil) or string when it should be int.

Anything that helps you visualize and internalize the data, structure, and flow of your project is good.

For me, sometimes it was just starting at the code (well, reading it) and then taking a day or two to just walk around and let my mind digest it.

Sometimes it was literally taking broken program and retyping it, word for word, line for line. That would quite often trigger things in my mind that just reading it wouldn’t.

If spreadsheets and data flow modeling work for you, use them.

I tell people all the time that the magic in this is never the code.

It’s the ability to clearly see what you need to do.

Actually doing it is relatively trivial.