WoW:Making a scrollable list using FauxScrollFrameTemplate
This HOWTO describes how to create a list view using the FauxScrollFrameTemplate FrameXML class. An alternative is to simply create a Slider object of your own, but there is no HOWTO on that at the moment. Using CreateFrame() to create the list items dynamically may also be a good idea.
How to do it[edit]
Before starting out, it helps to visualize that the Scrollbar is nothing more than a vertical slider. We set a min, max, steps to the slider and then react to changes in its offset. The slider doesn't keep tabs of any of our data beyond this. It's up to us to handle the actual movement of data as the user moves the thumb.
You can look at ItemTextFrame.xml/lua, QuestFrame.xml/lua for other examples of how to make ScrollFrames. This will focus on the FauxScrollFrameTemplate route, for a series of rows we define in detail.
FauxScrollFrameTemplate is inherited from UIPanelScrollFrameTemplate, both in UIPanelTemplates.xml. FriendsFrame.xml is a good reference for how to flesh out a FauxScrollFrame with things such as highlights and "gutter" textures. But for simplicity's sake ours will be very basic: An up+down arrow at the ends of the scrollbar and a thumb to move the data.
First, we need data to scroll through:
function MyMod_OnLoad() MyModData = {}; for i=1,50 do MyModData[i] = "Test "..math.random(100); end end
Now we have an array of 50 entries with "Test (some random number up to 100)"
Next, we need a scrollbar on the window:
<ScrollFrame name="MyModScrollBar" inherits="FauxScrollFrameTemplate"> <Anchors> <Anchor point="TOPLEFT"/> <Anchor point="BOTTOMRIGHT"/> </Anchors> <Scripts> <OnVerticalScroll> FauxScrollFrame_OnVerticalScroll(self, offset, 16, MyModScrollBar_Update); </OnVerticalScroll> <OnShow> MyModScrollBar_Update() </OnShow> </Scripts> </ScrollFrame>
The components of the ScrollFrame that we care about now:
- Anchors
- Note that the above example uses two anchors to stretch the ScrollFrame across the entire parent. You can use <Size> tags with one <Anchor> if you prefer. Note: The dimensions of the scrollframe itself are the area you intend to display information. The bar is not within this area but anchored to the right of this area. The OnMouseWheel event will only work over the area you define for the ScrollFrame. And of course it's important for properly positioning the scrollbar. If the ScrollFrame's parent has a UI-Tooltip-Border backdrop, you can use the following anchors to position the bar within the frame:
<Anchors> <Anchor point="TOPLEFT"> <Offset> <AbsDimension x="0" y="-8"/> </Offset> </Anchor> <Anchor point="BOTTOMRIGHT"> <Offset> <AbsDimension x="-30" y="8"/> </Offset> </Anchor> </Anchors>
- OnVerticalScroll
- This function is called whenever the scroll thumb is moved while shown. The 16 is the height of each entry in pixels. The second parameter is to a function you supply that updates the information. The function is required for the ScrollFrame to work.
- OnShow
- With 1.9, it appears that FauxScrollFrame_OnVerticalScroll doesn't call the supplied function during initialization. So we need to do the first update ourselves. An OnShow is a convenient spot to do so.
Note: The OnShow can happen BEFORE your mods' OnLoad. In 99% of cases this is not important but for testing purposes you may need to delay the OnShow to the mod's "primary" OnLoad.
The all-important MyModScrollBar_Update will look something like this in its most essential form:
function MyModScrollBar_Update() FauxScrollFrame_Update(MyModScrollBar,50,5,16); -- 50 is max entries, 5 is number of lines, 16 is pixel height of each line end
In UIPanelTemplates.lua, the function is documented as:
-- Function to handle the update of manually calculated scrollframes. Used mostly for listings with an indeterminate number of items function FauxScrollFrame_Update(frame, numItems, numToDisplay, valueStep, button, smallWidth, bigWidth, highlightFrame, smallHighlightWidth, bigHighlightWidth, alwaysShowScrollBar )
If the function that's the second parameter of FauxScrollFrame_OnVerticalScroll doesn't contain a FauxScrollFrame_Update, we will get an error. Once we have an update function within <OnVerticalScroll>, and a FauxScrollFrame_Update call within that function, the scrollbar is functional. We can test it by getting the offset and printing it:
function MyModScrollBar_Update() FauxScrollFrame_Update(MyModScrollBar,50,5,16); -- 50 is max entries, 5 is number of lines, 16 is pixel height of each line DEFAULT_CHAT_FRAME:AddMessage("We're at "..FauxScrollFrame_GetOffset(MyModScrollBar)); end
Try that and scroll the bar around. You'll see that _GetOffset will return from 0 to 45, which happens to be 50-5.
We now have an indexed array of data and a scrollbar. Now we need to display the data based on the scrollbar's position. First we need to construct the xml elements of the display.
Start simple and make one button template to play around with:
<Button name = "MyModEntryTemplate" virtual="true"> <Size> <AbsDimension x="150" y="16" /> </Size> <NormalFont style="GameFontHighlightLeft"/> </Button>
This is a virtual button, 150 pixels wide and 16 pixels tall, that can be used to display text.
Now use this template to draw our series of buttons:
<Button name="MyModEntry1" inherits="MyModEntryTemplate"> <Anchors> <Anchor point="TOPLEFT" relativeTo="MyModScrollBar" relativePoint="TOPLEFT"> <Offset> <AbsDimension x="8" y="0"/> </Offset> </Anchor> </Anchors> </Button> <Button name="MyModEntry2" inherits="MyModEntryTemplate"> <Anchors> <Anchor point="TOPLEFT" relativeTo="MyModEntry1" relativePoint="BOTTOMLEFT"/> </Anchors> </Button> <Button name="MyModEntry3" inherits="MyModEntryTemplate"> <Anchors> <Anchor point="TOPLEFT" relativeTo="MyModEntry2" relativePoint="BOTTOMLEFT"/> </Anchors> </Button> <Button name="MyModEntry4" inherits="MyModEntryTemplate"> <Anchors> <Anchor point="TOPLEFT" relativeTo="MyModEntry3" relativePoint="BOTTOMLEFT"/> </Anchors> </Button> <Button name="MyModEntry5" inherits="MyModEntryTemplate"> <Anchors> <Anchor point="TOPLEFT" relativeTo="MyModEntry4" relativePoint="BOTTOMLEFT"/> </Anchors> </Button>
The above draws the first entry with top-left aligned to top-left of the scrollbar's scrollable area, 8 pixels to the right, then it draws each one below aligned with top-left of next one aligned to bottom-left of previous.
Now we go back to the update function and add the code to display our data depending on the position of the bar:
We have 5 lines on the screen at all times: MyModEntry1, MyModEntry2, etc. Each entry has a text inside named MyModEntry1_Text, MyModEntry2_Text, etc.
function MyModScrollBar_Update() local line; -- 1 through 5 of our window to scroll local lineplusoffset; -- an index into our data calculated from the scroll offset FauxScrollFrame_Update(MyModScrollBar,50,5,16); for line=1,5 do lineplusoffset = line + FauxScrollFrame_GetOffset(MyModScrollBar); if lineplusoffset < 50 then getglobal("MyModEntry"..line):SetText(MyModData[lineplusoffset]); getglobal("MyModEntry"..line):Show(); else getglobal("MyModEntry"..line):Hide(); end end end
Now we have a scrollbar that scrolls through the data and redisplays it when we move. Now you can customize it to your mod's needs. First step would be to make the values fit your mod, especially the size and shape of the data.
Remember to check out FriendsFrame.xml/.lua to see how to select entries by locking/unlocking its highlight, how to draw a gutter, create multiple columns, etc.
Completed example[edit]
MyModScrollBar.toc[edit]
## Interface: 30000 ## Title: MyMod ## Notes: Example of using FauxScrollFrameTemplate MyModScrollBar.xml
MyModScrollBar.lua[edit]
MyModData = {} function MyMod_OnLoad() for i=1,50 do MyModData[i] = "Test "..math.random(100) end MyModScrollBar:Show() end function MyModScrollBar_Update() local line; -- 1 through 5 of our window to scroll local lineplusoffset; -- an index into our data calculated from the scroll offset FauxScrollFrame_Update(MyModScrollBar,50,5,16); for line=1,5 do lineplusoffset = line + FauxScrollFrame_GetOffset(MyModScrollBar); if lineplusoffset <= 50 then getglobal("MyModEntry"..line):SetText(MyModData[lineplusoffset]); getglobal("MyModEntry"..line):Show(); else getglobal("MyModEntry"..line):Hide(); end end end
MyModScrollBar.xml[edit]
<Ui> <Script file="MyModScrollBar.lua"/> <Button name = "MyModEntryTemplate" virtual="true"> <Size> <AbsDimension x="150" y="16" /> </Size> <NormalFont style="GameFontHighlightLeft"/> </Button> <Frame name="MyMod" parent="UIParent" enableMouse="true" movable="true"> <Size> <AbsDimension x="196" y="96"/> </Size> <Anchors> <Anchor point="CENTER"/> </Anchors> <Scripts> <OnLoad> MyMod_OnLoad() </OnLoad> </Scripts> <Backdrop bgFile="Interface\DialogFrame\UI-DialogBox-Background" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true"> <BackgroundInsets> <AbsInset left="4" right="4" top="4" bottom="4" /> </BackgroundInsets> <TileSize> <AbsValue val="16" /> </TileSize> <EdgeSize> <AbsValue val="16" /> </EdgeSize> </Backdrop> <Frames> <ScrollFrame name="MyModScrollBar" inherits="FauxScrollFrameTemplate" hidden="true"> <Anchors> <Anchor point="TOPLEFT"> <Offset> <AbsDimension x="0" y="-8"/> </Offset> </Anchor> <Anchor point="BOTTOMRIGHT"> <Offset> <AbsDimension x="-30" y="8"/> </Offset> </Anchor> </Anchors> <Scripts> <OnVerticalScroll> FauxScrollFrame_OnVerticalScroll(self, offset, 16, MyModScrollBar_Update); </OnVerticalScroll> <OnShow> MyModScrollBar_Update() </OnShow> </Scripts> </ScrollFrame> <Button name="MyModEntry1" inherits="MyModEntryTemplate"> <Anchors> <Anchor point="TOPLEFT" relativeTo="MyModScrollBar" relativePoint="TOPLEFT"> <Offset> <AbsDimension x="8" y="0"/> </Offset> </Anchor> </Anchors> </Button> <Button name="MyModEntry2" inherits="MyModEntryTemplate"> <Anchors> <Anchor point="TOPLEFT" relativeTo="MyModEntry1" relativePoint="BOTTOMLEFT"/> </Anchors> </Button> <Button name="MyModEntry3" inherits="MyModEntryTemplate"> <Anchors> <Anchor point="TOPLEFT" relativeTo="MyModEntry2" relativePoint="BOTTOMLEFT"/> </Anchors> </Button> <Button name="MyModEntry4" inherits="MyModEntryTemplate"> <Anchors> <Anchor point="TOPLEFT" relativeTo="MyModEntry3" relativePoint="BOTTOMLEFT"/> </Anchors> </Button> <Button name="MyModEntry5" inherits="MyModEntryTemplate"> <Anchors> <Anchor point="TOPLEFT" relativeTo="MyModEntry4" relativePoint="BOTTOMLEFT"/> </Anchors> </Button> </Frames> </Frame> </Ui>