Creating simple pop-up dialog boxes

From Warcraft Wiki
Jump to navigation Jump to search

Static popups are simple dialog boxes seen with logging off, accepting summons, sending money through the mail, renaming a pet, and so forth. This tutorial describes using StaticPopup code to insert simple dialogs to an addon.

FrameXML/StaticPopup.lua handles all of the basic UI elements (positioning, sound effects, layout).


Basic setup

New dialogs are created by adding an entry to the global StaticPopupDialogs table, and populating the entry with required and optional information. Here is a simple two-button entry:

StaticPopupDialogs["EXAMPLE_HELLOWORLD"] = {
	text = "Do you want to greet the world today?",
	button1 = "Yes",
	button2 = "No",
	OnAccept = function()
       SendChatMessage("HelloWorld!", "SAY")
 	end,
	timeout = 0,
	whileDead = true,
	hideOnEscape = true,
}
StaticPopup_Show("EXAMPLE_HELLOWORLD")

The index into the array is an arbitrary string, but it should be unique within the array. Many elements in this example are optional, but have customary uses suitable for most addons.

To display the dialog, call StaticPopup_Show() with the name of the entry. This function returns a reference to the static popup frame, or nil if unsuccessful (i.e. if four simultaneous static popups are already shown).

Usually the dialog will be configured to hide itself (pressing a button, pressing escape or after a timeout), but this may also be done programmatically with StaticPopup_Hide(). Different addons may have concurrent popups, so it is necessary to supply arguments indicating which entry to hide.

Optional features

Buttons
Key Type Description
button1,
button2,
button3
string Labels for each button starting from the left. Only buttons assigned a label are shown: may be none, and may skip buttons. Unless overriden, pressing a button hides the popup.
OnAccept,
OnButton1
function Fires when button1 pressed, and may return true to prevent the popup from hiding. Receives data and data2 optional arguments. Only OnAccept or OnButton1 may be defined, not both.
OnCancel,
OnButton2
function Fires when button2 pressed, and may return true to prevent the popup from hiding. Receives data optional argument. Only OnCancel or OnButton2 may be defined, not both. The former may fire if the popup is forced to close prematurely.
OnButton3,
OnAlt
function Fires when button3 pressed. Receives data optional argument.
DisplayButton1,
DisplayButton2,
DisplayButton3
function Optional function that may return a boolean to control whether the button is shown. For example, releasing your spirit after dying sets DisplayButton2 = function(self) return HasSoulstone() end.
button4,
extraButton,
onButton4,
onExtraButton
... Some extra features are currently being phased in, but there is temporary special handling for backward compatibiltiy. Refer to the source code for details.
FontStrings and Textures
Key Type Description
text string Text to display in the dialog box; e.g. "Looting this item will bind it to you." Receives optional arguments text_arg1 and text_arg2 from StaticPopup_Show()
startDelay number Disables button1 for a number of seconds while replacing text with delayText
delayText string Replacement text during the startDelay; will also receive optional argments text_arg1 and text_arg2.
hasItemFrame boolean Displays the item frame. Requires the optional argument data in StaticPopup_Show().
hasMoneyFrame boolean Displays the money frame, such as when sending money through in-game mail. Requires OnShow to call function(self) MoneyFrame_Update(self.moneyFrame, amountInCopper) end
showAlert boolean Displays an alert icon in the popupm such as when deleting a message with an item still attached.
Other settings
timeout number Unless zero, hides the popup after a number of seconds.
whileDead boolean Displays the dialog even while the player is a ghost.
hideOnEscape boolean Hides the dialog when the user presses escape.
sound number Plays when the dialog is displayed; e.g. SOUNDKIT.IG_PLAYER_INVITE (880).
notClosableByLogout boolean Prevents OnCancel from being called if the popup is dismissed early by the player logging out.
noCancelOnReuse boolean Prevents OnCancel from being called if the popup is dismissed early by a higher-priority popup interrupting it to reuse the dialog frame (i.e. the maximum number of frames were already being shown).
cancels string Dismisses other popups by this name, firing their OnCancel if applicable.
exclusive boolean Hides when any other popup is displayed.
enterClicksFirstButton boolean Pressing enter will click the left-most visible button (usually button1). Has no effect if no butons are visible. Note: the OnKeyDown handler does not suffice as a 'hardware event' for protected functions.
OnShow,
OnHide
function Called by the dialog frame's OnShow and OnHide handlers.

