Can't figure out how to scroll inside scrollRect with buttons inside

I have a scrollRect with a vertical layout of buttons that are full-width (a list of items). The UX I’m looking for is for a person to be able to scroll (or finger scroll) and then tap the one they want (no scroll bar).

The issue I’m having is the button is taking over the event, and not even trying to sense a scroll, even though it hasn’t processed a ‘click’ (which is what I’m listening for). How do I enable scroll events to pass through the buttons if a click isn’t registered? Thanks!

Does your button inherit from EventTriggerListener and override the OnDrag interface? The UGUI’s button only takes over the OnClick Event and will not affect ScrollRect.

I have buttons that look for the click event in a switch block.

It’s all done via script.

All I know is whenever the cursor or finger is over a button, it won’t scroll. Between the buttons and it’ll scroll until it hits a button and stops.

Make sure your button or it’s base not use IDragHandler

It’s only a click defined. I’m not sure how to check that.

would you mind putting up the button’s code?

As soon as i get back absolutely

Here is the logic for the events:

// Button Switch statement to determine which button has been clicked, as fire associated event
        switch (key)
        {
            case "Start Button":
            switch (type)
            {
                // case UiEvents.MouseUp:
                case UiEvents.Click:
                    {
                        StartButton();
                    }
                break;
            }

            break;

            case "Reset Button":
            switch (type)
            {
                // case UiEvents.MouseUp:
                case UiEvents.Click:
                    ResetTimer();
                break;
            }
            break;

            case "SettingsBtn":
            switch(type)
            {
                // case UiEvents.MouseUp:
                case UiEvents.Click:
                    view.settingsPanel.TogglePanel();
                break;
            }
            break;

            case "SetGraph":
            switch(type)
            {
                // case UiEvents.MouseUp:
                case UiEvents.Click:
                    // Get values
                    view.graph.DataSource.VerticalViewOrigin = double.Parse(view.graphOffset.text);
                    view.graph.DataSource.VerticalViewSize = double.Parse(view.graphSize.text);
                break;
            }
            break;

            case "SwitchGraph":
            switch(type)
            {
                case UiEvents.Click:
                if (rightFooted)
                {
                    rightFooted = false;
                    RenderGraph(currentUserForceArrayL, currentUserForceArrayR);
                    TextMeshProUGUI graphText = utils.getAttachedObject(data).GetComponent<TextMeshProUGUI>();
                    if (graphText != null)
                    {
                        graphText.text = "Right Graph";
                    } else
                    {
                        Debug.Log("TextMesh Empty");
                    }
                }
                else
                {
                    rightFooted = true;
                    RenderGraph(currentUserForceArrayL, currentUserForceArrayR);
                    TextMeshProUGUI graphText = utils.getAttachedObject(data).GetComponent<TextMeshProUGUI>();
                    if (graphText != null)
                    {
                        graphText.text = "Left Graph";
                    } else
                    {
                        Debug.Log("TextMesh Empty");
                    }
                }
                break;
            }
            break;

            case "RacesListBtn":
            switch(type)
            {
                // case UiEvents.MouseUp:
                case UiEvents.Click:
                    view.racesPanel.currentUser = currentUserSave;
                    view.racesPanel.TogglePanel();
                break;
            }
            break;

            /* User Session Buttons */
            case "AddUser":
            switch(type)
            {
                // case UiEvents.MouseUp:
                case UiEvents.Click:
                {
                    AddUser();
                }
                break;
            }
            break;

            case "AddUserSubmit":
            switch(type)
            {
                // case UiEvents.MouseUp:
                case UiEvents.Click:
                {
                    GameObject selObj = utils.getAttachedObject(data);
                    InputField contextInputField = selObj.GetComponentInChildren<InputField>();
                    string contextInput = contextInputField.text;
                    Debug.Log("Add user of name " + contextInput);
                    AddUserSubmit(contextInput);
                    contextInputField.text = "";
                    contextInputField.Select();
                }
                break;
            }
            break;

            case "AlertButton":
            switch(type)
            {
                // case UiEvents.MouseUp:
                case UiEvents.Click:
                    view.alertPanel.GetComponent<AlertWindow>().ToggleAlertWindow();
                break;
            }
            break;

            case "LoadUserDataButton":
            switch(type)
            {
                // case UiEvents.MouseUp:
                case UiEvents.Click:
                break;
            }
            break;
            case "UserListButton":
            switch(type)
            {
                // case UiEvents.MouseUp:
                case UiEvents.Click:
                    view.userWindow.toggleUserPanel();
                break;
            }
            break;
            case "RemoveUser":
            switch(type)
            {
                // case UiEvents.MouseUp:
                case UiEvents.Click:
                    PointerEventData eventData = utils.getValueForKey<PointerEventData>(data, "PointerEventData");
                    GameObject selObj = eventData.selectedObject;
                    GameObject attachedObj = utils.getAttachedObject(data);
                    string email = attachedObj.GetComponentInChildren<TextMeshProUGUI>().text;
                   
                    if (view.saveFile != null && view.saveFile.GetName() == email)
                    {
                        view.saveFile.Close();
                    }
                    RemoveUser(email);
                break;
            }
            break;

            case "RemoveRace":
            switch(type)
            {
                // case UiEvents.MouseUp:
                case UiEvents.Click:
                PointerEventData eventData = utils.getValueForKey<PointerEventData>(data, "PointerEventData");
                GameObject selObj = eventData.selectedObject;
                GameObject attachedObj = utils.getAttachedObject(data);
                string raceName = attachedObj.GetComponentInChildren<TextMeshProUGUI>().text;
                Debug.Log("Trying to erase " + raceName);
                view.saveFile.Delete(raceName);
                view.racesPanel.GetRaces(view.saveFile.GetName());
                break;
            }
            break;

            case "UserPanelMain":
            switch(type)
            {
                // case UiEvents.MouseUp:
                case UiEvents.Click:
                    PointerEventData eventData = utils.getValueForKey<PointerEventData>(data, "PointerEventData");
                    GameObject selObj = eventData.selectedObject;
                    GameObject attachedObj = utils.getAttachedObject(data);
                    string email = attachedObj.GetComponentInChildren<TextMeshProUGUI>().text;
                    Debug.Log("User is: " + email);
                    LoadUserData(email);
                    currentUserSave = email;
                    view.userWindow.toggleUserPanel();
                break;
            }
            break;

            case "RacePanel":
            switch(type)
            {
                // case UiEvents.MouseUp:
                case UiEvents.Click:
                    PointerEventData eventData = utils.getValueForKey<PointerEventData>(data, "PointerEventData");
                    GameObject attachedObj = utils.getAttachedObject(data);
                   
                    string race = attachedObj.GetComponentInChildren<TextMeshProUGUI>().text;
                    // Load the race into race comparison panel
                    Debug.Log("Loading race " + race);
                    view.racesPanel.LoadRace(race);
                    view.racesPanel.ToggleRacePanel(true);
                   
                break;
            }
            break;

            case "ImportRace":
            switch(type)
            {
                case UiEvents.Click:
                    ImportData();
                break;
            }
            break;

            case "ExportEmail":
            switch(type)
            {
                case UiEvents.Click:
                    GameObject attachedObj = utils.getAttachedObject(data);
                    string race = attachedObj.GetComponentInChildren<TextMeshProUGUI>().text;
                    Debug.Log("Exporting race: " + race);
                    ShareRace(race);
                break;
            }
            break;

            case "RacePanelBack":
            switch(type)
            {
                // case UiEvents.MouseUp:
                case UiEvents.Click:
                    view.racesPanel.ToggleRacePanel(false);
                break;
            }
            break;

            case "OpenMainGraph":
            switch(type)
            {
                case UiEvents.Click:
                TextMeshProUGUI graphText = GameObject.FindGameObjectWithTag("Main Graph Title").GetComponent<TextMeshProUGUI>();
                    if (rightFooted)
                    {
                        graphText.text = "Left Graph";
                    } else
                    {
                        graphText.text = "Right Graph";
                    }
                    RenderGraph(currentUserForceArrayL, currentUserForceArrayR);
                    view.graphCanvasGroup.interactable = true;
                    view.graphCanvasGroup.blocksRaycasts = true;
                    view.graphCanvasGroup.DOFade(1, .5f);
                break;
            }
            break;

            case "CloseMainGraph":
            switch(type)
            {
                case UiEvents.Click:
                    view.graphCanvasGroup.interactable = false;
                    view.graphCanvasGroup.blocksRaycasts = false;
                    view.graphCanvasGroup.DOFade(0, .5f);
                break;
            }
            break;
        } /* Eof switch block */
    }

