WoW:Creating simple pop-up dialog boxes (source)
Revision as of 10:35, 20 September 2009
, 20 September 2009→Passing Arguments to Local Functions
({{UIHowTo}}, slight lead-in tweak) |
|||
| Line 20: | Line 20: | ||
end, | end, | ||
timeout = 0, | timeout = 0, | ||
whileDead = | whileDead = true, | ||
hideOnEscape = | hideOnEscape = true, | ||
} | } | ||
The index into the array is an arbitrary string. It must be unique in the context of the array. (If you are | The index into the array is an arbitrary string. It must be unique in the context of the array. (If you are | ||
| Line 34: | Line 34: | ||
* ''OnAccept'' - This is a local function; it can be as complicated as you like. You do not need to explicitly hide the popup frame, it will be done for you. | * ''OnAccept'' - This is a local function; it can be as complicated as you like. You do not need to explicitly hide the popup frame, it will be done for you. | ||
* ''timeout'' - After this many seconds, the dialog will go away. If the entry has an <tt>OnCancel</tt> function, it will be called with "timeout" as the reason. Dialogs which do not expire should set this to zero. | * ''timeout'' - After this many seconds, the dialog will go away. If the entry has an <tt>OnCancel</tt> function, it will be called with "timeout" as the reason. Dialogs which do not expire should set this to zero. | ||
* ''whileDead'' - Set to | * ''whileDead'' - Set to true if this dialog can be shown while the player is a ghost. You probably want to do this. | ||
* ''hideOnEscape'' - Set to | * ''hideOnEscape'' - Set to true if hitting the Escape key should be treated like clicking button2. You probably want to do this. | ||
If your addon has a general OnLoad event handler, that is an excellent place to perform this array insertion. | If your addon has a general OnLoad event handler, that is an excellent place to perform this array insertion. | ||
Note that this guide refers to setting boolean options to 'true' or 'false'; earlier editions used '1' and 'nil' for the same purposes. The fact of the matter is that you can set those options to anything which evaluates to true-valued or false-valued results according to Lua: 'nil' and 'false' are false-valued, anything else (including 0 and "") is true-valued. A lot of original Blizzard code was written for an earlier version of Lua, which did not have formal boolean types. Use whatever makes the most sense for your addon. | |||
| Line 84: | Line 86: | ||
=== Editable Text Fields === | === Editable Text Fields === | ||
To add a editbox in the popup set the ''hasEditBox'' option to | To add a editbox in the popup set the ''hasEditBox'' option field to true. The EditBox gets the name "$parentEditBox", relative to the Popup, and is also available in the popup's ''.editBox'' field. (Recall that the popup dialog is the value returned from the ''StaticPopup_Show()'' function.) To get and set the value of the EditBox use the following code: | ||
OnShow = function() | OnShow = function (self, data) | ||
self.editBox:SetText("Some text goes here") | |||
end, | end, | ||
OnAccept = function() | OnAccept = function (self, data, data2) | ||
local text = self.editBox:GetText() | |||
-- do whatever you want with it | -- do whatever you want with it | ||
end, | end, | ||
hasEditBox = | hasEditBox = true | ||
More complicated usage might be to use OnShow to call <tt>self.button1:Disable()</tt> (graying out the Accept button), and then including the following in your options table to allow the Accept button to be clicked as soon as the user types something in the text field: | |||
EditBoxOnTextChanged = function (self, data) -- careful! 'self' here points to the editbox, not the dialog | |||
self:GetParent().button1:Enable() -- self:GetParent() is the dialog | |||
end | |||
Using ''hasEditBox''/''.editBox'' causes a small one-line text field to appear. You can use the ''hasWideEditBox'' option field (and the corresponding ''.wideEditBox'' dialog field) to cause the text field to have the full width of the dialog box instead. To do this, you must set both options, but only the wide edit box will be used. | |||
== Optional Features == | == Optional Features == | ||
| Line 104: | Line 111: | ||
* ''sound'' - Play this sound when the dialog is displayed. For example, "igPlayerInvite". | * ''sound'' - Play this sound when the dialog is displayed. For example, "igPlayerInvite". | ||
* ''hasMoneyFrame'' - This is for things like the money-in-mail confirmation. See the Lua file for more. | * ''hasMoneyFrame'' - This is for things like the money-in-mail confirmation. See the Lua file for more. | ||
* ''showAlert'' - Set this to | * ''showAlert'' - Set this to true if you also want the OH NO SOMETHING BAD icon to be displayed in the popup. For example, deleting a mail message with an item still attached to it. | ||
* ''notClosableByLogout'' - Normally if a player quits the game, your popup will go away and its OnCancel function will be called. Set this field to | * ''notClosableByLogout'' - Normally if a player quits the game, your popup will go away and its OnCancel function will be called. Set this field to true to avoid that behavior. | ||
* ''cancels'' - If your popup should make another popup go away, set this field to that popup's name. For example, <tt>cancels = "EXAMPLE_HELLOWORLD"</tt>. If that popup is displayed at the time yours is shown, that popup's OnCancel function will be called and its frame hidden. | * ''cancels'' - If your popup should make another popup go away, set this field to that popup's name. For example, <tt>cancels = "EXAMPLE_HELLOWORLD"</tt>. If that popup is displayed at the time yours is shown, that popup's OnCancel function will be called and its frame hidden. | ||
* ''StartDelay'' and ''delayText'' - These fields are responsible for things like the delay before you can resurrect, before you get kicked out of an ungrouped instance, etc. The first is a function (a pointer to the function itself, not its name) to be called which returns the number of seconds which must pass before button1 can be used. The second is text to be displayed during the delay; it is formatted like the normal ''text'' field, with the time as the parameters. | * ''StartDelay'' and ''delayText'' - These fields are responsible for things like the delay before you can resurrect, before you get kicked out of an ungrouped instance, etc. The first is a function (a pointer to the function itself, not its name) to be called which returns the number of seconds which must pass before button1 can be used. The second is text to be displayed during the delay; it is formatted like the normal ''text'' field, with the time as the parameters. | ||
* ''exclusive'' - Set this to | * ''exclusive'' - Set this to true to make your popup go away if any other popup is displayed. | ||
* ''enterClicksFirstButton'' - If your popup is in response to a slash command or some other keyboard event, then setting this allows the player to click Accept without having to move a hand off the keyboard to the mouse. Great for those fussy speed typists! :-) | |||
* ''OnShow'' and ''OnHide'' - These functions will be called when the popup frame is initially shown and finally hidden, respectively. Most popups can ignore these. A very few need to do something special. | * ''OnShow'' and ''OnHide'' - These functions will be called when the popup frame is initially shown and finally hidden, respectively. Most popups can ignore these. A very few need to do something special. | ||
* ''button3''/''OnAlt'' - You can set text for a third button to appear, and its corresponding callback. The button is placed ''between'' the first two buttons. (To remember this, think of how mouse buttons are labeled.) | |||
As an example, consider the Ready Check from [http://ctmod.net/ CT_RaidAssist], which pops up a two-button dialog box with a 30-second timeout. Currently the CT authors implement everything from scratch: 107 lines of XML specifying window, field, and button sizes, plus a couple lines of Lua here and there. It could be replaced with something like the following: | As an example, consider the Ready Check from [http://ctmod.net/ CT_RaidAssist], which pops up a two-button dialog box with a 30-second timeout. Currently the CT authors implement everything from scratch: 107 lines of XML specifying window, field, and button sizes, plus a couple lines of Lua here and there. It could be replaced with something like the following: | ||
| Line 118: | Line 127: | ||
button2 = "No", | button2 = "No", | ||
OnAccept = function() | OnAccept = function() | ||
CT_RA_SendReady() | CT_RA_SendReady() | ||
end, | end, | ||
OnCancel = function (_,reason) | OnCancel = function (_,reason) | ||
if reason == "timeout" or reason == "clicked" then | if reason == "timeout" or reason == "clicked" then | ||
CT_RA_SendNotReady() | CT_RA_SendNotReady() | ||
else | else | ||
-- "override" ...? | -- "override" ...? | ||
| Line 129: | Line 138: | ||
sound = "levelup2", | sound = "levelup2", | ||
timeout = 30, | timeout = 30, | ||
whileDead = | whileDead = true, | ||
hideOnEscape = | hideOnEscape = true, | ||
} | } | ||
and called via | and called via | ||
| Line 142: | Line 151: | ||
== Passing Arguments to Local Functions == | == Passing Arguments to Local Functions == | ||
It is possible, though not immediately obvious how, to pass arbitrary user data to the local functions (e.g. OnAccept, OnCancel). | It is possible, though not immediately obvious how, to pass arbitrary user data to the local functions (e.g. OnAccept, OnCancel). Write your local function like this: | ||
OnAccept = function (self, data, data2) | |||
DoSomethingWith(self.data) | |||
end | |||
And make the call like this: | |||
varName = "Some value" -- This is the data you want to use in OnAccept | |||
varName2 = "Some other value" -- This is the data you want to use in OnAccept | |||
local dialog = StaticPopup_Show("YOUR_POPUP") -- dialog contains the frame object | |||
if (dialog) then | |||
dialog.data = varName -- set the frame's data field to the value you want | |||
dialog.data2 = varName2 | |||
end | |||
The dialog's fields "data" and "data2" are passed as additional arguments to OnAccept. The "data" field is passed to OnCancel and OnAlt (the function called if button3 is set), but not "data2". They are also accessible through the dialog itself, as ''self.data'' and ''self.data2''. In this example they are strings, but they can be of any type including tables. | |||
Notice that you are actually setting the frame's data ''after'' the user has clicked the button. This works because popping up a dialog box does not halt script execution. The dialog box isn't even visible to the player until after the current script execution cycle completes. By the time the player clicks a button to run OnAccept/OnCancel/etc, all of this code will have long since finished. | |||
If you need to manipulate any of the dialog's visual elements, most of them are directly accessible as table fields of the dialog, so you do not need to use an expensive sequence of ''getglobal(self:GetName().."Something")''. For example, ''dialog.button1'' points to the first button. See the StaticPopup.xml file's various OnLoad sections for the full list. | |||
== Notes and Observations == | == Notes and Observations == | ||
* The 'text' fields and button1/2 fields are usually localized. Blizzard tends to use generic texts like ACCEPT, CANCEL, and OKAY; you can probably do the same. These global variables contain a localized string and can be used as-is, like <tt>button1 = ACCEPT</tt> | * The 'text' fields and button1/2/3 fields are usually localized. Blizzard tends to use generic texts like ACCEPT, CANCEL, and OKAY; you can probably do the same. These global variables contain a localized string and can be used as-is, like <tt>button1 = ACCEPT</tt> | ||
* Creating a static popup with an editbox and only one button will cause the button and the editbox to overlap. Having more than one button will get the desired behavior. (Tested on 2.4.1) | * Creating a static popup with an editbox and only one button will cause the button and the editbox to overlap. Having more than one button will get the desired behavior. (Tested on 2.4.1) | ||
* While creating your popup entries, you will probably be doing a lot of UI reloading. Extract the <tt>[[FrameXML/DebugUI.xml]]</tt> file from the default UI, copy it into your addon folder, and add it to your .toc file. This has two effects: it starts verbose logging into FrameXML.log, and it adds a "Reload UI" button to your screen. Very handy timesaver. :-) | * While creating your popup entries, you will probably be doing a lot of UI reloading. Extract the <tt>[[FrameXML/DebugUI.xml]]</tt> file from the default UI, copy it into your addon folder, and add it to your .toc file. This has two effects: it starts verbose logging into FrameXML.log, and it adds a "Reload UI" button to your screen. Very handy timesaver. :-) | ||