Search Unity

Question Reverse ScrollView

Discussion in 'UI Toolkit' started by gareth_untether, May 23, 2020.

  1. gareth_untether

    gareth_untether

    Joined:
    Jan 5, 2018
    Posts:
    69
    I'm using ScrollView to add Labels at runtime. I want the new label to appear at the bottom of the list and the ScrollView to display the new label (currently ScrollView keeps focus on the first label).

    Using scrollView.Add(newItem) places the label at the bottom, but I can't for the life of me work out how to focus the ScrollView at the bottom of the list insead of the top.

    A hack is to set the scrollOffset at the end of the frame.

    Code (CSharp):
    1.  scrollView.scrollOffset = new Vector2(0f, 2000000f);
     
  2. mrsorry78

    mrsorry78

    Joined:
    Sep 21, 2020
    Posts:
    2
    Hi. I have a similar problem. Could someone give us an example how to force Scrollview constantly scroll to the bottom. Like a debug console. Thanks in advance.
     
  3. antoine-unity

    antoine-unity

    Unity Technologies

    Joined:
    Sep 10, 2015
    Posts:
    780
    Hello, you could wait for the label to be receive its layout (by register a callback for the
    GeometryChangedEvent
    and then use
    ScrollView.ScrollTo(label).
     
    mrsorry78 likes this.
  4. mrsorry78

    mrsorry78

    Joined:
    Sep 21, 2020
    Posts:
    2
    Ok. Let's talk about ScrollView example below. This simple code adds a new text element as soon as a player types a command and presses Enter in the textfield.
    However the scrollbar never reaches the bottom. Always ends up a few pixels above.

    Code (CSharp):
    1. public class Test: MonoBehaviour
    2. {
    3.    private VisualElement _rootVisualElement;
    4.    private ScrollView _scrollView;
    5.    private TextField _textField;
    6.    private TextElement _lastItem;
    7.  
    8.    private void Awake()
    9.    {
    10.       _rootVisualElement = GetComponent<UIDocument>().rootVisualElement;
    11.       _scrollView = _rootVisualElement.Q<ScrollView>("view");
    12.       _textField = _rootVisualElement.Q<TextField>("command");
    13.       _textField.RegisterCallback<KeyDownEvent>(AddItem);
    14.    }
    15.  
    16.    private void AddItem(KeyDownEvent evt)
    17.    {
    18.             if (evt.keyCode != KeyCode.Return || string.IsNullOrEmpty(_textField.value))
    19.             {
    20.                 return;
    21.             }
    22.  
    23.             var _lastItem = new TextElement {text = _textField.value};
    24.  
    25.             _lastItem.RegisterCallback<GeometryChangedEvent>(e =>
    26.             {
    27.                 _scrollView.ScrollTo(_lastItem);
    28.             });
    29.  
    30.             _scrollView.Add(_lastItem);
    31.  
    32.             _textField.value = "";
    33.             _textField.Focus();
    34.    }
    35. }

    upload_2020-10-29_20-12-45.png
     
  5. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    A couple of things:

    1. Use Label instead of TextElement. TextElement is a low level base class for many types of elements, including Button.

    2. Avoid using a lambda for the GeometryChangedEvent registration so you can call "UnregisterCallback<GeometryChangeEvent>(yourFunc)" to immediately unregister this callback from your _lastItem element. Otherwise, all your _lastItem elements will constantly try to ScrollTo() themselves every time their Geometry changes (ie. width or height). This might explain the issue you're having.

    3. A simpler approach would be to just always scroll to the bottom of the ScrollView content. You can get the max offset from:
    Code (CSharp):
    1. ScrollView sv;
    2. var range = sv.verticalScroller.slider.range;
     
  6. skowroshima

    skowroshima

    Joined:
    Oct 14, 2018
    Posts:
    75
    I'm trying to do this exact same thing... a "auto scroll to bottom" ScrollView.

    This doesn't work. When the Callback is called, the verticalScroller is exactly the same as it was at the point as the callback was registered. The only thing that is updated on the ScrollView is the contentConatiner (has the new dimensions). This can't be used as the scroller won't take a value higher than it thinks the highValue is.

    The code that is not working (wrapping the label in a VisualElement as this will be more complicated in the future).

    Code (CSharp):
    1.    
    2.     public void LogText(string pText)
    3.     {
    4.         VisualElement vi = new VisualElement();
    5.         Label label = new Label(pText);
    6.         label.AddToClassList("eLabelStandard");
    7.  
    8.         vi.Add(label);
    9.         mSVLog.Add(vi);
    10.         vi.RegisterCallback<GeometryChangedEvent>(VIGeometryChangedCallback);
    11.     }
    12.  
    13.     public void VIGeometryChangedCallback(GeometryChangedEvent evt)
    14.     {
    15.         ((VisualElement)evt.target).UnregisterCallback<GeometryChangedEvent>(VIGeometryChangedCallBack);
    16.         mSVLog.verticalScroller.value = mSVLog.verticalScroller.highValue;
    17.  
    18.     }
    19.  
    My work around for now is to do the "verticalScroller.value = verticalScroller.highValue" in the next Update() call, which is less than ideal but works...
     
  7. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    Try registering the GeometryChangeEvent on the ScrollView's contentContainer itself (in addition to adding it to your new item). The ScrollView uses the same event to update its verticalScroller min/max and your callback should be called after ScrollView's callback (since you register it after it does).
     
  8. skowroshima

    skowroshima

    Joined:
    Oct 14, 2018
    Posts:
    75
    That worked, adding a GeometryChangeEvent on the ScrollView's contentContainer. I didn't need to also add it to the new item. Thanks!