well,the switch logic is all right. Actually I expect if your button inherits like this below.

public class Button1 : UIBehaviour, IPointerClickHandler
{

}

public class Button2 : EventTrigger
{

}

Would you like the inherited class as well?

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.EventSystems;

using strange.extensions.dispatcher.eventdispatcher.impl;
using strange.extensions.mediation.impl;
using app;

public class ButtonMediator : Mediator {

    [Inject]
    public ButtonView view { get; set; }

    [Inject]
    public UiSignal uiSignal { get; set;}

    [Inject]
    public SystemResponseSignal systemReponseSignal {get; set;}

    public void uiSignalListener(string key, string type, Dictionary<string, object> data)
    {
        uiSignal.Dispatch (key, type, data);
    }

    public void systemResponseSignalListener(string key, Dictionary<string, object> data){

        switch(key)
        {
        case SystemResponseEvents.Start:
            if(view.announcePresence)
            {
                uiSignal.Dispatch(view.buttonName, UiEvents.Presence, new Dictionary<string, object>{{"AttachedObject", view.attachedObject}});
            }

            break;
        }
    }

    // Use this for initialization
    override public void OnRegister(){

        //Debug.Log ("OnRegister(toolTip)");

        view.init ();


        view.uiSignal.AddListener (uiSignalListener);
        systemReponseSignal.AddListener(systemResponseSignalListener);



    }

