Page 2 of 3 FirstFirst
  1. #21
    Make sure you :SetAutoFocus(false) on EditBoxes you create. It defaults to true (which is rarely the desired behavior).

    Also, can you please paste the entire content of your file in one [code][/code] block? Your current pastes still don't show any :RegisterEvent("CHAT_MSG_ADDON") calls for your frame.
    UI & AddOns expert | Interface & Macros moderator - My work

  2. #22
    Quote Originally Posted by Treeston View Post
    Your current pastes still don't show any :RegisterEvent("CHAT_MSG_ADDON") calls for your frame.
    It's after registering the prefix. Here is the whole file:

    upper_limit = 100 -- Upper limit to random number generation for channel prefix generation
    SLASH_COLLABWOW1 = "/clw"
    SLASH_COLLABWOW2 = "/collabwow"
    SlashCmdList["COLLABWOW"] = function (chosen_name)
      print("Slash command used")
      --testLibraryTable(chosen_name) TODO
    -- Creates a name for the communication channel, based on the name and kind of group (raid, guild, ...?)
    -- and a random numeric sufix 
    function GenerateChannelName(name, grouptype)
      local sufix = tostring(math.random(upper_limit)) -- upper_limit is a global variable
      return grouptype .. name .. sufix
    ------------------------------------ Scripts ---------------------------------------------
    -- Run each time the screen is drawn by the game engine (
    function ColLabWoW_Update( )
    --	print("Updating content")
    -- Run whenever an event fires for which the frame is registered (
    function ColLabWoW_OnEvent(frame, event, ...)
      print "Event caught"
    	if (event == "CHAT_MSG_ADDON") then
    		-- Arguments: "prefix", "message", "channel", "sender"
      local prefix_received, message, channel, sender = ...
    		if (prefix_received == addon_prefix) then 
          local time = tonumber(message:match("time: (%d+)"))
          if time then
            print("Timestamp received: " + curtime)
    			elseif (message == "Test Message") then
    				print ("Test message received")
    ------------------------------------ Main execution --------------------------------------
    -- Get timestamp to know who connected first. The one with the longest connection
    -- (lowest timestamp) will have the master copy of the data. The rest will get copies
    -- from there
    curtime, uncertain = LibStub("LibSyncTime-1.0"):synctime()
    print ("Current time is " .. curtime)
    ColLabWoW = CreateFrame("Frame", "ColLabWoW", UIParent)
    ColLabWoW:SetPoint("CENTER", UIParent, "CENTER")
      edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
      tile = true, tileSize = 16, edgeSize = 16,
      insets = { left = 3, right = 3, top = 5, bottom = 3 }
    -- Positionate window to the left, make it movable always and visible when the character is dead
    UIPanelWindows["ColLabWoW"] = {
      area = "left",
      pushable = 1,
      whileDead = 1,
    ColLabWoW:SetScript("OnEvent", ColLabWoW_OnEvent)
    ColLabWoW:SetScript("OnUpdate", ColLabWoW_Update)
      -- Create text boxes
    ColLabWoW.texboxes = {}
    --for idx=1, 10 do 
    --    local textbox = CreateFrame("EditBox", "ColLabWoW_EditBox" .. idx, ColLabWoW)
      --    ColLabWoW.texboxes[idx] = textbox
          -- Anchor textboxes depending on position: the first one goes to the left,
          -- the rest go to the right of the previous one
      --    if idx == 1 then
      --      textbox:SetPoint("TOPLEFT", 40, -73)
      --    else
      --      textbox:SetPoint("TOPLEFT", ColLabWoW.texboxes[idx-1],"TOPRIGHT", 12, 0)
      --    end
    --communication(ColLabWoW) (The following code was in this function but it was not working there)
     -- Get group name
      local groupName, grouptype = "MyGuild", "Guild" -- TODO getGroupInfo()
      addon_prefix = GenerateChannelName(groupName, grouptype)
      print (addon_prefix .. " is the generated prefix")
      local success = RegisterAddonMessagePrefix(addon_prefix)
      if (success) then
        print ("Prefix registered")
        SendAddonMessage(addon_prefix, "Test Message", "WHISPER", (UnitName("player")))
        --SendAddonMessage(addon_prefix, "time: " .. tostring(curtime))
        print ("Error: the prefix registration failed")
    The editboxes are not displayed yet but yes, I'll make sure to add that and other settings (like allowing more than one line) later

  3. #23
    First thing I noticed is three really generically named globals - "upper_limit", "addon_prefix" and "GenerateChannelName". Use locals.

    Also, I'm not quite sure if SendAddonMessage on load will work, as I'm not certain if the messaging system is already initialized at that point.
    Try registering for ADDON_LOADED and check for arg1 == your addon folder's name. Remember to unregister ADDON_LOADED afterwards to save some cycles.

    PS: Your edit boxes wouldn't be able to display, as they currently have only a single point and no width/height set.
    PPS: If you change addon_prefix to be a local, make sure it's defined (doesn't need to be populated) before you define the _OnEvent. Functions only know about parent scope locals if they've already been defined at the time the function is defined.
    UI & AddOns expert | Interface & Macros moderator - My work

  4. #24
    That it is too soon to use it looks like the reason why it wasn't working. I've changed it and it is working now, thank you!! Now to continue with the protocol

  5. #25
    So the communication is working fine and progressing and the same for the interface. I'm not sure though of which template to use and can't find a list of them. I have checked this ( ) but it's difficult to follow and only uses the "usual" templates. Is there any list that can be checked?

    As for the logs, it seems easy to save the values I want to store using SavedVariables. However, it looks like it is not possible to upload these to my server without having to ask the player to do so. Do you know if there is any recommended approach to this situation? Thanks!

  6. #26
    The default UI tends to use InputBoxTemplate for its edit boxes. Note that this template requires the frame to have a name to work properly.

    As for getting data to your server, there's no way to communicate to anything outside the WoW client. Manual upload is the way to go, sadly (unless you want to write an uploader tool).
    UI & AddOns expert | Interface & Macros moderator - My work

  7. #27
    Quote Originally Posted by Treeston View Post
    The default UI tends to use InputBoxTemplate for its edit boxes. Note that this template requires the frame to have a name to work properly.
    I'm creating a frame to contain this edit boxes and tried DialogBoxFrame and BaseBasicFrameTemplate. I have also other frames, such as one with a list of the players participating. For these frames, I wanted to tried different styles (as I'm afraid of adapting one style to my needs and then discovering there was a better one. The design of the addon may change, so it is not that unlikely); or just go with the Basic one, but just by reading the XML file I don't know how to set the title in Lua, for instance.

    Quote Originally Posted by Treeston View Post
    As for getting data to your server, there's no way to communicate to anything outside the WoW client. Manual upload is the way to go, sadly (unless you want to write an uploader tool).
    By uploader tool I understand something out of the addon, right?

  8. #28
    Yeah, it'd have to be something outside of the client that detects (or asks the user for) WoW's install location, then auto-uploads the SVs whenever the client exits or something.

    Also, here's a snippet that I use in one of my projects for creating frames. Note how the frame is anchored to the header (counter-intuitive at first) and the header is the movable part.

    GDKPd.status = CreateFrame("Frame", "GDKPd_Status", UIParent)
    local status = GDKPd.status
    status:SetSize(200, 90)
    status.header = CreateFrame("Button", nil, status)
    status.header.text = status.header:CreateFontString()
    status.header.text:SetFont("Fonts\\FRIZQT__.TTF", 8, "")
    status.header:SetScript("OnMouseDown", function(self)
    status.header:SetScript("OnMouseUp", function(self)
    	GDKPd.opt.statuspoint.point, _, GDKPd.opt.statuspoint.relative, GDKPd.opt.statuspoint.x, GDKPd.opt.statuspoint.y = self:GetPoint()
    status:SetPoint("TOP", status.header, "TOP", 0, -6)
    status:SetScript("OnShow", function(self)
    You'll probably want to adjust sizes and stuff if your frame is larger - this is for quite a small frame (200x90).
    UI & AddOns expert | Interface & Macros moderator - My work

  9. #29
    Great, thank you! I'll use that mixed with my code to get a final template.
    As for the uploading, I guess I'll have to ask the player to do it (sigh)

  10. #30
    So I'm working on the UI now, trying to show the editboxes (the header is not being displayed for some reason but I decided to take a break on that and look at it after this). I get an error message when I try to set the OnEnterPressed event, being my intention to store the text of the editbox when it is modified. The error message is:
    "2x ....lua:260: Usage: Option1:SetScript("type", function)"

    Here's a snippet of that part of the code:

    function editBox_OnEnterPressed ( option )
      print ("Stored text from editbox: " .. option:GetText())
    function createMainWindow( options )
      local option_boxes = {}
      for option_number = 1, #(options) do
        option = CreateFrame ("EditBox", "Option" .. option_number, DiscussionContent, "InputBoxTemplate")
        option_boxes[option_number] = option 
        if (option_number == 1) then
          option:SetPoint("TOPLEFT", DiscussionContent, "TOPLEFT", 7, 30)
        else -- TODO set up differently if the row ends
          option:SetPoint("TOPLEFT", option_boxes[option_number-1], "TOPRIGHT", 7, 30)    
        option:SetTextInsets(5, 5, 2, 2)
        option:SetScript("OnEnterPressed", editBox_OnEnterPressed(option))
        option_number = option_number + 1

  11. #31
    You need to differ between a function reference and a function call.

    Function call: editBox_OnEnterPressed(option). This passes the value of "option" to editBox_OnEnterPressed, then evaluates to the result.
    Function reference: editBox_OnEnterPressed. This is a pointer to the function.

    You should be able to figure out what went wrong from there.

    PS: The frame itself is always passed as arg1 to OnEvent.
    UI & AddOns expert | Interface & Macros moderator - My work

  12. #32
    I see, I was mixing both concepts. Fixed now, thank you for the explanation!

  13. #33
    I'm testing the communication system and I probably don't understand well how variables work in Lua. I have a table (conflict.conflict_data) containing several "sub-tables (options, stakeholders_names, etc.)". Everything works fine when I send (sendData function) the first table (options) but not for the rest. I have compared the reference of the table when it is being filled with the received data (On_Event function) to the reference of the table that is read when creating the edit boxes (createEditboxes) and it is the same. It gets the elements of "options" but it says the rest of the tables are empty. Can you throw some light on this? Thank you very much!

    Here is the code I think is significant for the problem:

    function sendData( prefix, recipient )
      SendAddonMessage(prefix, "Starting data sending", "WHISPER", recipient)
      local conflict = conflicts_active[prefix]
       -- TODO split the description/title string  if it is too long and send it in several messages
      SendAddonMessage(prefix, "Title=".. conflict.conflict_data["description"], "WHISPER", recipient)
      -- Send the options information
      for option_number=1, (#conflict.conflict_data["options"]) do
        -- TODO split the string option if it is too long and send in different messages
        SendAddonMessage(prefix, "Option".. option_number .."="..(conflict.conflict_data["options"][option_number]), "WHISPER", recipient)
      -- Send the stakeholders information (also their interests and the values of those)
      for stakeholder_number=1, (#conflict.conflict_data["stakeholders_names"]) do
        -- TODO split the string if it is too long and send in different messages
        SendAddonMessage(prefix, "Stakeholder".. stakeholder_number .."="..(conflict.conflict_data["stakeholders_names"][stakeholder_number]), "WHISPER", recipient)
        for interest_number, interestlist in pairs (conflict.conflict_data["stakeholders_interests_values"][stakeholder_number]) do
         SendAddonMessage(prefix, "Interest" .. interest_number .. "of" .. stakeholder_number .. "=" .. conflict.conflict_data["stakeholders_interests_names"][stakeholder_number][interest_number], "WHISPER", recipient)
          -- Send row of values for each interest
          for index, index_value in pairs(interestlist) do
            SendAddonMessage(prefix, "Index".. index .. "of" .. interest_number .. "of" .. stakeholder_number .. "=" .. index_value, "WHISPER", recipient) 
      SendAddonMessage(prefix, "Data sent", "WHISPER", recipient)
    function On_Event (frame, event, ...) 
     local option_number, option_value = message:match("Option(%d)=(.+)")
            if (option_number ~= nil and option_value ~= nil and sender == master) then
              local optionnumber = tonumber(option_number)
              conflicts_active_testing[prefix].conflict_data["options"][optionnumber] = option_value
              local stakeholder_number, stakeholder_name = message:match("Stakeholder(%d)=(.+)")
              if (stakeholder_number ~= nil and stakeholder_name ~= nil and sender == master) then
                conflicts_active_testing[prefix].conflict_data["stakeholders_names"][stakeholder_number] = stakeholder_name
                conflicts_active_testing[prefix].conflict_data["stakeholders_interests_values"][stakeholder_number] = {}
                conflicts_active_testing[prefix].conflict_data["stakeholders_interests_names"][stakeholder_number] = {}
                local interest_number, stakeholder_number, interest_name = message:match("Interest(%d)of(%d)=(.+)")
                if (interest_number ~= nil and stakeholder_number ~= nil and interest_name ~= nil and sender == master) then
                  conflicts_active_testing[prefix].conflict_data["stakeholders_interests_names"][stakeholder_number][interest_number] = interest_name
                  conflicts_active_testing[prefix].conflict_data["stakeholders_interests_values"][stakeholder_number][interest_number] = {}
                  local index_number, interest_number, stakeholder_number, index_value = message:match("Index(%d)of(%d)of(%d)=(.+)")
                  if (index_number ~= nil and interest_number ~= nil and stakeholder_number ~= nil and index_value ~= nil and sender == master) then
                    conflicts_active_testing[prefix].conflict_data["stakeholders_interests_values"][stakeholder_number][interest_number][index_number] = index_value
    function createEditBoxes( prefix, discussion )
      local conflict = conflicts_active_testing[prefix]
      -- Create options/solutions bar
      local option_boxes = {}
      local posnegcounter = 1
      for option_number = 1, #(conflict.conflict_data["options"]) do
        local option = CreateFrame ("EditBox", "Option" .. option_number .. "of" .. prefix, discussion.scrollframe.content, "InputBoxTemplate")
        option_boxes[option_number] = option 
        if (option_number == 1) then
          option:SetPoint("LEFT", discussion.scrollframe.content, "TOPLEFT", 175*2,-40)--discussion.header:GetHeight()+20) -- 175 to leave space horizontally for the stakeholders column
          option:SetPoint("LEFT", option_boxes[option_number-1], "RIGHT", 15, 0)    
        option:SetTextInsets(5, 5, 2, 2)
        option:SetScript("OnEnterPressed", editBox_OnEnterPressed)
      end -- End of creation of options boxes
      conflict.conflict_ui["options"] = option_boxes
      -- Create stakeholders column and interests column, plus rows of consequences
      local stakeholder_boxes = {}
      local interests_boxes = {}
      local index_boxes = {}
      local index = 1
      print ("Creating editboxes. #stakeholders_names = " .. tostring(conflict.conflict_data["stakeholders_names"]))
      for stakeholder_number=1, #(conflict.conflict_data["stakeholders_names"]) do
        local stakeholder = CreateFrame ("EditBox", "Stakeholder" .. stakeholder_number.. "of" .. prefix, discussion.scrollframe.content, "InputBoxTemplate")
        -- Height of this box depends on how many interests that stakeholder has
        stakeholder:SetHeight(55 * (#(conflict.conflict_data["stakeholders_interests_names"][stakeholder_number])))
        stakeholder_boxes[stakeholder_number] = stakeholder 
        if (stakeholder_number == 1) then
          stakeholder:SetPoint("TOPRIGHT", option_boxes[1], "LEFT", -175, -50) -- --175 to leave room for the interests column
          stakeholder:SetPoint("TOP", stakeholder_boxes[stakeholder_number-1], "BOTTOM", 0, 0)    
        stakeholder:SetTextInsets(5, 5, 2, 2)
        stakeholder:SetScript("OnEnterPressed", editBox_OnEnterPressed)
        -- Create boxes with the names of the interests of that stakeholder (and their values)
        for key, interestlist in pairs (conflict.conflict_data["stakeholders_interests_values"][stakeholder_number]) do
          local interestbox = CreateFrame ("EditBox", "Interest"..stakeholder_number .. "n" .. key.. "of" .. prefix, discussion.scrollframe.content, "InputBoxTemplate")
          interests_boxes[key] = interestbox 
          if (key == 1) then
            interestbox:SetPoint("LEFT", stakeholder, "RIGHT", 0, 0)
            interestbox:SetPoint("TOP", interests_boxes[key-1], "BOTTOM", 0, 0)    
          interestbox:SetTextInsets(5, 5, 2, 2)
          interestbox:SetScript("OnEnterPressed", editBox_OnEnterPressed)
          -- Create row of values for each interest
          for key2, value2 in pairs(interestlist) do
            local indexbox = CreateFrame ("EditBox", "Index".. index.. "of" .. prefix, discussion.scrollframe.content, "InputBoxTemplate")
            index_boxes[index] = indexbox 
            if (key2 == 1) then
              indexbox:SetPoint("LEFT", interestbox, "RIGHT", 15, 0)
            elseif (key2 %2 == 0) then
              indexbox:SetPoint("LEFT", index_boxes[index-1], "RIGHT", 3, 0) 
              indexbox:SetPoint("LEFT", index_boxes[index-1], "RIGHT", 15, 0)
            index = index + 1
            indexbox:SetTextInsets(5, 5, 2, 2)
            indexbox:SetScript("OnEnterPressed", editBox_OnEnterPressed)
      end -- Create stakeholder boxes
      conflict.conflict_ui["stakeholders"] = stakeholder_boxes
      conflict.conflict_ui["interests"] = interests_boxes
      conflict.conflict_ui["index"] = index_boxes
      return conflict

  14. #34
    If I understood the issue correctly, you'll want to verify the reference of against the one that is created as well as check the values in that table after creation.

    PS: Also, "Index".. index .. "of" .. interest_number .. "of" .. stakeholder_number .. "=" .. index_value is highly inefficient (creates a total of 6 strings that will not be used in addition to the one that will be used. Consider using ("Index%dof%dof%d=%s"):format(index,interest_number,stakeholder_number,index_value").

    PPS: Also, I'm personally not fond of using human-readable format in inter-addon communication. The message above could simply be shortened to "ind%d,%d,%d,%s", thus greatly increasing the number of characters available for sending actual data.
    In your CHAT_MSG_ADDON, you could then match the first three characters out of every incoming message and decide what to do with the rest from there. Something like this:

        if event == "CHAT_MSG_ADDON" then
            local prefix,message = ...
            local conflict = conflicts_active[prefix]
            if conflict then
                local type,data = message:match("^(...)(.*)$")
                if type == "ini" then -- "Starting data sending" ("sta" is "Stakeholder")
                elseif type == "tit" then -- "Title"
                elseif type == "opt" then -- "Option"
                elseif type == "sta" then -- "Stakeholder"
                elseif type == "int" then -- "Interest"
                elseif type == "ind" then -- "Index"
                elseif type == "end" then -- "Data sent"
                    print(("Unknown data type received for prefix '%s': '%s' (Data: '%s')"):format(prefix,type,data))
    Last edited by Treeston; 2013-05-30 at 10:03 AM.
    UI & AddOns expert | Interface & Macros moderator - My work

  15. #35
    I think I've checked that but, honestly, the code is too messy now to be 100% sure. I wanted to distribute the functions in several files (the UI functions in one, the "raw data" in other, etc.) but I can't find an explanation of how to do this. Because it is not the same process as creating a library... or is it?

  16. #36
    Use the addon table for that, which is passed as vararg2 to each file's body. This table is the same for every file in a single addon. Simply add something along the lines of local folder, addontab = ... at the top of every file. addontab will point to the same table in every file - you can then use it to share functions. Just make sure to keep load order in mind.

    PS: If you post the function that creates/modifies the stakeholder_names table, I can give it a look.
    UI & AddOns expert | Interface & Macros moderator - My work

  17. #37
    Great, thank you! I've been refactoring the code including also your suggestions. That was extremely helpful, as then the code was way easier (at least for me ) to understand and follow. So after re-checking all the flow, I found the error. In:

      elseif (type == "sta" and data ~= nil) then -- Stakeholder
        local stakeholderNumber, stakeholder_name = data:match("(%d)=(.+)")
        if (stakeholderNumber ~= nil and stakeholder_name ~= nil and sender == master) then
          match = true
          local stakeholder_number = tonumber(stakeholderNumber)
          addon.conflicts_active_testing[prefix].conflict_data["stakeholders_names"][stakeholder_number] = stakeholder_name
          addon.conflicts_active_testing[prefix].conflict_data["stakeholders_interests_values"][stakeholder_number] = {}
          addon.conflicts_active_testing[prefix].conflict_data["stakeholders_interests_names"][stakeholder_number] = {}
    you can see that I have added a statement to change the string from the pattern matching to a number. I was using it directly and that was why later with #list I got 0 elements. There were three stored in "1", "2" and "3" instead of in 1,2,3.

    Again, thank you very much for all the help!!

  18. #38
    Great to hear you figured it out.
    UI & AddOns expert | Interface & Macros moderator - My work

  19. #39
    I am now finished with the edit boxes and have stored all that text in my data structure. The next step is to show these texts as buttons instead of edit boxes. That is, in the previous step you could set which text was going to be displayed on the table, whereas now you will get several buttons with that text and you should be able to click and select some of them.

    I thought of using CheckButton, as it would allow the player to see which ones he/she has already selected. But so far it doesn't fit my needs, as I get a check box plus text, when what I want is a button displaying text that can be selected (and graphically stays "selected" after that). The player should be able to undo the selection as well.

    I have tried UICheckButtonTemplate, InterfaceOptionsCheckButtonTemplate, ActionBarButtonTemplate and ChatConfigCheckButtonTemplate (though not in depth). Do you have any suggestions on this? Thank you!

  20. #40
    What about something like
        if then
   = false
            self.normalTexture = self:GetNormalTexture()
   = true
    UI & AddOns expert | Interface & Macros moderator - My work

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts