Wowpedia

We have moved to Warcraft Wiki. Click here for information and the new URL.

READ MORE

Wowpedia
Register
(Changed $parentTab1 relative y from -8 to -12 so border graphics line up)
(26 intermediate revisions by 12 users not shown)
Line 1: Line 1:
 
<center>'''Advanced Tutorial: Tabbed Windows in WoW''' ''-Contributed by DerGhulbus-''</center>
 
<center>'''Advanced Tutorial: Tabbed Windows in WoW''' ''-Contributed by DerGhulbus-''</center>
  +
[[image:tabs.png|right|Put it on my Tab]]
 
 
= Introduction =
 
= 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.
 
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.
 
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.
+
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 [[Mac|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.
+
Unfortunately the amount of code necessary 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 =
 
= The Theory behind it =
Line 13: Line 13:
   
 
= Getting our hands dirty =
 
= 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.
+
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. For an excellent example of tabbed interface implementation, check out the source code for the '''CT_ExpenseHistory''' mod [http://www.ctmod.net]
   
 
== Tabs in XML ==
 
== Tabs in XML ==
  +
Let's start off with the frame that will serve as the outermost container of our tabbed interface. Inside this frame, you will embed child frames that will each define a single "page" of the interface, and you will write button click handlers that show a single page at a time and hide the rest.
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:
 
  +
  +
This might look like a lot of code, and it is. Nature of the beast with the WoW XML UI.
  +
  +
<Frame
  +
name="myTabContainerFrame"
  +
toplevel="true"
  +
frameStrata="DIALOG"
  +
movable="true"
  +
enableMouse="true"
  +
hidden="false"
  +
parent="UIParent">
  +
<!-- B A S I C A T T R I B S -->
  +
<Size>
  +
<AbsDimension x="480" y="325"/>
  +
</Size>
  +
<Anchors>
  +
<Anchor point="CENTER">
  +
<Offset><AbsDimension x="-200" y="200"/></Offset>
  +
</Anchor>
  +
</Anchors>
  +
<Backdrop
  +
bgFile="Interface\DialogFrame\UI-DialogBox-Background"
  +
edgeFile="Interface\DialogFrame\UI-DialogBox-Border"
  +
tile="true">
  +
<BackgroundInsets>
  +
<AbsInset left="11" right="12" top="12" bottom="11"/>
  +
</BackgroundInsets>
  +
<TileSize>
  +
<AbsValue val="32"/>
  +
</TileSize>
  +
<EdgeSize>
  +
<AbsValue val="32"/>
  +
</EdgeSize>
  +
</Backdrop>
  +
<!-- T I T L E -->
  +
<Layers>
  +
<Layer level="ARTWORK">
  +
<Texture name="myFrameHeader" file="Interface\DialogFrame\UI-DialogBox-Header">
  +
<Size>
  +
<AbsDimension x="356" y="64"/>
  +
</Size>
  +
<Anchors>
  +
<Anchor point="TOP">
  +
<Offset>
  +
<AbsDimension x="0" y="12"/>
  +
</Offset>
  +
</Anchor>
  +
</Anchors>
  +
</Texture>
  +
<FontString inherits="GameFontNormal" text="My Frame">
  +
<Anchors>
  +
<Anchor point="TOP" relativeTo="myFrameHeader">
  +
<Offset>
  +
<AbsDimension x="0" y="-14"/>
  +
</Offset>
  +
</Anchor>
  +
</Anchors>
  +
</FontString>
  +
</Layer>
  +
</Layers>
  +
<Frames>
  +
<Frame name="myTabPage1" hidden="false">
  +
<Anchors>
  +
<Anchor point="TOPLEFT"/>
  +
<Anchor point="BOTTOMRIGHT"/>
  +
</Anchors>
  +
<Layers>
  +
<Layer level="ARTWORK">
  +
<FontString inherits="GameFontNormal" text="My Frame 1">
  +
<Anchors>
  +
<Anchor point="TOPLEFT" relativeTo="$parent">
  +
<Offset>
  +
<AbsDimension x="20" y="-30"/>
  +
</Offset>
  +
</Anchor>
  +
</Anchors>
  +
</FontString>
  +
</Layer>
  +
</Layers>
  +
<Frames>
  +
<!-- UI elements go here -->
  +
</Frames>
  +
</Frame>
  +
<Frame name="myTabPage2" hidden="true">
  +
<Anchors>
  +
<Anchor point="TOPLEFT"/>
  +
<Anchor point="BOTTOMRIGHT"/>
  +
</Anchors>
  +
<Layers>
  +
<Layer level="ARTWORK">
  +
<FontString inherits="GameFontNormal" text="My Frame 2">
  +
<Anchors>
  +
<Anchor point="TOPLEFT" relativeTo="$parent">
  +
<Offset>
  +
<AbsDimension x="20" y="-30"/>
  +
</Offset>
  +
</Anchor>
  +
</Anchors>
  +
</FontString>
  +
</Layer>
  +
</Layers>
  +
<Frames>
  +
<!-- UI elements go here -->
  +
</Frames>
  +
</Frame>
  +
<!-- T A B B U T T O N S -->
 
<Button name="$parentTab1" inherits="CharacterFrameTabButtonTemplate" id="1" text="My Tab 1">
  +
<Anchors>
  +
<Anchor point="CENTER" relativePoint="BOTTOMLEFT">
  +
<Offset>
  +
<AbsDimension x="60" y="-12"/>
  +
</Offset>
  +
</Anchor>
  +
</Anchors>
  +
<Scripts>
  +
<OnClick>
  +
PanelTemplates_SetTab(myTabContainerFrame, 1);
  +
myTabPage1:Show();
  +
myTabPage2:Hide();
  +
</OnClick>
  +
</Scripts>
  +
</Button>
 
<Button name="$parentTab2" inherits="CharacterFrameTabButtonTemplate" id="2" text="My Tab 2">
  +
<Anchors>
  +
<Anchor point="LEFT" relativeTo="$parentTab1" relativePoint="RIGHT">
  +
<Offset>
  +
<AbsDimension x="-16" y="0"/>
  +
</Offset>
  +
</Anchor>
  +
</Anchors>
  +
<Scripts>
  +
<OnClick>
  +
PanelTemplates_SetTab(myTabContainerFrame, 2);
  +
myTabPage1:Hide();
  +
myTabPage2:Show();
  +
</OnClick>
  +
</Scripts>
  +
</Button>
  +
</Frames>
  +
<Scripts>
  +
<OnLoad>
  +
this.elapsed = 0;
  +
PanelTemplates_SetNumTabs(myTabContainerFrame, 2);
  +
PanelTemplates_SetTab(myTabContainerFrame, 1);
  +
</OnLoad>
  +
<OnShow>
  +
PlaySound("UChatScrollButton");
  +
PanelTemplates_SetTab(myTabContainerFrame, 1);
  +
myTabPage1:Show()
  +
myTabPage2:Hide()
  +
</OnShow>
  +
<OnHide>
  +
PlaySound("UChatScrollButton");
  +
</OnHide>
  +
</Scripts>
  +
</Frame>
  +
  +
This is a completely functional, self contained Tab Frame which you should be able to plop into any WoW XML UI file and run (as of Apr. 23, 2008). There are a lot of redundancies here. Things like the tab button properties should be templated and the logic controlling the selection of tabs is best handled by a custom function.
  +
  +
Things like the tab button properties should be templated if you're going to have lots of tabs. As mentioned before, from an UI's point of view, tabs are simply some buttons at the bottom of your frame. You should define a template like this in XML, and then implement a smart click handler in Lua:
   
 
<Button name="myFrameTabTemplate" inherits="CharacterFrameTabButtonTemplate" virtual="true">
 
<Button name="myFrameTabTemplate" inherits="CharacterFrameTabButtonTemplate" virtual="true">
 
<Scripts>
 
<Scripts>
 
<OnClick>
 
<OnClick>
PanelTemplates_Tab_OnClick(dg_calFrame);
 
 
myButtonHandler(this:GetName());
 
myButtonHandler(this:GetName());
 
</OnClick>
 
</OnClick>
Line 27: Line 186:
 
</Button>
 
</Button>
   
  +
This will reduce the amount of code you need per tab. As an example, your first tab would be only:
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.
 
   
  +
<Button name="$parentTab1" inherits="myFrameTabTemplate" id="1" text="My Tab 1">
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.
 
  +
<Anchors>
  +
<Anchor point="CENTER" relativePoint="BOTTOMLEFT">
  +
<Offset>
  +
<AbsDimension x="60" y="-8"/>
  +
</Offset>
  +
</Anchor>
  +
</Anchors>
  +
</Button>
   
   
  +
I'll leave the implementation of myButtonHandler() as an exercise for the reader.
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">
 
[...]
 
   
 
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.
Place 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 second tab, the id would have to be 2 and so on. Don't forget to adjust the ids for every new button!
 
  +
  +
Let's look at our existing buttons:
  +
  +
<Button name="$parentTab1" inherits="CharacterFrameTabButtonTemplate" id="1" text="My Tab 1">
  +
<Anchors>
  +
<Anchor point="CENTER" relativePoint="BOTTOMLEFT">
  +
<Offset>
  +
<AbsDimension x="60" y="-8"/>
  +
</Offset>
  +
</Anchor>
  +
</Anchors>
  +
<Scripts>
  +
<OnClick>
  +
PanelTemplates_SetTab(myTabContainerFrame, 1);
  +
myTabPage1:Show();
  +
myTabPage2:Hide();
  +
</OnClick>
  +
</Scripts>
  +
</Button>
  +
<Button name="$parentTab2" inherits="CharacterFrameTabButtonTemplate" id="2" text="My Tab 2">
  +
<Anchors>
  +
<Anchor point="LEFT" relativeTo="$parentTab1" relativePoint="RIGHT">
  +
<Offset>
  +
<AbsDimension x="-16" y="0"/>
  +
</Offset>
  +
</Anchor>
  +
</Anchors>
  +
<Scripts>
  +
<OnClick>
  +
PanelTemplates_SetTab(myTabContainerFrame, 2);
  +
myTabPage1:Hide();
  +
myTabPage2:Show();
  +
</OnClick>
  +
</Scripts>
  +
</Button>
  +
  +
'''NOTE:''' The names of these buttons ''must'' be exactly "$parentTab1", "$parentTab2", etc... otherwise the calls to PanelTemplates_UpdateTabs() will fail with an error.
  +
 
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.
 
You won't need any more eventhandlers in there, it's all in the template.
   
Line 44: Line 246:
   
 
== Tabs in LUA ==
 
== Tabs in LUA ==
  +
Look at the OnLoad code which registers our tabbed frame with Blizzard's UI code, sets which tab should be selected, and then shows and hides the appropriate pages:
Now things are getting interesting. The first thing to do is register our tabs in Blizzards tabbing-system:
 
   
PanelTemplates_SetNumTabs(myFrame, n);
+
PanelTemplates_SetNumTabs(myTabContainerFrame, 2); -- 2 because there are 2 frames total.
  +
PanelTemplates_SetTab(myTabContainerFrame, 1); -- 1 because we want tab 1 selected.
dg_calFrame.selectedTab=1;
 
  +
myTabPage1:Show(); -- Show page 1.
PanelTemplates_UpdateTabs(myFrame);
 
  +
myTabPage2:Hide(); -- Hide all other pages (in this case only one).
   
 
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.
 
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.
   
 
Take your time to understand what the PanelTemplates_SetTab()-function does. This is basically the whole trick behind it all. PanelTemplates_SetTab() goes some graphics magic, making the selected tab appear to be selected, while you're left to actually display the proper page.
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()!
 
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!
 
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!
   
 
In your Lua button handler (the one I left for an exercise), you probably already know what to do: Extract the number of the selected tab from the button's name which was passed as a parameter ('''this:GetID()'''), 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:
 
 
* Always wrap up your tabs inside of separate 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.
So, one last thing to do: The button handler!
 
 
* 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 clear, making maintenance so much easier
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 it with a single for loop instead of writing hundreds of lines and 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
 
* 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 =
 
= 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 in use.
+
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.
While 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.
+
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.
 
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.
  +
  +
[[Category:HOWTOs|Create Tabbed Windows]]

Revision as of 01:15, 29 April 2009

Advanced Tutorial: Tabbed Windows in WoW -Contributed by DerGhulbus-
Put it on my Tab

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 necessary 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. For an excellent example of tabbed interface implementation, check out the source code for the CT_ExpenseHistory mod [1]

Tabs in XML

Let's start off with the frame that will serve as the outermost container of our tabbed interface. Inside this frame, you will embed child frames that will each define a single "page" of the interface, and you will write button click handlers that show a single page at a time and hide the rest.

This might look like a lot of code, and it is. Nature of the beast with the WoW XML UI.

   <Frame
       name="myTabContainerFrame"
       toplevel="true"
       frameStrata="DIALOG"
       movable="true"
       enableMouse="true"
       hidden="false"
       parent="UIParent">
       <Size>
           <AbsDimension x="480" y="325"/>
       </Size>
       <Anchors>
           <Anchor point="CENTER">
               <Offset><AbsDimension x="-200" y="200"/></Offset>
           </Anchor>
       </Anchors>
       <Backdrop
           bgFile="Interface\DialogFrame\UI-DialogBox-Background"
           edgeFile="Interface\DialogFrame\UI-DialogBox-Border"
           tile="true">
           <BackgroundInsets>
               <AbsInset left="11" right="12" top="12" bottom="11"/>
           </BackgroundInsets>
           <TileSize>
               <AbsValue val="32"/>
           </TileSize>
           <EdgeSize>
               <AbsValue val="32"/>
           </EdgeSize>
       </Backdrop>
       <Layers>
           <Layer level="ARTWORK">
               <Texture name="myFrameHeader" file="Interface\DialogFrame\UI-DialogBox-Header">
                   <Size>
                       <AbsDimension x="356" y="64"/>
                   </Size>
                   <Anchors>
                       <Anchor point="TOP">
                           <Offset>
                               <AbsDimension x="0" y="12"/>
                           </Offset>
                       </Anchor>
                   </Anchors>
               </Texture>
               <FontString inherits="GameFontNormal" text="My Frame">
                   <Anchors>
                       <Anchor point="TOP" relativeTo="myFrameHeader">
                           <Offset>
                               <AbsDimension x="0" y="-14"/>
                           </Offset>
                       </Anchor>
                   </Anchors>
               </FontString>
           </Layer>
       </Layers>
       <Frames>
           <Frame name="myTabPage1" hidden="false">
               <Anchors>
                   <Anchor point="TOPLEFT"/>
                   <Anchor point="BOTTOMRIGHT"/>
               </Anchors>
               <Layers>
                   <Layer level="ARTWORK">
                       <FontString inherits="GameFontNormal" text="My Frame 1">
                           <Anchors>
                               <Anchor point="TOPLEFT" relativeTo="$parent">
                                   <Offset>
                                       <AbsDimension x="20" y="-30"/>
                                   </Offset>
                               </Anchor>
                           </Anchors>
                       </FontString>
                   </Layer>
               </Layers>
               <Frames>
               </Frames> 
           </Frame>   
           <Frame name="myTabPage2" hidden="true">
               <Anchors>
                   <Anchor point="TOPLEFT"/>
                   <Anchor point="BOTTOMRIGHT"/>
               </Anchors>
               <Layers>
                   <Layer level="ARTWORK">
                       <FontString inherits="GameFontNormal" text="My Frame 2">
                           <Anchors>
                               <Anchor point="TOPLEFT" relativeTo="$parent">
                                   <Offset>
                                       <AbsDimension x="20" y="-30"/>
                                   </Offset>
                               </Anchor>
                           </Anchors>
                       </FontString>
                   </Layer>
               </Layers>
               <Frames>
               </Frames> 
           </Frame>
           <Button name="$parentTab1" inherits="CharacterFrameTabButtonTemplate" id="1" text="My Tab 1">
               <Anchors>
                   <Anchor point="CENTER" relativePoint="BOTTOMLEFT">
                       <Offset>
                           <AbsDimension x="60" y="-12"/>
                       </Offset>
                   </Anchor>
               </Anchors>
               <Scripts>
                   <OnClick>
                       PanelTemplates_SetTab(myTabContainerFrame, 1);
                       myTabPage1:Show();
                       myTabPage2:Hide();
                   </OnClick>
               </Scripts>
           </Button>
           <Button name="$parentTab2" inherits="CharacterFrameTabButtonTemplate" id="2" text="My Tab 2">
               <Anchors>
                   <Anchor point="LEFT" relativeTo="$parentTab1" relativePoint="RIGHT">
                       <Offset>
                           <AbsDimension x="-16" y="0"/>
                       </Offset>
                   </Anchor>
               </Anchors>
               <Scripts>
                   <OnClick>
                       PanelTemplates_SetTab(myTabContainerFrame, 2);
                       myTabPage1:Hide();
                       myTabPage2:Show();
                   </OnClick>
               </Scripts>
           </Button>
       </Frames>
       <Scripts>
           <OnLoad>
               this.elapsed = 0;
               PanelTemplates_SetNumTabs(myTabContainerFrame, 2);
               PanelTemplates_SetTab(myTabContainerFrame, 1);
           </OnLoad>
           <OnShow>
               PlaySound("UChatScrollButton");
               PanelTemplates_SetTab(myTabContainerFrame, 1);
               myTabPage1:Show()
               myTabPage2:Hide()
           </OnShow>
           <OnHide>
               PlaySound("UChatScrollButton");
           </OnHide>
       </Scripts>
   </Frame>

This is a completely functional, self contained Tab Frame which you should be able to plop into any WoW XML UI file and run (as of Apr. 23, 2008). There are a lot of redundancies here. Things like the tab button properties should be templated and the logic controlling the selection of tabs is best handled by a custom function.

Things like the tab button properties should be templated if you're going to have lots of tabs. As mentioned before, from an UI's point of view, tabs are simply some buttons at the bottom of your frame. You should define a template like this in XML, and then implement a smart click handler in Lua:

<Button name="myFrameTabTemplate" inherits="CharacterFrameTabButtonTemplate" virtual="true">
	<Scripts>
		<OnClick>
			myButtonHandler(this:GetName());
		</OnClick>
	</Scripts>
</Button>

This will reduce the amount of code you need per tab. As an example, your first tab would be only:

           <Button name="$parentTab1" inherits="myFrameTabTemplate" id="1" text="My Tab 1">
               <Anchors>
                   <Anchor point="CENTER" relativePoint="BOTTOMLEFT">
                       <Offset>
                           <AbsDimension x="60" y="-8"/>
                       </Offset>
                   </Anchor>
               </Anchors>
           </Button>


I'll leave the implementation of myButtonHandler() as an exercise for the reader.

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.

Let's look at our existing buttons:

           <Button name="$parentTab1" inherits="CharacterFrameTabButtonTemplate" id="1" text="My Tab 1">
               <Anchors>
                   <Anchor point="CENTER" relativePoint="BOTTOMLEFT">
                       <Offset>
                           <AbsDimension x="60" y="-8"/>
                       </Offset>
                   </Anchor>
               </Anchors>
               <Scripts>
                   <OnClick>
                       PanelTemplates_SetTab(myTabContainerFrame, 1);
                       myTabPage1:Show();
                       myTabPage2:Hide();
                   </OnClick>
               </Scripts>
           </Button>
           <Button name="$parentTab2" inherits="CharacterFrameTabButtonTemplate" id="2" text="My Tab 2">
               <Anchors>
                   <Anchor point="LEFT" relativeTo="$parentTab1" relativePoint="RIGHT">
                       <Offset>
                           <AbsDimension x="-16" y="0"/>
                       </Offset>
                   </Anchor>
               </Anchors>
               <Scripts>
                   <OnClick>
                       PanelTemplates_SetTab(myTabContainerFrame, 2);
                       myTabPage1:Hide();
                       myTabPage2:Show();
                   </OnClick>
               </Scripts>
           </Button>

NOTE: The names of these buttons must be exactly "$parentTab1", "$parentTab2", etc... otherwise the calls to PanelTemplates_UpdateTabs() will fail with an error.

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

Look at the OnLoad code which registers our tabbed frame with Blizzard's UI code, sets which tab should be selected, and then shows and hides the appropriate pages:

 PanelTemplates_SetNumTabs(myTabContainerFrame, 2);  -- 2 because there are 2 frames total.
 PanelTemplates_SetTab(myTabContainerFrame, 1);      -- 1 because we want tab 1 selected.
 myTabPage1:Show();  -- Show page 1.
 myTabPage2:Hide();  -- Hide all other pages (in this case only one).

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.

Take your time to understand what the PanelTemplates_SetTab()-function does. This is basically the whole trick behind it all. PanelTemplates_SetTab() goes some graphics magic, making the selected tab appear to be selected, while you're left to actually display the proper page.

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!

In your Lua button handler (the one I left for an exercise), you probably already know what to do: Extract the number of the selected tab from the button's name which was passed as a parameter (this:GetID()), 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:

  • Always wrap up your tabs inside of separate 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 clear, 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.