    override public void OnRemove()
    {
        if(view != null)
            view.uiSignal.RemoveListener (uiSignalListener);
       
        if(systemReponseSignal != null)
            systemReponseSignal.RemoveListener(systemResponseSignalListener);
    }

}

My first first thought was event triggers, since those ‘eat’ events that the scroll rect uses. One thing to check when debugging as well is to verify the events using the event system’s inspector. This can verify what you think is receiving events is actually what is receiving events. Also if this behaviour is causing you trouble, try removing it and if your scrollRect works, then its definitely that script.

From what I’ve read, the buttons in Unity do ‘eat’ the scroll events in the scrollRect, but I’m not exactly sure how to test for it, nor am I certain on how to remedy it. The buttons are looking for ‘clicks’ which shouldn’t include a drag (click is fired on mouse up), so I’m confused as to why so many people have issues with buttons inside scrollrects.

Any direction would be greatly appreciated.

Sure ,Maybe the class Mediator ‘eats’ the Drag Event.

The extended mediator class is posted above

So it uses UnityEngine.EventSystem.
Maybe you could check ButtonView or SystemResponseSignal and see if they inherit from EventTrigger or IDragHandler.

This is ButtonView, which uses EventTrigger, and has drag events. Not sure how to fix this though.

using UnityEngine;
using System.Collections.Generic;
using UnityEngine.UI;

using UnityEngine.EventSystems;

using strange.extensions.dispatcher.eventdispatcher.api;
using strange.extensions.mediation.impl;
using strange.extensions.signal.impl;
using strange.extensions.context.api;

using TMPro;

using app;

public class ButtonView : View {
    public string toolTip;
    public float tipTimeSeconds = 2.5f;
    public string customName;
    public GameObject attachedObject;
    public bool announcePresence = false;
    public bool isToggle = false;
    public Image indicator;
    public GameObject dim;

    public UiSignal uiSignal = new UiSignal();

    public System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch ();

    bool isHover = false;
    bool isOn = false;

    Vector3 lastMouse = Vector3.zero;

    [HideInInspector]
    public string buttonName = "Unknown";

    public void mouseEnter(PointerEventData eventData){
        timer.Start ();
        isHover = true;
        lastMouse = Input.mousePosition;
    }

    public void mouseExit(PointerEventData eventData){
        timer.Reset ();
        if (toolTip != null && toolTip.Length > 0) {
            uiSignal.Dispatch ("ToolTip", null, new Dictionary<string, object> {
                { "AttachedObject", attachedObject },
                {
                    "Message",
                    toolTip
                },
                 {
                    "IsVisable",
                    false
                },
                 {
                    "PointerEventData",
                    eventData
                }
            });
        }
        isHover = false;
    }

    public void mouseDown(PointerEventData eventData){
        timer.Reset ();
        timer.Start ();
        uiSignal.Dispatch(buttonName, UiEvents.MouseDown, new Dictionary<string, object>{{"AttachedObject", attachedObject}, {"PointerEventData", eventData}});
        if (indicator != null) {
            indicator.gameObject.SetActive (true);
        }

        if (dim != null) {
            dim.SetActive(false);
        }

    }

    public void mouseUp(PointerEventData eventData){
        timer.Reset ();
        timer.Start ();
        uiSignal.Dispatch(buttonName, UiEvents.MouseUp, new Dictionary<string, object>{{"AttachedObject", attachedObject}, {"PointerEventData", eventData}});
        if (indicator != null) {
            indicator.gameObject.SetActive (false);
        }

        if (dim != null) {
            dim.SetActive(true);
        }

    }

    public void beginDrag(PointerEventData eventData){
        timer.Reset ();
        timer.Start ();
        uiSignal.Dispatch(buttonName, UiEvents.BeginDrag, new Dictionary<string, object>{{"AttachedObject", attachedObject}, {"PointerEventData", eventData}});
    }

    public void endDrag(PointerEventData eventData)
    {
        timer.Reset ();
        timer.Start ();
        uiSignal.Dispatch(buttonName, UiEvents.EndDrag,  new Dictionary<string, object>{{"AttachedObject", attachedObject}, {"PointerEventData", eventData}});
    }

    public void drag(PointerEventData eventData)
    {
        timer.Reset ();
        timer.Start ();
        uiSignal.Dispatch(buttonName, UiEvents.Drag, new Dictionary<string, object>{{"AttachedObject", attachedObject}, {"PointerEventData", eventData}});
    }

    public void setOn(bool _isOn){
        isOn = _isOn;

        if (isOn) {
            if (indicator != null) {
                indicator.gameObject.SetActive (true);
            }

            if (dim != null) {
                dim.SetActive(false);
            }

        } else {
            if (indicator != null) {
                indicator.gameObject.SetActive (false);
            }

            if (dim != null) {
                dim.SetActive(true);
            }
        }
    }

    public void click(PointerEventData eventData)
    {
        timer.Reset ();
        timer.Start ();

        if (isToggle) {

            isOn = !isOn;

            if (isOn) {
                if (indicator != null) {
                    indicator.gameObject.SetActive (true);
                }

                if (dim != null) {
                    dim.SetActive(false);
                }

                uiSignal.Dispatch(buttonName + " On", UiEvents.Click, new Dictionary<string, object>{{"AttachedObject", attachedObject}, {"PointerEventData", eventData}});
            } else {
                if (indicator != null) {
                    indicator.gameObject.SetActive (false);
                }

                if (dim != null) {
                    dim.SetActive(true);
                }
                uiSignal.Dispatch(buttonName + " Off", UiEvents.Click, new Dictionary<string, object>{{"AttachedObject", attachedObject}, {"PointerEventData", eventData}});
            }

        } else {
            uiSignal.Dispatch(buttonName, UiEvents.Click, new Dictionary<string, object>{{"AttachedObject", attachedObject}, {"PointerEventData", eventData}});
        }

    }

    public void setName(string name)
    {
        buttonName = name;
        customName = name;
    }


