1. #1
    High Overlord
    10+ Year Old Account
    Join Date
    Jan 2011
    Location
    The Netherlands
    Posts
    187

    Making a table-like layout in the Interface Options Addon panel



    I'm working on an addon that saves challenge mode timers, and the above image shows what I would like to do. I've made a subcategory in the Blizzard Interface Options Addon panel for each of the WoD dungeons, and now I want to fill in the space in the following way:

    1) N equally wide columns, N being the number of objectives in the challenge mode.
    2) 1 column of absolute width, showing time stamps.
    3) 1 column with an X-button, which can be clicked to erase the run data from the memory.
    4) A row for each run in the memory, showing timers, a time stamp and an erase-button.

    I'm a decent programmer (C++, Java, Python), but my experience with widgets/XMLobjects is very limited. The data collection for all the runs (through SavedVariables) is already working. Can someone give me some pointers towards a good implementation?

  2. #2
    Implementing options gui's really isn't my strong suit but I can manage usually what I want to do so someone else may chime in with a more informative reply. I assume you're going to want to scroll through times so I'd say the use of FauxScrollFrame is what you're going to want to use here.
    AddOns: Tim @ WoWInterface
    Characters: Mage, Priest, Devoker, Pally
    Battle Tag: Mysterio#11164
    Current PC Setup: PCPartPicker List

  3. #3
    You can use the Ace3-GUI library to save yourself some hassle. Here is the API. As Cirax said, the widget type you'll want to use is ScrollFrame, or FauxScrollFrame if you write your own implementation.

  4. #4
    High Overlord
    10+ Year Old Account
    Join Date
    Jan 2011
    Location
    The Netherlands
    Posts
    187
    Thanks for your replies. I decided to go with making a scrollframe (following http://wowprogramming.com/snippets/S...croll_Frame_35), which worked. Now I'm running into the problem of constantly recreating my frames:



    The relevant code:

    Code:
    function Panel_Refresh(self, name)
    	local width = self:GetWidth();
    	local height = self:GetHeight();
    	
    	-- time stamp header
    	local timeStampHeader = CreateFrame("FRAME", nil, self);
    	timeStampHeader:SetPoint("TOPLEFT", self, 10, -10);
    	timeStampHeader:SetWidth(100);
    	timeStampHeader:SetHeight(20);
    		
    	timeStampHeader.text = timeStampHeader:CreateFontString();
    	timeStampHeader.text:SetPoint("CENTER");
    	timeStampHeader.text:SetSize(100, 20);
    	timeStampHeader.text:SetFont("Fonts\\FRIZQT__.ttf", 12, "OUTLINE");
    	timeStampHeader.text:SetText("Time/Date");
    	
    	-- boss name headers
    	local headers = bosses[name];
    	local columnWidth = (width - 120) / #headers;
    	
    	for i, v in ipairs(headers) do
    		local bossHeader = CreateFrame("FRAME", nil, self);
    		bossHeader:SetPoint("TOPLEFT", self, 110 + columnWidth * (i - 1), -10);
    		bossHeader:SetWidth(columnWidth);
    		bossHeader:SetHeight(20);
    			
    		bossHeader.text = bossHeader:CreateFontString();
    		bossHeader.text:SetPoint("CENTER");
    		bossHeader.text:SetSize(columnWidth, 20);
    		bossHeader.text:SetFont("Fonts\\FRIZQT__.ttf", 12, "OUTLINE");
    		bossHeader.text:SetText(v);
    	end
    	
    	-- parent scroll frame 
    	local scrollParent = CreateFrame("Frame", nil, self);
    	scrollParent:SetSize(width - 24, height - 34);
    	scrollParent:SetPoint("TOPLEFT", self, 0, -30);
    	
    	-- scroll frame 
    	local scrollFrame = CreateFrame("ScrollFrame", nil, scrollParent);
    	scrollFrame:SetPoint("TOPLEFT");
    	scrollFrame:SetPoint("BOTTOMRIGHT");
    	scrollParent.scrollframe = scrollFrame; 
    	
    	-- scroll bar 
    	local scrollBar = CreateFrame("Slider", nil, scrollFrame, "UIPanelScrollBarTemplate"); 
    	scrollBar:SetPoint("TOPLEFT", scrollParent, "TOPRIGHT", 4, -16);
    	scrollBar:SetPoint("BOTTOMLEFT", scrollParent, "BOTTOMRIGHT", 4, 16); 
    	scrollBar:SetMinMaxValues(1, 200);
    	scrollBar:SetValueStep(1);
    	scrollBar.scrollStep = 1;
    	scrollBar:SetValue(0);
    	scrollBar:SetWidth(16);
    	scrollBar:SetScript("OnValueChanged", function (self, value) self:GetParent():SetVerticalScroll(value) end);
    	local scrollbg = scrollBar:CreateTexture(nil, "BACKGROUND");
    	scrollbg:SetAllPoints(scrollBar);
    	scrollbg:SetTexture(0, 0, 0, 0.4);
    	scrollParent.scrollbar = scrollBar;
    	
    	-- content frame
    	local contentFrame = CreateFrame("Frame", nil, scrollFrame);
    	contentFrame:SetSize(width - 36, height);
    	scrollFrame.content = contentFrame;
    	 
    	scrollFrame:SetScrollChild(contentFrame);
    		
    	-- runs
    	for k, run in ipairs(RUNS) do
    		if(name == run[3] and run[2] ~= {}) then
    			local timeStampFrame = CreateFrame("FRAME", nil, contentFrame);
    			timeStampFrame:SetPoint("TOPLEFT", contentFrame, 10, -(15 * (k - 1)));
    			timeStampFrame:SetWidth(100);
    			timeStampFrame:SetHeight(15);
    				
    			timeStampFrame.text = timeStampFrame:CreateFontString();
    			timeStampFrame.text:SetPoint("CENTER");
    			timeStampFrame.text:SetSize(columnWidth, 15);
    			timeStampFrame.text:SetFont("Fonts\\FRIZQT__.ttf", 10, "OUTLINE");
    			timeStampFrame.text:SetText(run[1]);
    			
    			for i, s in pairs(run[2]) do
    				local timer = Seconds_To_Timer(s);
    				local timerFrame = CreateFrame("FRAME", nil, contentFrame);
    				timerFrame:SetPoint("TOPLEFT", contentFrame, 110 + columnWidth * (i - 1), -(15 * (k - 1)));
    				timerFrame:SetWidth(columnWidth);
    				timerFrame:SetHeight(15);
    					
    				timerFrame.text = timerFrame:CreateFontString();
    				timerFrame.text:SetPoint("CENTER");
    				timerFrame.text:SetSize(columnWidth, 15);
    				timerFrame.text:SetFont("Fonts\\FRIZQT__.ttf", 10, "OUTLINE");
    				timerFrame.text:SetText(timer);
    			end
    		end
    	end
    end
    Which is called here:

    Code:
    self.refresh = function (self) Panel_Refresh(self, name); end;
    for each of the 8 dungeon categories. I've read that deleting all the frames doesn't work, but how do I re-use them efficiently? My fear is that I've made some major design errors in my code, and I might need to do a complete overhaul.

  5. #5
    Store references to the widgets you create in a table and reuse them when you display data.

  6. #6
    High Overlord
    10+ Year Old Account
    Join Date
    Jan 2011
    Location
    The Netherlands
    Posts
    187
    Quote Originally Posted by Woogs View Post
    Store references to the widgets you create in a table and reuse them when you display data.
    Thanks, I've managed to implement this as well. Now I'm trying to add a multiline editbox, but I'm running into another Lua error - which has to do with the template I'm using - that occurs when I create a new line that goes outside of the box. Do you know what is causing this error? I cannot seem to be able to set a max number of lines. Code for the editbox included:

    Code:
    function Click_Run(self, button)
    	if button == "LeftButton" then
    		if lastSelected then
    			lastSelected.texture:Hide()
    		end
    		
    		self.texture:Show()
    		lastSelected = self
    	
    		local parent = self:GetParent():GetParent():GetParent():GetParent():GetParent()
    		
    		if currentNotes then
    			currentNotes:Hide()
    		end
    		
    		if not self.notes then
    			self.notes = CreateFrame("ScrollFrame", nil, parent, "InputScrollFrameTemplate")
    			self.notes:SetPoint("BOTTOMLEFT", 10, 10)
    			self.notes.CharCount:Hide()
    			
    			self.notes.EditBox:SetFontObject(GameFontWhite)
    			self.notes.EditBox:SetMaxLetters(256)
    		end
    		
    		if RUNS[self.index][5] then
    			self.notes.EditBox:SetText(RUNS[self.index][5])
    		end
    		self.notes:SetSize(parent:GetWidth() - 20, 80)
    		
    		self.notes.EditBox:SetWidth(parent:GetWidth() - 20)
    		self.notes.EditBox.index = self.index
    		self.notes.EditBox:SetScript("OnTextChanged", function(self, userInput) RUNS[self.index][5] = self:GetText() end)
    		self.notes:Show()
    		currentNotes = self.notes
    	end
    end
    And the Lua error as presented to me in-game:

    Code:
    Message: Interface\SharedXML\SharedUIPanelTemplates.lua:337: attempt to concatenate a nil value
    Time: 12/30/14 14:02:18
    Count: 2
    Stack: Interface\SharedXML\SharedUIPanelTemplates.lua:337: in function `ScrollFrame_OnScrollRangeChanged'
    [string "*:OnScrollRangeChanged"]:1: in function <[string "*:OnScrollRangeChanged"]:1>
    
    Locals: self = <unnamed> {
     BottomRightTex = <unnamed> {
     }
     BottomLeftTex = <unnamed> {
     }
     TopLeftTex = <unnamed> {
     }
     BottomTex = <unnamed> {
     }
     scrollBarHideable = 1
     EditBox = <unnamed> {
     }
     TopRightTex = <unnamed> {
     }
     FocusButton = <unnamed> {
     }
     CharCount = <unnamed> {
     }
     0 = <userdata>
     RightTex = <unnamed> {
     }
     ScrollBar = InterfaceOptionsFramePanelContainerScrollBar {
     }
     MiddleTex = <unnamed> {
     }
     LeftTex = <unnamed> {
     }
     TopTex = <unnamed> {
     }
    }
    xrange = 0
    yrange = 16.000017166138
    scrollbar = InterfaceOptionsFramePanelContainerScrollBar {
     0 = <userdata>
     ScrollUpButton = InterfaceOptionsFramePanelContainerScrollBarScrollUpButton {
     }
     ScrollDownButton = InterfaceOptionsFramePanelContainerScrollBarScrollDownButton {
     }
    }
    value = 0
    (*temporary) = <table> {
     ContainerFrame5Item7 = ContainerFrame5Item7 {
     }
     MultiCastActionButton6Cooldown = MultiCastActionButton6Cooldown {
     }
     MerchantItem9ItemButtonStock = MerchantItem9ItemButtonStock {
     }
     GetTrainerServiceTypeFilter = <function> defined =[C]:-1
     UNIT_NAMES_COMBATLOG_TOOLTIP = "Color unit names."
     UNIT_NAMEPLATES_TYPE_TOOLTIP_3 = "This method avoids overlapping nameplates by spreading them out horizontally and vertically."
     SetTrainerServiceTypeFilter = <function> defined =[C]:-1
     EventTraceFrameButton7HideButton = EventTraceFrameButton7HideButton {
     }
     SPELL_FAILED_CUSTOM_ERROR_71 = "This partygoer wants to dance with you."
     RecruitAFriendFrame = RecruitAFriendFrame {
     }
     CompactUnitFrameProfilesGeneralOptionsFrameHealthTextDropdownButtonNormalTexture = CompactUnitFrameProfilesGeneralOptionsFrameHealthTextDropdownButtonNormalTexture {
     }
     TutorialFrameLeft19 = TutorialFrameLeft19 {
     }
     MultiCastActionButton2Cooldown = MultiCastActionButton2Cooldown {
     }
     ERR_TRADE_EQUIPPED_BAG = "You can't trade equipped bags."
     PVP_RANK_6_1 = "Corporal"
     BOOKTYPE_PROFESSION = "professions"
     AudioOptionsVoicePanelOutputDeviceDropDownButtonHighlightTexture = AudioOptionsVoicePanelOutputDeviceDropDownButtonHighlightTexture {
     }
     InterfaceOptionsDisplayPanelShowAggroPercentageText = InterfaceOptionsDisplayPanelShowAggroPercentageText {
     }
     VideoOptionsFrameCategoryFrameButton17ToggleHighlightTexture = VideoOptionsFrameCategoryFrameButton17ToggleHighlightTexture {
     }
     MerchantItem3AltCurrencyFrameItem1Text = MerchantItem3AltCurrencyFrameItem1Text {
     }
     OPTION_TOOLTIP_ACTION_BUTTON_USE_KEY_DOWN = "Action button keybinds will respond on key down, rather than on key up."
     BINDING_NAME_NAMEPLATES = "Show Enemy Name Plates"
     INSTANCE_UNAVAILABLE_OTHER_TEMPORARILY_DISABLED = "%s cannot enter. This instance is temporarily disabled."
     IsReferAFriendLinked = <function> defined =[C]:-1
     MAIL_LETTER_TOOLTIP = "Click to make a permanent
    copy of this letter."
     AudioOptionsVoicePanelMicrophoneVolumeThumb = AudioOptionsVoicePanelMicrophoneVolumeThumb {
     }
     ItemTextFrameInsetInsetTopRightCorner = ItemTextFrameInsetInsetTopRightCorner {
     }
     MANA = "Mana"
     CHAT_CONFIG_OTHER_COMBAT = <table> {
     }
     CONSOLIDATED_BUFFS_PER_ROW = 4
     TutorialFrameRight19 = TutorialFrameRight19 {
     }
     MoneyFrame_OnEvent = <function> defined @Interface\FrameXML\MoneyFrame.lua:225
     BN_UNABLE_TO_RESOLVE_NAME = "Unable to whisper '%s'. Battle.net may be unavailable."
     CinematicFrameRaidBossEmoteFrame = CinematicFrameRaidBossEmoteFrame {
     }
     InterfaceOptionsCombatTextPanelFCTDropDown_OnClick = <function> defined @Interface\FrameXML\InterfaceOptionsPanels.lua:1661
     CompactRaidFrameManagerDisplayFrameHiddenModeToggleTopRight = CompactRaidFrameManagerDisplayFrameHiddenModeToggleTopRight {
     }
     LFGTeleport = <function> defined =[C]:-1
     GetMonitorAspectRatio = <function> defined =[C]:-1
     ToggleEncounterJournal

  7. #7
    Have you tried changing nil to a string here;
    self.notes = CreateFrame("ScrollFrame", nil, parent, "InputScrollFrameTemplate")?

    I have no idea what the error could be because I suck at troubleshooting unless I'm allowed to play with it myself since I go through different stuff fast, but that just stuck out to me because I'm not sure that argument can be nil.

    Actually it can be nil, don't mind me I'm stupid.

    At some point you're passing a nil value though, and the function isn't equipped to handle it it seems.
    Last edited by Neteyes; 2014-12-30 at 01:24 PM.

  8. #8
    Nateyes is correct. Most of the time you don't have to give global names to your widgets, but you do in this case. https://github.com/Resike/BlizzardIn...lTemplates.lua

    The line that is giving the error is:
    Code:
    _G[self:GetName().."ScrollBar"]:Show();

  9. #9
    High Overlord
    10+ Year Old Account
    Join Date
    Jan 2011
    Location
    The Netherlands
    Posts
    187
    Quote Originally Posted by Woogs View Post
    Nateyes is correct. Most of the time you don't have to give global names to your widgets, but you do in this case. https://github.com/Resike/BlizzardIn...lTemplates.lua

    The line that is giving the error is:
    Code:
    _G[self:GetName().."ScrollBar"]:Show();
    Thanks to you both, this did indeed fix it.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •