WoW:Creating tabbed windows
Introduction
As your UI is growing more and more complex you will spend quite some time organizing your UI elements in a way that's user-friendly but still leaves enough capabilities to make use of the more advanced features in your AddOns. A great way to achieve this with very little effort is the use of Tabs.
You probably know these from the Character or Friendsframe windows in WoW. With Tabs you have the ability to store multiple windows inside of a single one. Tabs are a very important feature in UI design, since their introduction in early MacOS they became kind of a pseudo-standard in recent graphical applications, such as Mozilla Firefox.
Unfortunately the amount of code neccessary to generate even a simple tabbing system in WoW is quite large. However, the guys at Blizzard implemented an easy-to-use library that makes the task of handling tabs a whole lot more comfortable.
The Theory behind it
Blizzard's approach on Tabs is pretty straightforward: The buttons for switching between the different Tabs are simply just that, buttons! The OnClick handler of each button hides the current UI-elements and shows the ones that correspond to the selected Tab. Blizzard's library adds some eye candy to it, so that it's easier to identify which tab is active at the moment. Take a peek at some of the tabbed windows that are in the game and you'll see what i mean.
Getting our hands dirty
First of all: The library for Tabs is included in the UIPanelTemplates XML and LUA files. If you are unsure about how certain things work, take a look at them.
Tabs in XML
As mentioned before, from an UI's point of view, tabs are simply some buttons at the bottom of your frame. Because of that, our first task is to create a Button-template:
<Button name="myFrameTabTemplate" inherits="CharacterFrameTabButtonTemplate" virtual="true"> <Scripts> <OnClick> PanelTemplates_Tab_OnClick(myFrame); myButtonHandler(this:GetName()); </OnClick> </Scripts> </Button>
We are inheriting from the template that's used for the CharacterFrame. You can find it at the top of the CharacterFrame.XML file. By using this template we basically provide WoW with the data structure that is specified by the Tab library. If you take a look at the source of the template, you'll notice that this is quite complex.
Don't forget to overload the OnClick-Eventhandler! Leave the remaining handlers as they are, you won't need them anyway. We'll discuss the functionality of myButtonHandler() later on.
Now, let's build some buttons:
<Button name="myTabbedWindowFrameTab1" inherits="myFrameTabTemplate" id="1" text="Tab #1"> [...] <Button name="myTabbedWindowFrameTab2" inherits="myFrameTabTemplate" id="2" text="Tab #2"> [...]
Place this line inside of your frame declaration and anchor the buttons at the bottom of your frame. Take care of the naming: The buttons must have the exact same names apart from the number-suffix. This is very important because most of the Tab management WoW does, is done by 'guessing' names based on specific naming conventions. Notice the id Attribute in the declaration. This specifies the number of your tab, so for the third tab, the id would have to be 3 and so on. Don't forget to adjust the ids for every new button! You won't need any more eventhandlers in there, it's all in the template.
So far, so good. Now, let's step over to LUA.
Tabs in LUA
Now things are getting interesting. The first thing to do is register our tabs in Blizzards tabbing-system:
PanelTemplates_SetNumTabs(myFrame, n); dg_calFrame.selectedTab=1; PanelTemplates_UpdateTabs(myFrame);
These lines should be executed before your frame is displayed for the first time. myFrame is the name of your frame, n is the number of tabs you specified.
Now, what have we just done? Let's take a look at the UIPanelTemplates.LUA:
- SetNumTabs() is basically adding an attribute to our frame called numTabs containing the value of n
- In the next line we specify another attribute by ourselve, selectedTab
- The UpdateTabs is a bit more sophisticated. Basically, it takes care of setting up the buttons like we've already seen in other tabbed windows like the characterframe. This function already uses the prior defined numTabs and selectedTab attributes.
Take your time to understand what the UpdateTabs()-function does. This is basically the whole trick behind it all. You'll also understand now, why it was so important to consider certain naming conventions back in your XML file.
Now, let's take a look at the Tab_OnClick() function that we used when creating our button template. What it basically does is setting the selectedTab and then calling UpdateTabs()! You know what that means, don't you? Go ahead and fire up WoW right now (you should outcomment the myButtonHandler() call in the template to avoid errors). You will notice that your tabs are already working just fine! Beside one little thing: They don't do anything yet!
So, one last thing to do: The button handler!
You'll probably already know what to do: Extract the number of the selected tab from the button's name which was passed as a parameter, then show the UI elements of the new tab and hide all of the other UI elements. Shouldn't be any problem now, since all of the hard work is already done by the library. So rather than boring you with the details of the implementation, i'll close with some tips on efficient techniques for writing button handlers:
- Wrap up your tabs inside of seperate Frame-tags. Thus you'll only have to hide that very frame once, instead of iterating through every single UI element and hiding it by hand.
- Use a string table that contains the names of these frames. Thus you can iterate through the table with a single for loop, instead of writing hundreds of lines. Your code becomes a whole lot more clearly, making maintenance so much easier
- Notice that you can also change the background textures when switching tabs. This is done a lot in WoW and is extremely useful when you have very different UIs in the various tabs
Conclusion
As you have seen in the above sections, using tabs ain't that hard at all. As long as you're paying attention to certain naming conventions, making use of Blizzard's library is both powerful and easy to use. Since the OnClick-buttonhandler is specified completely independent from the rest of the library, you may also do some very weird and unexpected stuff with tabs if you wish to.
I hope you enjoyed this tutorial and did not despair over my dessolute style of writing. I'd love to read some feedback in the discussions section and hope that many of you will benefit from the great capabilities that Tabs provide.