Search Unity

UI Dialog Panel Return Result When Button is clicked

Discussion in 'UGUI & TextMesh Pro' started by Optimus_Prime, Jan 4, 2016.

  1. Optimus_Prime

    Optimus_Prime

    Joined:
    Apr 15, 2015
    Posts:
    34
    Hi Guys

    Happy new year to all and best wishes :)

    I needed to make a Modal Panel for my game and I watched the following tutorials which really helped me get to 90% of what I was looking for.

    http://unity3d.com/learn/tutorials/modules/intermediate/live-training-archive/modal-window
    http://unity3d.com/learn/tutorials/modules/intermediate/live-training-archive/modal-window-pt2
    http://unity3d.com/learn/tutorials/modules/intermediate/live-training-archive/modal-window-pt3

    The modal panel described in the above tutorials works well. I just need the modal panel to return a result when the button is clicked so I can use that result in my code.

    Like for example in standard C# desktop development using the .NET Framework. If you want to prompt the user and depending on the result of the what the user chose, you want to do something so you would simply write some code like this

    var result = MessageBox.Show("Do you want to save your word doc?", "Title", Buttons).DialogResult
    if(result == DialogResult.Ok)
    //do something here

    I just need my Modal Panel to return a result using the above tutorials. So when I can call the panel like below
    var result = ModalPanel.Choice("Do you want to save this inventory?", UnityAction1, UnityAction2);

    Thanks guys
     
  2. Optimus_Prime

    Optimus_Prime

    Joined:
    Apr 15, 2015
    Posts:
    34
    Any Ideas ?
     
  3. moure

    moure

    Joined:
    Aug 18, 2013
    Posts:
    184
    Hi,
    Not sure if i understand correctly but i think you just want to call a function when a button is clicked?
    You can do that with a couple of ways. If your button is created in editor you can simply call the function by adding the script in the on click section of the button script. You can see how this is done here.
    If you want more options like onmouseenter, onmouseexit etc, you can add to the button the Event Trigger script and use a similar workflow as above. You can find info about the event trigger script here.
    Finally if your buttons are created during runtime or if the editor way isnt for you, you can do something like this by adding a listener for a button click.
     
  4. Optimus_Prime

    Optimus_Prime

    Joined:
    Apr 15, 2015
    Posts:
    34
    Hi Moure

    Thanks for your reply

    The button is just a normal UI button added in the designer, nothing fancy. The button is already subscribed to an event action which works. I dont have a problem with the button and how to interact with the button. Let me paste some code and explain this problem a bit better, I must apologize for my lack of explanation.

    using System;
    using UnityEngine;
    using UnityEngine.UI;
    using UnityEngine.Events;
    using System.Collections;

    public class MessageBox : MonoBehaviour
    {
    public Text question;
    public Button yesButton;
    public Button noButton;
    public Button cancelButton;
    public Button okButton;
    public GameObject messageBoxObject;


    private static MessageBox messageBox;

    private MessageBox() { }

    public static MessageBox Instance()
    {
    if (!messageBox)
    {
    messageBox = FindObjectOfType(typeof(MessageBox)) as MessageBox;
    if (!messageBox)
    Debug.LogError("There needs to be one active messageBox script on a GameObject in your scene.");
    }

    return messageBox;
    }

    public void Show(string question, UnityAction yesEvent = null,
    UnityAction noEvent = null, UnityAction cancelEvent = null, UnityAction okEvent = null)
    {
    messageBoxObject.SetActive(true);

    this.question.text = question;

    if (yesEvent != null)
    {
    yesButton.gameObject.SetActive(true);
    yesButton.onClick.RemoveAllListeners();
    yesButton.onClick.AddListener(yesEvent);
    yesButton.onClick.AddListener(ClosePanel);
    // if this button is clicked then return a result "Yes" to the calling method
    }
    else
    yesButton.gameObject.SetActive(false);

    if (noEvent != null)
    {
    noButton.gameObject.SetActive(true);
    noButton.onClick.RemoveAllListeners();
    noButton.onClick.AddListener(noEvent);
    noButton.onClick.AddListener(ClosePanel);
    // if this button is clicked then return a result "No" to the calling method
    }
    else
    noButton.gameObject.SetActive(false);

    if (okEvent != null)
    {
    okButton.gameObject.SetActive(true);
    okButton.onClick.RemoveAllListeners();
    okButton.onClick.AddListener(okEvent);
    okButton.onClick.AddListener(ClosePanel);
    // if this button is clicked then return a result "Ok" to the calling method

    }
    else
    okButton.gameObject.SetActive(false);

    if (cancelEvent != null)
    {
    cancelButton.gameObject.SetActive(true);
    cancelButton.onClick.RemoveAllListeners();
    cancelButton.onClick.AddListener(cancelEvent);
    cancelButton.onClick.AddListener(ClosePanel);
    // if this button is clicked then return a result "Cancel" to the calling method
    }
    else
    cancelButton.gameObject.SetActive(false);
    }

    void ClosePanel()
    {
    messageBoxObject.SetActive(false);
    }

    }

    Now my Game Manager has an instance of this class so I just call the MessageBox like this
    messageBox.Show("Testing the messagebox", myYesAction, null, null, null);

    That works fine and the MessageBox is displayed and my YesButton works fine and calls my method

    I want something like this
    var result = messageBox.Show("Testing the messagebox", myYesAction, null, null, null);
    if(result == "Yes")
    // do something here

    I simply want my Show Method to return a string or an Enum if possible. Now I tried changing the method to return a string(it wasnt a void) but I couldnt see the method anymore in my Game Manager and it is public. I also tried using an Enum class but still no luck.

    This example is a straight forward C# example using the .NET Framework but you cant use this in Unity for obvious reasons. The article explains what a dialog result is and how to use it in Desktop development. I just want my messagebox.Show method to try and mimic this behavior.

    http://www.dotnetperls.com/dialogresult

    any ideas ?
     
  5. moure

    moure

    Joined:
    Aug 18, 2013
    Posts:
    184
    Hey mate,
    i just tested your code and everything works with a public string instead of a public void. Here is my example based on your code.
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3. using UnityEngine.UI;
    4. using UnityEngine.Events;
    5. using System.Collections;
    6.  
    7. public class MessageBox : MonoBehaviour
    8. {
    9.     public Text question;
    10.     public Button yesButton;
    11.     public Button noButton;
    12.     public Button cancelButton;
    13.     public Button okButton;
    14.     public GameObject messageBoxObject;
    15.  
    16.  
    17.     private static MessageBox messageBox;
    18.  
    19.     private MessageBox() { }
    20.  
    21.     public static MessageBox Instance()
    22.     {
    23.         if (!messageBox)
    24.         {
    25.             messageBox = FindObjectOfType(typeof(MessageBox)) as MessageBox;
    26.             if (!messageBox)
    27.                 Debug.LogError("There needs to be one active messageBox script on a GameObject in your scene.");
    28.         }
    29.  
    30.         return messageBox;
    31.     }
    32.  
    33.     public string Show(string question, UnityAction yesEvent = null,
    34.     UnityAction noEvent = null, UnityAction cancelEvent = null, UnityAction okEvent = null)
    35.     {
    36.         messageBoxObject.SetActive(true);
    37.  
    38.         this.question.text = question;
    39.  
    40.         if (yesEvent != null)
    41.         {
    42.             yesButton.gameObject.SetActive(true);
    43.             yesButton.onClick.RemoveAllListeners();
    44.             yesButton.onClick.AddListener(yesEvent);
    45.             yesButton.onClick.AddListener(ClosePanel);
    46.             // if this button is clicked then return a result "Yes" to the calling method
    47.         }
    48.         else
    49.         {
    50.             yesButton.gameObject.SetActive(false);
    51.         }
    52.            
    53.  
    54.         if (noEvent != null)
    55.         {
    56.             noButton.gameObject.SetActive(true);
    57.             noButton.onClick.RemoveAllListeners();
    58.             noButton.onClick.AddListener(noEvent);
    59.             noButton.onClick.AddListener(ClosePanel);
    60.             // if this button is clicked then return a result "No" to the calling method
    61.         }
    62.         else
    63.         {
    64.             noButton.gameObject.SetActive(false);
    65.         }
    66.            
    67.  
    68.         if (okEvent != null)
    69.         {
    70.             okButton.gameObject.SetActive(true);
    71.             okButton.onClick.RemoveAllListeners();
    72.             okButton.onClick.AddListener(okEvent);
    73.             okButton.onClick.AddListener(ClosePanel);
    74.             // if this button is clicked then return a result "Ok" to the calling method
    75.  
    76.         }
    77.         else
    78.         {
    79.             okButton.gameObject.SetActive(false);
    80.         }
    81.          
    82.  
    83.         if (cancelEvent != null)
    84.         {
    85.             cancelButton.gameObject.SetActive(true);
    86.             cancelButton.onClick.RemoveAllListeners();
    87.             cancelButton.onClick.AddListener(cancelEvent);
    88.             cancelButton.onClick.AddListener(ClosePanel);
    89.             // if this button is clicked then return a result "Cancel" to the calling method
    90.         }
    91.         else
    92.         {
    93.             cancelButton.gameObject.SetActive(false);
    94.         }
    95.            
    96.  
    97.         return "HelloWorld";
    98.     }
    99.  
    100.     void ClosePanel()
    101.     {
    102.         messageBoxObject.SetActive(false);
    103.     }
    104.  
    105. }
    and my test ManagerClass

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class DesktopManager : MonoBehaviour {
    5.  
    6.     public MessageBox thismessagebox;
    7.     public string thisstring;
    8.  
    9.     // Use this for initialization
    10.     void Start () {
    11.  
    12.         thisstring = thismessagebox.Show("Testing the messagebox", myYesAction, null, null, null);
    13.  
    14.     }
    15.    
    16.     // Update is called once per frame
    17.     void Update () {
    18.    
    19.     }
    20.  
    21.     public void myYesAction()
    22.     {
    23.         Debug.Log("YesPressed");
    24.     }
    25. }
    26.  
    And as you can see on the image i get the HelloWorld string correctly.
     

    Attached Files:

  6. moh05

    moh05

    Joined:
    Nov 26, 2015
    Posts:
    65
    Hello there,

    I am just adding to the code @moure posted...Hope I understood what was your problem


    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3. using UnityEngine.UI;
    4. using UnityEngine.Events;
    5. using System.Collections;
    6.  
    7. public class MessageBox : MonoBehaviour
    8. {
    9.     public Text question;
    10.     public Button yesButton;
    11.     public Button noButton;
    12.     public Button cancelButton;
    13.     public Button okButton;
    14.     public GameObject messageBoxObject;
    15.     public string Answer;  //Stores which button was clicked
    16.  
    17.  
    18.  
    19.     private static MessageBox messageBox;
    20.  
    21.     private MessageBox() { Answer = "Nothing"   }  //when you start the game, there shoudl be nothing stored as an answer
    22.  
    23.     public static MessageBox Instance()
    24.     {
    25.         if (!messageBox)
    26.         {
    27.             messageBox = FindObjectOfType(typeof(MessageBox)) as MessageBox;
    28.             if (!messageBox)
    29.                 Debug.LogError("There needs to be one active messageBox script on a GameObject in your scene.");
    30.         }
    31.  
    32.         return messageBox;
    33.     }
    34.  
    35.     public void Show(string question, UnityAction yesEvent = null,
    36.     UnityAction noEvent = null, UnityAction cancelEvent = null, UnityAction okEvent = null)  //keep the void type
    37.     {
    38.         messageBoxObject.SetActive(true);
    39.  
    40.         this.question.text = question;
    41.  
    42.         if (yesEvent != null)
    43.         {
    44.             yesButton.gameObject.SetActive(true);
    45.             yesButton.onClick.RemoveAllListeners();
    46.             yesButton.onClick.AddListener(yesEvent);
    47.             yesButton.onClick.AddListener(ClosePanel);
    48.             yesButton.onClick.AddListener(ISaidYes); //Let yes button listen to a new function
    49.         }
    50.         else
    51.         {
    52.             yesButton.gameObject.SetActive(false);
    53.         }
    54.          
    55.  
    56.         if (noEvent != null)
    57.         {
    58.             noButton.gameObject.SetActive(true);
    59.             noButton.onClick.RemoveAllListeners();
    60.             noButton.onClick.AddListener(noEvent);
    61.             noButton.onClick.AddListener(ClosePanel);
    62.             noButton.onClick.AddListener(ISaidNo);
    63.         }
    64.         else
    65.         {
    66.             noButton.gameObject.SetActive(false);
    67.         }
    68.          
    69.  
    70.         if (okEvent != null)
    71.         {
    72.             okButton.gameObject.SetActive(true);
    73.             okButton.onClick.RemoveAllListeners();
    74.             okButton.onClick.AddListener(okEvent);
    75.             okButton.onClick.AddListener(ClosePanel);
    76.             okButton.onClick.AddListener(ISaidOk);
    77.  
    78.         }
    79.         else
    80.         {
    81.             okButton.gameObject.SetActive(false);
    82.         }
    83.        
    84.  
    85.         if (cancelEvent != null)
    86.         {
    87.             cancelButton.gameObject.SetActive(true);
    88.             cancelButton.onClick.RemoveAllListeners();
    89.             cancelButton.onClick.AddListener(cancelEvent);
    90.             cancelButton.onClick.AddListener(ClosePanel);
    91.             // if this button is clicked then return a result "Cancel" to the calling method
    92.         }
    93.         else
    94.         {
    95.             cancelButton.gameObject.SetActive(false);
    96.         }
    97.          
    98.  
    99.         return "HelloWorld";
    100.     }
    101.  
    102.     void ClosePanel()
    103.     {
    104.         messageBoxObject.SetActive(false);
    105.     }
    106.  
    107.     void ISaidYes()
    108.     {
    109.             Answer = Yes;
    110.      } //Do the same for the other created functions
    111.  
    112.  
    113. }
    and my test ManagerClass

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class DesktopManager : MonoBehaviour {
    5.  
    6.     public MessageBox thismessagebox;
    7.     public string thisstring;
    8.  
    9.     // Use this for initialization
    10.     void Start () {
    11.  
    12.         thismessagebox.Show("Testing the messagebox", myYesAction, null, null, null);  //Show funtion returning value will never work since show funtion is invoked before you even chose anything :)
    13.  
    14. //Then when you click any button, that button will change the Answer string in MessageBox
    15.  
    16.     }
    17.  
    18.     // Update is called once per frame
    19.     void Update () {  // i guess u need to use update
    20.  
    21.    if( MessageBox.Answer == "Yes")
    22.    {
    23.         //Do whatever you want to do
    24.          MessageBox.Answer = "Nothing" // Of course you only need to "Do whatever you want to do" once :)
    25.    }
    26.    else
    27.    ///Same pattern for all button cases
    28.  
    29.  
    30.     }
    31.  
    32.     public void myYesAction()
    33.     {
    34.         Debug.Log("YesPressed");
    35.     }
    36. }
    37.  
    Is that what are you looking for?
     
    Last edited: Jan 8, 2016
  7. Optimus_Prime

    Optimus_Prime

    Joined:
    Apr 15, 2015
    Posts:
    34
    Is there anyway to not use the Update Function?
    can it not work like the standard Messagebox in .NET ?
     
  8. Thoreandon

    Thoreandon

    Joined:
    Aug 21, 2014
    Posts:
    37
    What Optimus_Prime wants, is a true modal Window, that stops code execution until the user made his choice.
    I have the same problem right now.

    The Code normaly is run in an async way, so if i open the modal window like:

    mywindow.showDialog(blabla..)
    (more code)

    the code beneath is still executed, which i dont want.
    After showDialog, code execution should wait until a result is returned.

    I can put everything in a Coroutine und use yield until the value is set, but it is very uncomfortable.

    So i hope someone does have an idea, perhaps it is not possible due to the architecture of unity?!?
     
  9. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    Optimus_Prime:

    There's nothing preventing you from doing just that; No need to use Update / coroutines either (IMO):

    - Use UI Button and it has slot for calling methods on On Click event. Just add your method there.
    - Or use Event Trigger component on your UI item, and add a method to it that you want to call on panel yes/no/close pressed.
    - Or add comparable interface (IPointerClickHandler) and OnPointerClick Method to your script, and add this script to your UI item / no need for it to be a button then, it can be just an UI item like Image.
     
  10. Adam-Buckner

    Adam-Buckner

    Joined:
    Jun 27, 2007
    Posts:
    5,664
    The example the OP is using is simply an event driven dialogue box. When using this system, if you want the underlying code to stop executing, you'll need to write that functionality into your project and call it when opening the window, and call an unpause when you're done.

    As this is event driven, the architecture is not to return a value from the modal window, but to call an event directly - so - NOT:
    Code (csharp):
    1. var choice = modalWindow (title, buttonAName, buttonBName);
    2. If (choice == a) callFunctionA ();
    3. else if (choice == b) callFunctionB ();
    ... It IS:

    The event code calls the functions directly. This is the point to events. You send to the button itself the name and icon and function that the button will call when the button is pushed. The if/else logic is being performed directly by the user through the choices they make.
     
  11. Sir-Toby

    Sir-Toby

    Joined:
    Dec 21, 2018
    Posts:
    1
    Yeah, after following the tutorial linked in the first post i asked myself the same question. I think Adam-Buckner ist right, this is an architecture decision but for me it is much more practical, not to break the flow of the code every time i wanted to show a modal dialog. Also i don't like the fact, that the call of the modal window must be the last line in the current method due to this event driven design.

    So i tried to archieve my goal with async / await and tasks which worked for me very well an let me use (optional) events and/or check directly in the same method, which button the user clicked.

    Here is the modified example code with my solution:

    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3. using UnityEngine.UI;
    4. using UnityEngine.Events;
    5. using System.Collections;
    6. using System.Threading.Tasks;
    7.  
    8. public class MessageBox : MonoBehaviour
    9. {
    10.     public Text question;
    11.     public Button yesButton;
    12.     public Button noButton;
    13.     public Button cancelButton;
    14.     public Button okButton;
    15.     public GameObject messageBoxObject;
    16.  
    17.     public TaskCompletionSource<int> ButtonClicked { get; set; } = null;
    18.  
    19.     private static MessageBox messageBox;
    20.  
    21.     private MessageBox() { }
    22.  
    23.     public static MessageBox Instance()
    24.     {
    25.         if (!messageBox)
    26.         {
    27.             messageBox = FindObjectOfType(typeof(MessageBox)) as MessageBox;
    28.             if (!messageBox)
    29.                 Debug.LogError("There needs to be one active messageBox script on a GameObject in your scene.");
    30.         }
    31.  
    32.         return messageBox;
    33.     }
    34.  
    35.     public async Task<int> Show(string question, UnityAction yesEvent = null, UnityAction noEvent = null, UnityAction cancelEvent = null, UnityAction okEvent = null)
    36.     {
    37.         messageBoxObject.SetActive(true);
    38.  
    39.         this.question.text = question;
    40.  
    41.         if (yesEvent != null)
    42.         {
    43.             yesButton.gameObject.SetActive(true);
    44.             yesButton.onClick.RemoveAllListeners();
    45.             yesButton.onClick.AddListener(yesEvent);
    46.             yesButton.onClick.AddListener(() => { ButtonClicked?.TrySetResult(1); });
    47.             yesButton.onClick.AddListener(ClosePanel);
    48.         }
    49.         else
    50.             yesButton.gameObject.SetActive(false);
    51.  
    52.         if (noEvent != null)
    53.         {
    54.             noButton.gameObject.SetActive(true);
    55.             noButton.onClick.RemoveAllListeners();
    56.             noButton.onClick.AddListener(noEvent);
    57.             noButton.onClick.AddListener(() => { ButtonClicked?.TrySetResult(2); });
    58.             noButton.onClick.AddListener(ClosePanel);
    59.         }
    60.         else
    61.             noButton.gameObject.SetActive(false);
    62.  
    63.         if (okEvent != null)
    64.         {
    65.             okButton.gameObject.SetActive(true);
    66.             okButton.onClick.RemoveAllListeners();
    67.             okButton.onClick.AddListener(okEvent);
    68.             okButton.onClick.AddListener(() => { ButtonClicked?.TrySetResult(3); });
    69.             okButton.onClick.AddListener(ClosePanel);
    70.         }
    71.         else
    72.             okButton.gameObject.SetActive(false);
    73.  
    74.         if (cancelEvent != null)
    75.         {
    76.             cancelButton.gameObject.SetActive(true);
    77.             cancelButton.onClick.RemoveAllListeners();
    78.             cancelButton.onClick.AddListener(cancelEvent);
    79.             cancelButton.onClick.AddListener(() => { ButtonClicked?.TrySetResult(4); });
    80.             cancelButton.onClick.AddListener(ClosePanel);
    81.         }
    82.         else
    83.             cancelButton.gameObject.SetActive(false);
    84.  
    85.         return await ButtonClickedTask();
    86.     }
    87.  
    88.     void ClosePanel()
    89.     {
    90.         messageBoxObject.SetActive(false);
    91.     }
    92.  
    93.     private Task<int> ButtonClickedTask()
    94.     {
    95.         ButtonClicked = new TaskCompletionSource<int>();
    96.         return ButtonClicked.Task;
    97.     }
    98. }
    99.  
    100.  
    101. // Example for calling the messagebox:
    102. var DialogResult = await messageBox.Show("Testing the messagebox", myYesAction);  // The actions are fully optional and combinable
    103.  
    104. if (DialogResult == 1)
    105.     DoSomething();
    106. else
    107.     return;
    To optimize this you can also use your own enumeration (just like https://docs.microsoft.com/de-de/dotnet/api/system.windows.forms.dialogresult) instead of a simple int.
    Hope, this helps someone, who is searching for this question. ;-)

    Greetings,
    Sir Toby
     
    f5fairy and Vorocity like this.
  12. Vorocity

    Vorocity

    Joined:
    Mar 13, 2019
    Posts:
    2
    Hi Sir Toby, bringing some life back to an old thread! I have a similar issue where I am porting (to Unity) a turn-based winforms project that makes extensive use of modal dialog's. Your solution seems perfect for this, but I'm having a bit of trouble with making the call to messageBox.Show. Can you show a more complete example of how you would call the messageBox from code in the main thread, outside the messageBox script? I have no problems with inter-class calls or references, it's just the async / await / task called from main thread that's messing me up.

    Thanks muchly.
     
    Last edited: Apr 24, 2019