This project is read-only.

UI Widget Basics

This page contains a brief description on how individual UI widgets are handled, from creation to execution. It covers all of the reserved fields and methods that can be supplied to alter widget behaviour, as well as how to define additional widgets.

Contents

Defining Widgets

Widget definitions consist of a name for the class and a table containing all of the functions and values available to frames which inherit this widget. None of the reserved fields or methods listed above are mandatory, and can be omitted if you so choose. The following example shows how to define a simple widget class.

-- PowaAuras.UI:Register(name, class);
PowaAuras.UI:Register("Greeter", {
    Init = function(self, name)
        if(not name) then
            PowaAuras:ShowText("Hello there, you forgot to tell me your name.");
        else
            PowaAuras:ShowText("Hello there, ", name, "!");
        end
    end,
});

In the above example we created a widget class named Greeter, and supplied it our class table. The class table had a method named Init, which was a reserved method and is covered later on.

Using Widgets

It's all well and good having a widget class, but it'll never be executed like that because no frames are having it applied. So let's take a frame and apply the Greeter widget class to it, and see what happens.

-- Make a frame first. You can use existing frames if you want.
local frame = CreateFrame("Frame");
-- Call the Greeter class and pass the frame along to it. The greeter class is accessible from the UI table, same as the Register function.
PowaAuras.UI:Greeter(frame);

If done correctly, this will initialize the frame and you'll have "Hello there, you forgot to tell me your name." output into your chatbox. Obviously, you should be passing a name too.

-- Make a frame first. You can use existing frames if you want.
local frame = CreateFrame("Frame");
-- Call the Greeter class and pass the frame along to it. The greeter class is accessible from the UI table, same as the Register function.
PowaAuras.UI:Greeter(frame, "Bob");

Simple enough, right? Let's go on to something more advanced.

Reserved Method - Init

The Init method is special, but is not mandatory. It is fired whenever a frame has been initialized with the fields, hooks and scripts of a widget class and is used to set per-frame values up as well as perform updates. You can supply it any number of arguments, and the self argument is always a reference to the frame you initialized.

The Init method should always be designed in mind to allow for multiple executions on a single frame in its lifetime, especially if the class constructor implements any form of widget recycling.

Defining Functions

There's not much point just specifying a class that just has an Init method, so let's also add some extra things to the class. You can only define a class once, so apply this to the existing example.

PowaAuras.UI:Register("Greeter", {
    Init = function(self, name)
        if(not name) then
            PowaAuras:ShowText("Hello there, you forgot to tell me your name.");
        else
            PowaAuras:ShowText("Hello there, ", name, "!");
        end
    end,
    IsAwesome = function(self)
        return true;
    end,
});

If you define a frame without applying the Greeter class to it, it will not have a method named IsAwesome, however if you apply the class it will magically gain it.

local frame = CreateFrame("Frame");
PowaAuras.UI:Greeter(frame, "Bob");
print(frame:IsAwesome());

You'll notice the self argument in the IsAwesome function argument list, this should always be a direct reference to the frame that called the function. To ensure that it is passed, you should always call the function with a double-colon directly from the frame, but there is a situation later on where double colon syntax should not be used. These examples will behave differently.

frame:IsAwesome(); -- self argument points to frame
frame.IsAwesome(); -- self argument is not passed at all.
frame.IsAwesome(frame); -- self argument is a reference to frame again.

PowaAuras.UI.Greeter:IsAwesome(); -- self doesn't point to frame, it points to the Greeter class!
PowaAuras.UI.Greeter.IsAwesome(frame); -- self is pointing to frame.


Definitions - How Do They Work?

When you define a widget class, the following things take place (in this order).
  1. We make sure that no class with this name already exists, and that the name does not conflict with a reserved name. Reserved names are Register and Construct.
  2. Your class is checked to see if it inherits another widget class, if it does then the following takes place;
    1. The Scripts and Hooks tables are merged, so your new class will inherit the same hooks and scripts as its ancestors.
    2. All non-special fields are inherited so long as they don't overwrite your existing ones. Special fields are Hooks and Scripts but not Base, Construct or Init.
  3. If your definition does not contain a Construct method, it inherits the basic one.

Some of these things have not yet been covered, and are explained below.

Reserved Field - Base

This is a simple one, it represents the name of another widget class which you want to inherit the non-special fields, hooks and scripts from.

PowaAuras.UI:Register("Base", {
    BaseFunc = function(self)
        PowaAuras:ShowText("BaseFunc executed!");
    end,
    OtherFunc = function(self)
        PowaAuras:ShowText("OtherFunc executed in base class!");
    end,
});
PowaAuras.UI:Register("Child", {
    Base = "Base",
    TopFunc = function(self)
        PowaAuras:ShowText("TopFunc executed!");
    end,
    OtherFunc = function(self)
        PowaAuras:ShowText("OtherFunc executed in top class!");
    end,
});
local frame = CreateFrame("Frame");
PowaAuras.UI:Child(frame);
frame:BaseFunc(); -- Inherited from Base class.
frame:TopFunc(); -- Inherited from Child class.
frame:OtherFunc(); -- Child class one is used, as it overwrites the Base one.

