WoW:Creating simple pop-up dialog boxes: Difference between revisions

({{UIHowTo}}, slight lead-in tweak)
Line 20: Line 20:
   end,
   end,
   timeout = 0,
   timeout = 0,
   whileDead = 1,
   whileDead = true,
   hideOnEscape = 1
   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 1 if this dialog can be shown while the player is a ghost.  You probably want to do this.
* ''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 1 if hitting the Escape key should be treated like clicking button2.  You probably want to do this.
* ''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 1. The EditBox get the name "$parentEditBox", relative to the Popup. To get and set the value of the EditBox use the following code:
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)
     getglobal(this:GetName().."EditBox"):SetText("Some data")
     self.editBox:SetText("Some text goes here")
    -- notice: "this" is the StaticPopup, normally its "StaticPopup1"
  end,
  end,
  OnAccept = function()
  OnAccept = function (self, data, data2)
     getglobal(this:GetParent():GetName().."EditBox"):GetText()
     local text = self.editBox:GetText()
     -- do whatever you want with it
     -- do whatever you want with it
    -- notice: "this" is the left button, normally its "StaticPopup1Button1",
    --        so you need the GetParent() call.
  end,
  end,
  hasEditBox = 1,
  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 1 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.
* ''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 1 to avoid that behavior.
* ''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 1 to make your popup go away if any other popup is displayed.
* ''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 = 1,
   whileDead = true,
   hideOnEscape = 1
   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:


Write your local function like this:
  OnAccept = function (self, data, data2)
      DoSomethingWith(self.data)
  end


    OnAccept = function(self)
And make the call like this:
                  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


    varName = "Some value"   -- This is the data you want to use in OnAccept
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.
    local dialog = StaticPopup_Show("YOUR_POPUP")    -- dialog contains the frame object
    if(dialog) then
        dialog.data = varName    -- set the frame's data to the value you want
    end


If you'll notice, you are actually setting the frame's data (which is passed as the first argument to the local function) ''after'' the user has clicked the button. I'm not exactly sure how this mechanism works, my initial guess is that the popup functions are run in a separate thread to allow for this kind of manipulation.
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.


Added by [[user:egingell]]: The above works because script execution is not halted by the static popup.
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.  :-)
Anonymous user