    public void init()
    {

        if (customName == null || customName.Length == 0)
            buttonName = gameObject.name;
        else
            buttonName = customName;

        timer.Reset ();

        EventTrigger trigger = gameObject.GetComponent<EventTrigger> ();

        if (trigger == null) {
            trigger = gameObject.AddComponent<EventTrigger> ();
        }

        EventTrigger.Entry pointerEnter = new EventTrigger.Entry();
        pointerEnter.eventID = EventTriggerType.PointerEnter;
        pointerEnter.callback.AddListener((eventData) => { mouseEnter((PointerEventData)eventData); } );
        trigger.triggers.Add(pointerEnter);

        EventTrigger.Entry pointerExit = new EventTrigger.Entry();
        pointerExit.eventID = EventTriggerType.PointerExit;
        pointerExit.callback.AddListener((eventData) => { mouseExit((PointerEventData)eventData); } );
        trigger.triggers.Add(pointerExit);

        EventTrigger.Entry pointerDown = new EventTrigger.Entry();
        pointerDown.eventID = EventTriggerType.PointerDown;
        pointerDown.callback.AddListener((eventData) => { mouseDown((PointerEventData)eventData); } );
        trigger.triggers.Add(pointerDown);

        EventTrigger.Entry pointerUp = new EventTrigger.Entry();
        pointerUp.eventID = EventTriggerType.PointerUp;
        pointerUp.callback.AddListener((eventData) => { mouseUp((PointerEventData)eventData); } );
        trigger.triggers.Add(pointerUp);

        EventTrigger.Entry beginDragEvent = new EventTrigger.Entry();
        beginDragEvent.eventID = EventTriggerType.BeginDrag;
        beginDragEvent.callback.AddListener((eventData) => { beginDrag((PointerEventData)eventData); } );
        trigger.triggers.Add(beginDragEvent);

        EventTrigger.Entry endDragEvent = new EventTrigger.Entry();
        endDragEvent.eventID = EventTriggerType.EndDrag;
        endDragEvent.callback.AddListener((eventData) => { endDrag((PointerEventData)eventData); } );
        trigger.triggers.Add(endDragEvent);

        EventTrigger.Entry dragEvent = new EventTrigger.Entry();
        dragEvent.eventID = EventTriggerType.Drag;
        dragEvent.callback.AddListener((eventData) => { drag((PointerEventData)eventData); } );
        trigger.triggers.Add(dragEvent);

        EventTrigger.Entry clickEvent = new EventTrigger.Entry();
        clickEvent.eventID = EventTriggerType.PointerClick;
        clickEvent.callback.AddListener((eventData) => { click((PointerEventData)eventData); } );
        trigger.triggers.Add(clickEvent);

    }

    private string _text;

    public string text{
        get{ return _text;}
        set{
            _text = value;

            TextMeshProUGUI[] texts = gameObject.GetComponentsInChildren<TextMeshProUGUI> (true);

            for (int i = 0; i < texts.Length; i++) {
                texts [i].text = _text;
            }
        }
    }

    // Update is called once per frame
    void Update () {

        if(toolTip != null && toolTip.Length > 0)
        {
            if (timer.ElapsedMilliseconds >= tipTimeSeconds * 1000) {
                uiSignal.Dispatch("ToolTip", null, new Dictionary<string, object>{{"Message", toolTip},{"IsVisable", true}});
            }

            if (isHover) {
                if (lastMouse.x != Input.mousePosition.x || lastMouse.y != Input.mousePosition.y) {
                    timer.Reset ();
                    timer.Start ();
                    uiSignal.Dispatch("ToolTip", null, new Dictionary<string, object>{{"Message", toolTip},{"IsVisable", false}});

                    lastMouse = Input.mousePosition;
                }
            }
        }
    }
}

That’s it ,the button overrides the Ondrag interface and eats the drag event.
To fix this,you have to create a new class to contain the code that only deals with ClickEvent from View.
and let the buttonView inherits from the new base class.

Here is the sample code.

    public class PointClickListener : MonoBehaviour, IPointerClickHandler
    {
        public delegate void VoidDelegate(PointerEventData data);
        public VoidDelegate onClick;
        static public PointClickListener Get(UnityEngine.GameObject go)
        {
            return go.GetComponent<PointClickListener>() ??go.AddComponent<PointClickListener>();
        }
        public void OnPointerClick(PointerEventData eventData)
        {
            if (onClick != null)
            {
                onClick(eventData);
            }
        }
    }

Couldn’t I just add it in the class that’s already been defined?

Sure,what ever you like. Just ensure that the button should take the right event.