This is useful if you're trying to keep repeated code to a minimum. But what if you want to access the inherited class from your child class? That's possible too, but it requires a minor caveat. We replace the OtherFunc in the Child class with this instead.

PowaAuras.UI:Register("Child", {
    Base = "Base",
    TopFunc = function(self)
        PowaAuras:ShowText("TopFunc executed!");
    end,
    OtherFunc = function(self)
        -- This time, both OtherFunc functions will be called.
        PowaAuras.UI.Base.OtherFunc(self);
        PowaAuras:ShowText("OtherFunc executed in top class!");
    end,
});

While in the class definition we declared the Base field as a string, when it is passed to the Register function it is converted into a reference to the parent class. Multiple inheritance is not supported, but you can create a long chain of inherited classes such as A > B > C. When accessing methods in parent classes, do not use the double colon syntax when calling the functions as the self argument will point to the class and not the frame, instead use the period syntax and pass the frame manually in the argument list.

Reserved Field - Scripts

This is a simple one, the Scripts field can be supplied and if the frame being initialized with the class supports the SetScript method, then the scripts will be registered automatically. When a script is executed, it will either run a method with the same name as the script or a different function depending on the name passed.

PowaAuras.UI:Register("ScriptTest", {
    Scripts = {
        OnEnter = true, -- To make it so that a function with the same name as the script is called, simply use true as the value.
        OnLeave = "OnLeaveFunc", -- Otherwise, pass the function name to execute as a string.
    },
    OnEnter = function(self)
        PowaAuras:ShowText("Mouse is here! GET HIM!");
    end,
    OnLeaveFunc = function(self)
        PowaAuras:ShowText("Mouse escaped :(");
    end,
});

local frame = CreateFrame("Frame");
PowaAuras.UI:ScriptTest(frame);

If you mouseover a frame created by the above example, you'll see that the OnEnter and OnLeave scripts were registered and called.

Reserved Field - Hooks

Similarly, you can completely replace functions while keeping the old ones around for hooking purposes if needed. The Hooks field should be a table of fields you want to store before having them overwritten, and they do not need to necessarily be functions. Functions stored by hooks are prefixed with two underscores.

PowaAuras.UI:Register("HooksTest", {
    Hooks = {
        "Show",
        "Hide",
    },
    Hide = function(self)
        self:__Show();
    end,
    Show = function(self)
        self:__Hide();
    end,
});

local frame = CreateFrame("Frame");
PowaAuras.UI:HooksTest(frame);


So in the above example we replaced the Show and Hide functions and have swapped them over, calling Show will actually call the original Hide function (which is now named Hide), and vice versa.

Reserved Method - Construct

Before initialization, hooks and scripts are handled we need to execute a constructor to handle everything. If you don't manually override the Construct method, then the default one will be used, it is invoked when you do the following.

PowaAuras.UI:Greeter(frame);

In Lua terms, the __call metamethod of your widget class is the constructor. It is only ever executed once per frame, unlike Init which can potentially be executed multiple times on a single frame, and it handles the following when executed.
  1. The existance of a Hooks table is checked, if found then any fields and methods mentioned in the table are referenced in a new key with the double underscore prefix.
  2. Any fields and methods defined in the widget class are copied to the actual frame.
  3. Any scripts in the Scripts field are registered and point to the methods on the frame.
  4. If an Init function has been supplied, it is executed.
  5. The modified frame is returned.

You are free to replace the constructor on your classes, however note that in doing so none of the above tasks will be performed for you. To ease the amount of code copying for this however, you can access the default Construct method if you simply want to apply additional things for a single class, or if you want to apply widget recycling. The original constructor method is named Construct and is on the PowaAuras.UI table.

-- Construct arguments are as follows:
-- :Construct(<widget class>, <PowaAuras.UI table>, <frame>, <additional arguments, use the vararg operator (...)>)
PowaAuras.UI:Register("NonconformingWidget", {
    Construct = function(class, ui, frame, ...)
        -- Call original constructor if you want.
        frame = ui.Construct(class, _, frame ...); -- You can omit the second argument from the original constructor call, it's always PowaAuras.UI.
        -- Do something here.
        frame.Random = random(1,100);
        -- Now we're done.
        return frame;
    end,
});

Final Notes

Below is a final summary of notes to take into consideration when creating and initializing widgets.
  • Anything defined in a class is accessed by reference. If you define an integer to be equal to random(1, 100), then any frames which inherit this widget class will all get the same value. If you define a table, all frames will be able to see the exact same table - you should use the Init method if you want to define fields on a per-frame basis.
  • Never execute the Construct method of a single widget class twice for a single frame, unless you have properly overridden the method. Doing so with the default constructor will cause Hooks to be duplicated, potentially resulting in stack overflows if your hooked functions are called.
  • The Constructor method can be overridden for whatever you want, for example the Tooltip class replaces it to allow for existing frames to specify their own tooltip update method without forcing it to be overwritten.
  • While you can define a Base field in your class definition, it is not copied to any frames. This is also true for the Hooks and Scripts fields.

Last edited May 11, 2011 at 11:08 PM by dyates92, version 23

Comments

No comments yet.