Search Unity

Resolved how to add text contained in a string list to a scroll view

Discussion in 'Scripting' started by Para00100, Jun 4, 2022.

  1. Para00100

    Para00100

    Joined:
    Apr 22, 2022
    Posts:
    48
    Hi all,

    first of all I want to say that I am very new to Unity and have just some basic knowledge in programing. So pls have mercy with me if I ask stupid question :-/

    Ok my problem:

    I have a fixed list of strings defined in a script and I would like to add it to a scroll view field via script. How can I achieve that?

    I tried to do add the strings into a textbox, where it works - this is the code for that:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5.  
    6. public class Viewing : MonoBehaviour
    7. {
    8.     public Text TextBox;
    9.     void Start()
    10.     {
    11.         List<string> items = new List<string>();
    12.         items.Add("Label 1");
    13.         items.Add("Label 2");
    14.         items.Add("Label 3");
    15.         items.Add("Label 4");
    16.         items.Add("Label 5");
    17.         items.Add("Label 6");
    18.         items.Add("Label 7");
    19.         items.Add("Label 8");
    20.         items.Add("Label 9");
    21.         items.Add("Label 10");
    22.         items.Add("Label 11");
    23.  
    24.  
    25.         foreach (var item in items)
    26.         {
    27.             //TextBox.text = TextBox.text +",\n " + item;
    28.             TextBox.text = TextBox.text + ", " + item;
    29.         }      
    30.     }
    31.  
    32.     void Update()
    33.     {  
    34.     }
    35. }
    36.  

    But I am a bit unsure how to realise it for a scrollview. Can anyone pls help me - I would be soo thankful for that. Thanks.
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    Leverage the power of the editor to make the scrollview, get all the anchoring, settings, padding, etc just right.

    Now make a single instance of whatever "line" you want inside of it.

    This lets you trivially dupe it 27 times and go "Yup, scrolls correctly."

    Go back and delete all but one of those copies.

    This copy is your "example."

    Then structure your code to read from the file and clone that single instance into however many you need.

    Attached is a fully-functional example of this kind of dynamic UI in Unity.
     

    Attached Files:

    Para00100 likes this.
  3. Para00100

    Para00100

    Joined:
    Apr 22, 2022
    Posts:
    48
    Thank you very much for the instructions and also for the examples. I will go through and hopefully can transfer and reuse it with necessary adaptions for my case :)
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    Para00100 likes this.
  5. Para00100

    Para00100

    Joined:
    Apr 22, 2022
    Posts:
    48
    Hi once again, thanks for sharing the functional example.
    I have tried to transfer it to my case but unfortunately failed and its not working :-(

    Well, I actually want to fill a scrollview with entries from a string list.
    This string list is not fixed - it is dynamically created. Actually, the app which some students an dme has to build, allows users to enter labels for each gameObject. And these gameobjats are stored in a list. And for each gameobject there will be another list containing all labels entered for the corresponding object.

    This is the code I wrote or rather an adaption of yours.

    SingleLabelTile.cs:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5.  
    6. public class SingleLabelTile : MonoBehaviour
    7. {
    8.     //[SerializeField] Text Caption;
    9.     [SerializeField] Text TextBox;
    10.  
    11.     public void SetCaptionText(string caption)
    12.     {
    13.         //Caption.text = caption;
    14.         TextBox.text = caption;
    15.     }
    16. }
    Viewing.cs (aka DynamicUIDemo):
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5.  
    6.  
    7. public class Viewing : MonoBehaviour
    8. {
    9.     private LabelHandler labelHandler;
    10.     [SerializeField]
    11.     private GameObject current_gameObject;
    12.     [SerializeField]
    13.     private List<string> lbls;  // For viewing in Inspector only
    14.  
    15.     public SingleLabelTile ExemplarTile;
    16.  
    17.     //public Text TextBox;
    18.  
    19.     SingleLabelTile MakeFreshCopyOfExampleTile()
    20.     {
    21.         // create it and simultaneously parent it to the same place in the UI
    22.         var copy = Instantiate<SingleLabelTile>(ExemplarTile, ExemplarTile.transform.parent);
    23.  
    24.         // make it visible
    25.         copy.gameObject.SetActive(true);
    26.  
    27.         return copy;
    28.     }
    29.  
    30.     void Start()
    31.     {
    32.         // turn off the example first, so it doesn't interfere
    33.         ExemplarTile.gameObject.SetActive(false);
    34.        
    35.         labelHandler = GameObject.Find("Labeling").GetComponent<LabelHandler>();
    36.         current_gameObject = labelHandler.selectedGameObject;
    37.        
    38.         GameObjectLabels gl = labelHandler.findGameObjectLabels_by_GameObject(current_gameObject);
    39.         if (gl != null)
    40.         {
    41.             lbls = gl.labels;
    42.  
    43.             //Fill dropdown with labels of the current GameObject
    44.             foreach (string item in lbls)
    45.             {
    46.                 var entry = MakeFreshCopyOfExampleTile();
    47.                 Debug.Log("DONE");
    48.                 entry.SetCaptionText(item);
    49.                 //TextBox.text = item;
    50.  
    51.             }
    52.         }
    53.     }
    54. }
    Would be very thankful, if you could point out where my mistake is. I simply cannot figure out even spending hours on it....
     
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    Um... what's your mistake?

    How to report your problem productively in the Unity3D forums:

    http://plbm.com/?p=220

    You may edit your post above.


    Here's how to troubleshoot what you have:

    You must find a way to get the information you need in order to reason about what the problem is.

    What is often happening in these cases is one of the following:

    - the code you think is executing is not actually executing at all
    - the code is executing far EARLIER or LATER than you think
    - the code is executing far LESS OFTEN than you think
    - the code is executing far MORE OFTEN than you think
    - the code is executing on another GameObject than you think it is
    - you're getting an error or warning and you haven't noticed it in the console window

    To help gain more insight into your problem, I recommend liberally sprinkling Debug.Log() statements through your code to display information in realtime.

    Doing this should help you answer these types of questions:

    - is this code even running? which parts are running? how often does it run? what order does it run in?
    - what are the values of the variables involved? Are they initialized? Are the values reasonable?
    - are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

    Knowing this information will help you reason about the behavior you are seeing.

    If your problem would benefit from in-scene or in-game visualization, Debug.DrawRay() or Debug.DrawLine() can help you visualize things like rays (used in raycasting) or distances.

    You can also call Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene manually, looking for all the parts, where they are, what scripts are on them, etc.

    You can also call GameObject.CreatePrimitive() to emplace debug-marker-ish objects in the scene at runtime.

    You could also just display various important quantities in UI Text elements to watch them change as you play the game.

    If you are running a mobile device you can also view the console output. Google for how on your particular mobile target, such as this answer or iOS: https://forum.unity.com/threads/how-to-capturing-device-logs-on-ios.529920/ or this answer for Android: https://forum.unity.com/threads/how-to-capturing-device-logs-on-android.528680/

    Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

    Here's an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

    https://forum.unity.com/threads/coroutine-missing-hint-and-error.1103197/#post-7100494
     
  7. Para00100

    Para00100

    Joined:
    Apr 22, 2022
    Posts:
    48
    I have actually two problems:
    1. the code is executing far MORE OFTEN than I want
    2. the code is executing on another GameObject than I want.

    Let me explain my scene.
    I have a scene with two cubes: "Cube1" and "Cube2".
    And there are two buttons "Add label" and "View label".
    If you click on "Add label", a panel opens, where u can enter labels during runtime for the current gameObject in focus/touched via MRTK. These labels will be stored in a string list for that gameobject.
    If you click on "View label" a panel opens, where all labels entered for the respective gameobjact will be displayed in a scrollview.

    Let's say during runtime I am entering for "Cube1", the labels "lab1" and "lab2" but for "Cube2" I do not provide any labels. Then a string list {lab1, lab2} will be stored for Cube1.
    If I click view label for Cube1, the system retrieves the labels list for the current object (Cube1) and displays each entry in the list (lab1, lab2) in the scroll view labels. All fine until now.
    If I now closes the panel and click again on the View label button, the scroll view contains now the label list twice {lab1, lab2, lab1, lab2}. If I reapeat these steps of closing panel and opening panelvia buttons, the content will be repeated - meaning ExemplarTiles are copied for every time I click on View label.
    This is my Problem 1.
    Is there maybe a way to empty teh content of the scrollview or destroy all copies of the exemplartile before filling the content?

    Problem 2 is, that if I move to Cube2 and click on View label for Cube2, the list of Cube1 ist displayed.

    This is my code - do you see where I do the mistake?

    SingleLabelTile.cs (assigned to ExemplarTile):
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5.  
    6. public class SingleLabelTile : MonoBehaviour
    7. {
    8.     [SerializeField] Text TextBox;
    9.  
    10.     public void SetText(string txt)
    11.     {
    12.         TextBox.text = txt;
    13.     }
    14. }
    Viewing.cs (similiar to your DynamicUIDemo.cs - assigned to my View Label Button object):

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5.  
    6.  
    7. public class Viewing : MonoBehaviour
    8. {
    9.     private LabelHandler labelHandler;
    10.     [SerializeField]
    11.     private GameObject current_gameObject;
    12.     [SerializeField]
    13.     private List<string> lbls;  // For viewing in Inspector only
    14.  
    15.     public SingleLabelTile ExemplarTile;
    16.  
    17.     void Start()
    18.     {
    19.         //labelHandler = GameObject.Find("Labeling").GetComponent<LabelHandler>();
    20.         //current_gameObject = labelHandler.selectedGameObject;
    21.  
    22.         //FillViewContent(current_gameObject);
    23.     }
    24.  
    25.     void Update()
    26.     {
    27.         //labelHandler = GameObject.Find("Labeling").GetComponent<LabelHandler>();
    28.         //current_gameObject = labelHandler.selectedGameObject;
    29.  
    30.         //FillViewContent(current_gameObject);
    31.     }
    32.  
    33.     SingleLabelTile MakeFreshCopyOfExampleTile()
    34.     {
    35.         // create it and simultaneously parent it to the same place in the UI
    36.         var copy = Instantiate<SingleLabelTile>(ExemplarTile, ExemplarTile.transform.parent);
    37.  
    38.         // make it visible
    39.         copy.gameObject.SetActive(true);
    40.  
    41.         return copy;
    42.     }
    43.  
    44.     //public void FillViewContent(GameObject go)
    45.     public void FillViewContent()
    46.     {
    47.         // turn off the example first, so it doesn't interfere
    48.         ExemplarTile.gameObject.SetActive(false);
    49.  
    50.         labelHandler = GameObject.Find("Labeling").GetComponent<LabelHandler>();
    51.         current_gameObject = labelHandler.selectedGameObject;
    52.         Debug.Log("This is current object " + current_gameObject.name);
    53.  
    54.         GameObjectLabels gl = labelHandler.findGameObjectLabels_by_GameObject(current_gameObject);
    55.         if (gl != null)
    56.         {
    57.             Debug.Log("List is not empty");
    58.                 lbls = gl.labels;
    59.  
    60.             //Fill dropdown with labels of the current GameObject
    61.             foreach (string item in lbls)
    62.             {
    63.                 var entry = MakeFreshCopyOfExampleTile();
    64.                 Debug.Log("Copy created");
    65.                 entry.SetText(item);
    66.             }
    67.         }
    68.         if (gl == null)
    69.             Debug.Log("List is empty - NULL");
    70.     }
    71. }
    72.  
    73.  
    74.    

    Would be so great if you could help - I am totally lost :-(
     
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    What makes sure this find the correct thing?

    If you're not sure, prove that it IS finding the correct thing.

    These are the sorts of things you are going to have to find out from your running code. Get to it with Debug.Log()

    Staring at a wall of dead code in an editor may not be the answer.
     
  9. Para00100

    Para00100

    Joined:
    Apr 22, 2022
    Posts:
    48


    oh yes, I did check it.
    I added afer lbls = gl.labels (line 58) the following code:

    foreach (string item in lbls)
    {
    Debug.Log("Label in list:" + item);
    }

    And it was outputting me the correct labels.

    The problem is for every time I click on the view button, the intantiated copies from before remain availble and new ones added. by this i have the labels displayed in the scrollview many times (actually if i clicked x times on view button, then every label is displayed x times in scroll view)
    Is there a way to remove all copies?
     
  10. Para00100

    Para00100

    Joined:
    Apr 22, 2022
    Posts:
    48
    Additionally to the instantiation, I have now added that all instantiated clones get also the tag "Clone".
    And this works.

    Furthermore I created the following function for destroying all instantiated clones with tag "Clone", when the user closes the panel:

    Code (CSharp):
    1.  public void DestroyGameObjects()
    2.     {
    3.         string tag = "Clone";
    4.         GameObject [] clones = GameObject.FindGameObjectsWithTag(tag);
    5.  
    6.         if (clones == null)
    7.         { Debug.Log("Clones list empty"); }
    8.  
    9.  
    10.         if (clones != null)
    11.         { Debug.Log("Clones list NOT empty"); }
    12.  
    13.         foreach (GameObject clone in clones)
    14.         {
    15.             Destroy(clone);
    16.             Debug.Log("Deleted");
    17.         }
    18.     }
    During runtime I get also the log that "Clones list is NOT empty" - so GameObject [] clones = GameObject.FindGameObjectsWithTag(tag) works, too.

    However none of the clone objects are deleted. They all remain.
    Does anyone know, what I have missed in my code, so that the deletion is not properly working?

    I believe that if I solve this problem (deleting all instantiated clones when closing the panel) all my problems with the code are solved and everything will work as expected.

    I would be very grateful if anyone can suggest how to fix the deletion problem... thanks
     
  11. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    You should go read the
    GameObject.FindGameObjectsWithTag(tag);
    docs for explicitly what it says happens when there are zero items.

    And then you should adjust your
    if
    checks appropriately once you understand the API.
     
    Para00100 likes this.
  12. Para00100

    Para00100

    Joined:
    Apr 22, 2022
    Posts:
    48
    Oh gosh - thank you.

    I adapted the if checks to:
    if (clones.Length == 0)
    { Debug.Log("No gameobjects with that tag");
    }

    if (clones.Length != 0)
    { Debug.Log("There are gameobjects with that tag");

    And result: No gameobjects with that tag were found.
    Therefore nothing is deleted.

    I will try to figure out, why GameObject.FindGameObjectsWithTag(tag) is not finding those instantiated objects as they are actually there (see pic) :-(.
     

    Attached Files:

  13. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    Make sure you don't have this script in your scene twice: Destroy() doesn't happen until end of frame, so if you do it once, then do it again the same frame, they'll still be there.
     
  14. Para00100

    Para00100

    Joined:
    Apr 22, 2022
    Posts:
    48
    Actually the script is assigned in two places:
    - ViewLabelButton in order to fill the scrollview on the panel opened with the entries from the string list.
    - ViewLabel_CloseButton, where I have defined in the OnClick section, that once Close button on the viea label panel is clicked, the function DestroyGame() should be executed.

    Would this result then in a problem?
     
  15. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    Let me rephrase:

    if this code runs twice in one frame, BOTH times will find and destroy all the same objects. This is expected.

    If it runs the NEXT frame and they're still around, then you have a bug.
     
    Para00100 likes this.