Search Unity

UI Buttons - Button Choice with Keyboard - InvalidCastException: Specified cast is not valid in Unit

Discussion in 'Scripting' started by VasyaProgrammist, May 3, 2020.

  1. VasyaProgrammist

    VasyaProgrammist

    Joined:
    Oct 6, 2019
    Posts:
    4
    I am working with UI Button choice with keyboard by keys "Up Arrow", "Down Arrow", "Enter" etc.
    I must get from the Canvas prefab all children, and get from them Buttons.
    My code:


    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using UnityEngine.EventSystems;
    4. using System.Collections.Generic;
    5.  
    6. public class ChoiceButtonv4 : MonoBehaviour {
    7.     [SerializeField] public GameObject canvasprefab;
    8.  
    9.     private List<Button> button_array = new List<Button>();
    10.     private List<Object> button_array_canvas = new List<Object>();
    11.     private List<Button> button_array_btns = new List<Button>();
    12.     private List<Button> btn_rry = new List<Button>();
    13.     private GameObject canvas = null;
    14.     private EventSystem eventSystem;
    15.     private Object ch;
    16.     private string str = "Buttons in canvas:\n";
    17.     private int count;
    18.     private int button_pressed = 0;
    19.  
    20.     private void Start() {
    21.         if (canvas == null) {
    22.             canvas = Instantiate(canvasprefab) as GameObject;
    23.         }
    24.         foreach (Transform child in canvas.transform) {
    25.             Button[] _array_child = FindObjectsOfType<Button>();
    26.             for (int i = 0; i < _array_child.Length; i++) {
    27.                 Object ch = _array_child[i];
    28.                 if (child.name == ch.name) {
    29.                     button_array_canvas.Add(child);
    30.                 }
    31.             }
    32.         }
    33.         count = 0;
    34.         foreach (Object obj in button_array_canvas) {
    35.             Button bbj = (Button)obj;
    36.             count++;
    37.             if (count % 2 == 0) {
    38.                 button_array.Add(bbj);
    39.             }
    40.         }
    41.         foreach (Object nm in button_array) {
    42.             str += nm.name + "\n";
    43.         }
    44.     }
    45.     private void Update() {
    46.         if (Input.GetKeyDown(KeyCode.DownArrow)) {
    47.             int name1 = button_array.Count % (button_pressed + 1);
    48.             button_array[name1].Select();
    49.             Debug.Log("Selected button: " + button_array[name1].name);
    50.         }
    51.         if (Input.GetKeyDown(KeyCode.UpArrow))
    52.         {
    53.             int name2 = button_array.Count % (button_pressed - 1);
    54.             button_array[name2].Select();
    55.             Debug.Log("Selected button: " + button_array[name2].name);
    56.         }
    57.     }
    58. }
    59.  
    Error was in 35th line:

    InvalidCastException: Specified cast is not valid.
    ChoiceButtonv4.Start () (at Assets/ChoiceButtonv4.cs:35)


    So please, can somebody say to me, how do I get my code to the end, how to fix the error.
    I already tried with UIElements button, but didn't work. Sorry for my bad English, I'm Russian.
     
    Last edited: May 3, 2020
  2. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Just like the exception states, the cast you're trying to perform is invalid.

    Check these lines:
    Code (csharp):
    1. foreach (Transform child in canvas.transform) // line 24, "child" is of type Transform
    2. // ...
    3. button_array_canvas.Add(child); // line 29: adds 'Child', which is a Transform
    4. // ...
    5. Button bbj = (Button)obj; // line 35: if anything was added by line 29, it attempts to cast a Transform to a Button, that doesn't work
    6.  
    Note that there is potential for multiple bugs to occur.
    You find all buttons in the active scenes, which is probably why you need the additional name checks? Not sure if you want to constraint that to the canvas, but that's up to you, we can't help with that.

    Next, there's probably no need to store the buttons that you've found in a list of Objects, just in order to cast them back to Buttons? If the only purpose is to find the buttons that are in the hierarchy of the canvas, see below.

    Also, you probably want to avoid the comparison by name. what if the object happens to have the same name, but no button attached? You'd still add it. Names are not reliable. This is prone to errors.

    I think all of that you could be solved if you call GetComponentsInChildren<Button>() - optionally with an argument indicating whether inactive components shall be returned as well - on the parent that you want to get all buttons for. There's no need to then check which buttons are in the canvas hierarchy (which you apparently try to do here) and there's no need for all the loops, no casts, no name comparisons required.
     
    VasyaProgrammist likes this.
  3. VasyaProgrammist

    VasyaProgrammist

    Joined:
    Oct 6, 2019
    Posts:
    4
    Thanks, Suddoha!

    Yes, I wanted to get from the Canvas prefab all buttons in its hierarchy, get them to the array and then make them be selected by arrows on the keyboard.
    Also, I made my program to search not Buttons, but all Selectable objects. There's no big changes.

    But, when I were remaking my program, it gone wrong.


    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using System.Collections.Generic;
    4.  
    5. public class ChoiceButtonv5 : MonoBehaviour {
    6.     [SerializeField] public GameObject canvasprefab;
    7.     private GameObject canvas;
    8.     public List<Selectable> button_array = new List<Selectable>();
    9.  
    10.     private void Start() {
    11.         canvas = GameObject.Find(canvasprefab.name);
    12.         if (canvas == null) {
    13.             canvas = Instantiate(canvasprefab) as GameObject;
    14.         }
    15.         button_array = canvas.GetComponentsInChildren<Selectable>();
    16.     }
    17.  
    18.     private void Update() {
    19.         if (Input.GetKeyDown(KeyCode.DownArrow)) {
    20.             //
    21.         }
    22.         if (Input.GetKeyDown(KeyCode.UpArrow)) {
    23.             //
    24.         }
    25.     }
    26. }
    The error is in 14th line:
    Error CS0029: Cannot implicitly convert the type "UnityEngine.UI.Selectable[]" into "System.Collections.Generic.List<UnityEngine.UI.Selectable>".

    So, how to convert this types? Thanks again.


     
  4. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    You cannot just assign an array to a list. Create a new list using the constructor that takes an IEnumerable<Selectable>, if you really need it to be a list.

    Code (csharp):
    1. button_array = new List<Selectable>(canvas.GetComponentsInChildren<Selectable>());
    or create it and use AddRange, which also takes an IEnumerable<Selectable> in your case
    Code (csharp):
    1.  button_array = new List<Selectable>();
    2. button_array.AddRange(canvas.GetComponentsInChildren<Selectable>())
    Code may contain errors, as I've quickly written it in the webbrowser.

    Note that you may want to rename the array field, as it's no longer just for buttons and lists != arrays.
     
    Last edited: May 4, 2020
    VasyaProgrammist likes this.
  5. VasyaProgrammist

    VasyaProgrammist

    Joined:
    Oct 6, 2019
    Posts:
    4
    Oh my god. What have I do without you! I had no knowledge about arrays.
    I finally made this program works. Thank you, Suddoha!

    So, this is my completed code, to anyone, who will have the same problem, everything works:

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEngine.UI;
    4. using System.Collections.Generic;
    5. using System;
    6.  
    7. public class ChoiceButtonv4 : MonoBehaviour {
    8.  
    9.     [SerializeField] public GameObject canvasprefab;
    10.  
    11.     private List<Selectable> button_array = new List<Selectable>();
    12.     private GameObject canvas;
    13.     private string debug_str;
    14.     private int debug_count = 0;
    15.     private int slc_pressed = 0;
    16.  
    17.     private void Start() {
    18.         canvas = GameObject.Find(canvasprefab.name);
    19.         if (canvas == null) {
    20.             canvas = Instantiate(canvasprefab) as GameObject;
    21.         }
    22.         button_array.AddRange(canvas.GetComponentsInChildren<Selectable>());
    23.         foreach (Selectable child in button_array) {
    24.             debug_count++;
    25.             debug_str += debug_count + ". "+child.name+"\n";
    26.         }
    27.         debug_str += "Total Selectable objects: " + debug_count;
    28.         Debug.Log(debug_str);
    29.     }
    30.     private void Update() {
    31.         if (Input.GetKeyDown(KeyCode.DownArrow)) {
    32.             try {
    33.                 slc_pressed++;
    34.                 button_array[slc_pressed].Select();
    35.                 Debug.Log("Selected Selectable: " + button_array[slc_pressed].name);
    36.             } catch (ArgumentOutOfRangeException) {
    37.                 slc_pressed = 0;
    38.                 button_array[slc_pressed].Select();
    39.                 Debug.Log("Selected Selectable: " + button_array[slc_pressed].name);
    40.             }
    41.         } if (Input.GetKeyDown(KeyCode.UpArrow)) {
    42.             try {
    43.                 slc_pressed--;
    44.             button_array[slc_pressed].Select();
    45.             Debug.Log("Selected Selectable: " + button_array[slc_pressed].name);
    46.             } catch (ArgumentOutOfRangeException) {
    47.                 slc_pressed = button_array.Count-1;
    48.                 button_array[slc_pressed].Select();
    49.                 Debug.Log("Selected Selectable: " + button_array[slc_pressed].name);
    50.             }
    51.         }
    52.     }
    53. }
    54.  
     
    Last edited: May 7, 2020
  6. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    As an additional note, do not (actually never) catch ArgumentOutOfRangeExceptions. They're indicators for bugs that you can definitely avoid. E.g. check the index against min and max and do not change it if it's outside, or just use a built-in clamp method.
     
  7. VasyaProgrammist

    VasyaProgrammist

    Joined:
    Oct 6, 2019
    Posts:
    4
    Oh, I am just starting with Unity, so I am not good at C#. So I changed code again :D (for void Update)
    Again, thank you!

    Code (CSharp):
    1. private void Update() {
    2.         if (Input.GetKeyDown(KeyCode.DownArrow)) {
    3.             if (slc_pressed == 0) {
    4.                 slc_pressed = button_array.Count - 1;
    5.                 button_array[slc_pressed].Select();
    6.                 Debug.Log("Selected Selectable: " + button_array[slc_pressed].name);
    7.             } else {
    8.                 slc_pressed--;
    9.                 button_array[slc_pressed].Select();
    10.                 Debug.Log("Selected Selectable: " + button_array[slc_pressed].name);
    11.             }
    12.         } if (Input.GetKeyDown(KeyCode.UpArrow)) {
    13.             if (slc_pressed == button_array.Count - 1)
    14.             {
    15.                 slc_pressed = 0;
    16.                 button_array[slc_pressed].Select();
    17.                 Debug.Log("Selected Selectable: " + button_array[slc_pressed].name);
    18.             } else {
    19.                 slc_pressed++;
    20.                 button_array[slc_pressed].Select();
    21.                 Debug.Log("Selected Selectable: " + button_array[slc_pressed].name);
    22.             }
    23.         }
    24.     }