Advanced configuration

Localization

See also: Localizing an addon

For a simple popup in any language, use YES, NO, ACCEPT, CANCEL, DELETE, OKAY, or CONTINUE on button labels; and CONFIRM_CONTINUE for text ("Do you wish to continue?").

StaticPopupDialogs["EXAMPLE_CONFIRM"] = {
	text = CONFIRM_CONTINUE,
	button1 = ACCEPT,
	button2 = CANCEL,
}

Callback functions

Icon-time.svg This section contains information that is out-of-date. Reason: The callback handlers have evolved since Legion; but this content is still partly backward compatible

  • OnAccept - This function can take up to two arguments, both optional. They are for passing arbitrary data around the callbacks. In general this is used with dialogs that have an editable text field, to pass the entered text back to the function; also for chaining dialog boxes together. For details, see "Passing arguments to local functions" below.
  • OnCancel - This function can take up to three arguments, all optional. The first and second are used the same way as the first parameter in OnAccept (whatever that may actually be). The third is a string describing the reason the popup was cancelled; the game will pass this as required:
    • "override" - Another popup cancelled this one, or is set to 'exclusive' (see below), or the player is dead or logging out.
    • "timeout" - The popup's entry specified a nonzero timeout field, and the time expired.
    • "clicked" - The player clicked button2, or hit the escape key and hideOnEscape is set.

Dialog text parameters

The text field of an entry can contain formatting placeholders. When the popup is actually displayed, the calling function can pass two additional arguments to StaticPopup_Show. The text field and these two arguments will be passed through the format function before being added to the dialog.

For example, the default guild invitation popup sets text = "%s invites you to join %s", and the display call is to StaticPopup_Show ("GUILD_INVITE", name_of_officer, name_of_guild).

Editable text fields

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 (self, data)
    self.editBox:SetText("Some text goes here")
end,
OnAccept = function (self, data, data2)
    local text = self.editBox:GetText()
    -- do whatever you want with it
end,
hasEditBox = true

More complicated usage might be to use OnShow to call self.button1:Disable() (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. By default this is 130 pixels wide; you can set the optional editBoxWidth key to change the width. (There used to be a hasWideEditBox boolean key that would create a .wideEditBox field in the dialog; in Cataclysm these were replaced by a editBoxWidth of 260 to 350.) The editbox will respect the maxLetters field if you set it (most editbox popups used by Blizzard set this to 24 or 31).

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). Write your local function like this:

 OnAccept = function (self, data, data2)
     DoSomethingWith(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 you have called StaticPopup_Show. 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

  • 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 FrameXML/DebugUI.xml file from the default UI, copy it into your addon's 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.  :-)
  • Added by Layrajha: The OnHide function will always be called after OnAccept or OnCancel have finished their execution. Therefore, it is safe to assume that your changes done in OnAccept or OnCancel will be done when OnHide is called. Also note that you can prevent the popup from hiding after clicking the "Accept" button: just make the OnAccept function return "true" (or anything different from "nil" and "false", it will work just as well).

Example

As an example, consider the pre WoW 1.11.0 "Ready Check" from CT_RaidAssist, which pops up a two-button dialog box with a 30-second timeout. The CT authors implemented 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:

StaticPopupDialogs["EXAMPLE_CTRA_READY"] = {
  text = "%s has performed a ready check.  Are you ready?",
  -- YES, NO, ACCEPT, CANCEL, etc, are global WoW variables containing localized
  -- strings, and should be used wherever possible.
  button1 = YES,
  button2 = NO,
  OnAccept = function()
      CT_RA_SendReady()
  end,
  OnCancel = function (_,_, reason)
      if reason == "timeout" or reason == "clicked" then
          CT_RA_SendNotReady()
      else
          -- "override" ...?
      end;
  end,
  sound = "levelup2",
  timeout = 30,
  whileDead = true,
  hideOnEscape = true,
}

and called via

StaticPopup_Show ("EXAMPLE_CTRA_READY", CT_RA_CheckReady_Person)

(Note: the above was written before the 1.11 game patch added a builtin ready check. While ready checks no longer need to be implemented by addon authors, the example is simple enough to remain here.)