798 votes

Comment empêcher la fermeture d'une boîte de dialogue lorsqu'un bouton est cliqué ?

J'ai un dialogue avec EditText pour l'entrée. Lorsque je clique sur le bouton "oui" de la boîte de dialogue, celle-ci valide l'entrée, puis se ferme. Cependant, si la saisie est erronée, je veux rester dans la même boîte de dialogue. À chaque fois, quelle que soit l'entrée, la boîte de dialogue doit être automatiquement fermée lorsque je clique sur le bouton "non". Comment puis-je désactiver cela ? À propos, j'ai utilisé PositiveButton et NegativeButton pour les boutons de la boîte de dialogue.

992voto

Tom Bollwitt Points 3391

EDIT : Cela ne fonctionne que sur l'API 8+, comme l'indiquent certains commentaires.

Il s'agit d'une réponse tardive, mais vous pouvez ajouter un onShowListener à l'AlertDialog, où vous pouvez ensuite remplacer le onClickListener du bouton.

final AlertDialog dialog = new AlertDialog.Builder(context)
        .setView(v)
        .setTitle(R.string.my_title)
        .setPositiveButton(android.R.string.ok, null) //Set to null. We override the onclick
        .setNegativeButton(android.R.string.cancel, null)
        .create();

dialog.setOnShowListener(new DialogInterface.OnShowListener() {

    @Override
    public void onShow(DialogInterface dialogInterface) {

        Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
        button.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View view) {
                // TODO Do something

                //Dismiss once everything is OK.
                dialog.dismiss();
            }
        });
    }
});
dialog.show();

7 votes

Hé, mieux vaut tard que jamais, je cherchais exactement cela, merci, +1 :) C'est une façon élégante d'ajouter la validation à votre boîte de dialogue, surtout lorsque vous avez déjà une classe d'habillage pour gérer les alertes.

12 votes

Ne fonctionne pas. AlertDialog.Builder.setOnShowListener n'existe pas. developer.Android.com/reference/Android/app/

4 votes

Avec l'API pré-8, vous pouvez appeler d.getButton(AlertDialog.BUTTON_POSITIVE) ; comme c'est une méthode publique, mais vous devez l'appeler lorsque show() ; a été émis, sinon vous obtenez simplement null.

713voto

Sogger Points 2761

Voici quelques solutions pour tous les types de boîtes de dialogue, y compris une solution pour AlertDialog.Builder qui fonctionnera sur tous les niveaux d'API (fonctionne en dessous de l'API 8, ce que l'autre réponse ici ne fait pas). Il existe des solutions pour les dialogues AlertDialog utilisant AlertDialog.Builder, DialogFragment et DialogPreference.

Les exemples de code ci-dessous montrent comment remplacer le gestionnaire de bouton commun par défaut et empêcher la fermeture de la boîte de dialogue pour ces différentes formes de boîtes de dialogue. Tous les exemples montrent comment empêcher le bouton positif de fermer la boîte de dialogue.

Remarque : Une description du fonctionnement de la fermeture de la boîte de dialogue pour les classes Android de base et des raisons pour lesquelles les approches suivantes ont été choisies suit les exemples, pour ceux qui veulent plus de détails.


AlertDialog.Builder - Modifier le gestionnaire de bouton par défaut immédiatement après show()

AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage("Test for preventing dialog close");
builder.setPositiveButton("Test", 
        new DialogInterface.OnClickListener()
        {
            @Override
            public void onClick(DialogInterface dialog, int which)
            {
                //Do nothing here because we override this button later to change the close behaviour. 
                //However, we still need this because on older versions of Android unless we 
                //pass a handler the button doesn't get instantiated
            }
        });
final AlertDialog dialog = builder.create();
dialog.show();
//Overriding the handler immediately after show is probably a better approach than OnShowListener as described below
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener()
      {            
          @Override
          public void onClick(View v)
          {
              Boolean wantToCloseDialog = false;
              //Do stuff, possibly set wantToCloseDialog to true then...
              if(wantToCloseDialog)
                  dialog.dismiss();
              //else dialog stays open. Make sure you have an obvious way to close the dialog especially if you set cancellable to false.
          }
      });

DialogFragment - surcharge onResume()

@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    builder.setMessage("Test for preventing dialog close");
    builder.setPositiveButton("Test", 
        new DialogInterface.OnClickListener()
        {
            @Override
            public void onClick(DialogInterface dialog, int which)
            {
                //Do nothing here because we override this button later to change the close behaviour. 
                //However, we still need this because on older versions of Android unless we 
                //pass a handler the button doesn't get instantiated
            }
        });
    return builder.create();
}

//onStart() is where dialog.show() is actually called on 
//the underlying dialog, so we have to do it there or 
//later in the lifecycle.
//Doing it in onResume() makes sure that even if there is a config change 
//environment that skips onStart then the dialog will still be functioning
//properly after a rotation.
@Override
public void onResume()
{
    super.onResume();    
    final AlertDialog d = (AlertDialog)getDialog();
    if(d != null)
    {
        Button positiveButton = (Button) d.getButton(Dialog.BUTTON_POSITIVE);
        positiveButton.setOnClickListener(new View.OnClickListener()
                {
                    @Override
                    public void onClick(View v)
                    {
                        Boolean wantToCloseDialog = false;
                        //Do stuff, possibly set wantToCloseDialog to true then...
                        if(wantToCloseDialog)
                            d.dismiss();
                        //else dialog stays open. Make sure you have an obvious way to close the dialog especially if you set cancellable to false.
                    }
                });
    }
}

Préférence de dialogue - surcharge showDialog()

@Override
protected void onPrepareDialogBuilder(Builder builder)
{
    super.onPrepareDialogBuilder(builder);
    builder.setPositiveButton("Test", this);   //Set the button here so it gets created
}

@Override
protected void showDialog(Bundle state)
{       
    super.showDialog(state);    //Call show on default first so we can override the handlers

    final AlertDialog d = (AlertDialog) getDialog();
    d.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener()
            {            
                @Override
                public void onClick(View v)
                {
                    Boolean wantToCloseDialog = false;
                    //Do stuff, possibly set wantToCloseDialog to true then...
                    if(wantToCloseDialog)
                        d.dismiss();
                    //else dialog stays open. Make sure you have an obvious way to close the dialog especially if you set cancellable to false.
                }
            });
}

Explication des approches :

En examinant le code source d'Android, l'implémentation par défaut d'AlertDialog fonctionne en enregistrant un gestionnaire de bouton commun à tous les boutons actuels dans OnCreate(). Lorsqu'un bouton est cliqué, le gestionnaire de bouton commun transmet l'événement de clic au gestionnaire que vous avez passé dans setButton(), puis appelle la boîte de dialogue.

Si vous souhaitez empêcher la fermeture d'une boîte de dialogue lorsque vous appuyez sur l'un de ces boutons, vous devez remplacer le gestionnaire de bouton commun par la vue réelle du bouton. Comme il est affecté dans OnCreate(), vous devez le remplacer après l'appel de l'implémentation OnCreate() par défaut. OnCreate est appelé dans le processus de la méthode show(). Vous pourriez créer une classe de dialogue personnalisée et surcharger OnCreate() pour appeler super.OnCreate() puis surcharger les gestionnaires de bouton, mais si vous créez un dialogue personnalisé, vous n'obtenez pas le Builder gratuitement, auquel cas quel est l'intérêt ?

Ainsi, pour utiliser une boîte de dialogue de la manière dont elle a été conçue, mais en contrôlant le moment où elle est rejetée, une approche consiste à appeler d'abord dialog.Show(), puis à obtenir une référence au bouton en utilisant dialog.getButton() pour remplacer le gestionnaire de clic. Une autre approche consiste à utiliser setOnShowListener() et à implémenter la recherche de la vue du bouton et le remplacement du gestionnaire dans le OnShowListener. La différence fonctionnelle entre les deux est "presque" nulle, selon le thread qui crée initialement l'instance de dialogue. En regardant le code source, le OnShowListener est appelé par un message posté à un handler s'exécutant sur le thread qui a créé ce dialogue. Ainsi, puisque votre OnShowListener est appelé par un message posté dans la file d'attente des messages, il est techniquement possible que l'appel de votre écouteur soit retardé quelque temps après la fin de l'affichage.

Par conséquent, je pense que l'approche la plus sûre est la première : appeler show.Dialog(), puis immédiatement dans le même chemin d'exécution remplacer les gestionnaires de boutons. Puisque le code qui appelle show() fonctionnera sur le thread principal de l'interface graphique, cela signifie que le code qui suit show() sera exécuté avant tout autre code sur ce thread, alors que le timing de la méthode OnShowListener est à la merci de la file d'attente des messages.

14 votes

C'est de loin la mise en œuvre la plus simple et elle fonctionne parfaitement. J'ai utilisé AlertDialog.Builder - Modifier le gestionnaire de bouton par défaut immédiatement après show() et ça marche comme sur des roulettes.

1 votes

@sogger dude, j'ai totalement édité votre réponse étonnante parce que dans la section 1 vous aviez dismiss() ; au lieu de je crois dialog.dismiss() ; merci beaucoup pour la réponse impressionnante !

0 votes

Existe-t-il un moyen d'empêcher la fermeture d'un ProgressDialog lorsqu'un bouton est cliqué dessus ?

36voto

YuviDroid Points 1057

J'ai écrit une classe simple (un AlertDialogBuilder) que vous pouvez utiliser pour désactiver la fonction de désactivation automatique lorsque vous appuyez sur les boutons de la boîte de dialogue.

Il est également compatible avec Android 1.6, il n'utilise donc pas le OnShowListener (qui n'est disponible que pour l'API >= 8).

Ainsi, au lieu d'utiliser AlertDialog.Builder, vous pouvez utiliser ce CustomAlertDialogBuilder. La partie la plus importante est que vous ne devez pas appeler créer() mais seulement le show() méthode. J'ai ajouté des méthodes comme setCanceledOnTouchOutside() et setOnDismissListener afin que vous puissiez toujours les définir directement sur le constructeur.

Je l'ai testé sur Android 1.6, 2.x, 3.x et 4.x donc il devrait fonctionner assez bien. Si vous trouvez des problèmes, veuillez commenter ici.

package com.droidahead.lib.utils;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.view.View;
import android.view.View.OnClickListener;

public class CustomAlertDialogBuilder extends AlertDialog.Builder {
    /**
     * Click listeners
     */
    private DialogInterface.OnClickListener mPositiveButtonListener = null;
    private DialogInterface.OnClickListener mNegativeButtonListener = null;
    private DialogInterface.OnClickListener mNeutralButtonListener = null;

    /**
     * Buttons text
     */
    private CharSequence mPositiveButtonText = null;
    private CharSequence mNegativeButtonText = null;
    private CharSequence mNeutralButtonText = null;

    private DialogInterface.OnDismissListener mOnDismissListener = null;

    private Boolean mCancelOnTouchOutside = null;

    public CustomAlertDialogBuilder(Context context) {
        super(context);
    }

    public CustomAlertDialogBuilder setOnDismissListener (DialogInterface.OnDismissListener listener) {
        mOnDismissListener = listener;
        return this;
    }

    @Override
    public CustomAlertDialogBuilder setNegativeButton(CharSequence text, DialogInterface.OnClickListener listener) {
        mNegativeButtonListener = listener;
        mNegativeButtonText = text;
        return this;
    }

    @Override
    public CustomAlertDialogBuilder setNeutralButton(CharSequence text, DialogInterface.OnClickListener listener) {
        mNeutralButtonListener = listener;
        mNeutralButtonText = text;
        return this;
    }

    @Override
    public CustomAlertDialogBuilder setPositiveButton(CharSequence text, DialogInterface.OnClickListener listener) {
        mPositiveButtonListener = listener;
        mPositiveButtonText = text;
        return this;
    }

    @Override
    public CustomAlertDialogBuilder setNegativeButton(int textId, DialogInterface.OnClickListener listener) {
        setNegativeButton(getContext().getString(textId), listener);
        return this;
    }

    @Override
    public CustomAlertDialogBuilder setNeutralButton(int textId, DialogInterface.OnClickListener listener) {
        setNeutralButton(getContext().getString(textId), listener);
        return this;
    }

    @Override
    public CustomAlertDialogBuilder setPositiveButton(int textId, DialogInterface.OnClickListener listener) {
        setPositiveButton(getContext().getString(textId), listener);
        return this;
    }

    public CustomAlertDialogBuilder setCanceledOnTouchOutside (boolean cancelOnTouchOutside) {
        mCancelOnTouchOutside = cancelOnTouchOutside;
        return this;
    }

    @Override
    public AlertDialog create() {
        throw new UnsupportedOperationException("CustomAlertDialogBuilder.create(): use show() instead..");
    }

    @Override
    public AlertDialog show() {
        final AlertDialog alertDialog = super.create();

        DialogInterface.OnClickListener emptyOnClickListener = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) { }
        };

        // Enable buttons (needed for Android 1.6) - otherwise later getButton() returns null
        if (mPositiveButtonText != null) {
            alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, mPositiveButtonText, emptyOnClickListener);
        }

        if (mNegativeButtonText != null) {
            alertDialog.setButton(AlertDialog.BUTTON_NEGATIVE, mNegativeButtonText, emptyOnClickListener);
        }

        if (mNeutralButtonText != null) {
            alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, mNeutralButtonText, emptyOnClickListener);
        }

        // Set OnDismissListener if available
        if (mOnDismissListener != null) {
            alertDialog.setOnDismissListener(mOnDismissListener);
        }

        if (mCancelOnTouchOutside != null) {
            alertDialog.setCanceledOnTouchOutside(mCancelOnTouchOutside);
        }

        alertDialog.show();

        // Set the OnClickListener directly on the Button object, avoiding the auto-dismiss feature
        // IMPORTANT: this must be after alert.show(), otherwise the button doesn't exist..
        // If the listeners are null don't do anything so that they will still dismiss the dialog when clicked
        if (mPositiveButtonListener != null) {
            alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    mPositiveButtonListener.onClick(alertDialog, AlertDialog.BUTTON_POSITIVE);
                }
            });
        }

        if (mNegativeButtonListener != null) {
            alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    mNegativeButtonListener.onClick(alertDialog, AlertDialog.BUTTON_NEGATIVE);
                }
            });
        }

        if (mNeutralButtonListener != null) {
            alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    mNeutralButtonListener.onClick(alertDialog, AlertDialog.BUTTON_NEUTRAL);
                }
            });
        }

        return alertDialog;
    }   
}

EDIT Voici un petit exemple de l'utilisation du CustomAlertDialogBuilder :

// Create the CustomAlertDialogBuilder
CustomAlertDialogBuilder dialogBuilder = new CustomAlertDialogBuilder(context);

// Set the usual data, as you would do with AlertDialog.Builder
dialogBuilder.setIcon(R.drawable.icon);
dialogBuilder.setTitle("Dialog title");
dialogBuilder.setMessage("Some text..");

// Set your buttons OnClickListeners
dialogBuilder.setPositiveButton ("Button 1", new DialogInterface.OnClickListener() {
    public void onClick (DialogInterface dialog, int which) {
        // Do something...

        // Dialog will not dismiss when the button is clicked
        // call dialog.dismiss() to actually dismiss it.
    }
});

// By passing null as the OnClickListener the dialog will dismiss when the button is clicked.               
dialogBuilder.setNegativeButton ("Close", null);

// Set the OnDismissListener (if you need it)       
dialogBuilder.setOnDismissListener(new DialogInterface.OnDismissListener() {
    public void onDismiss(DialogInterface dialog) {
        // dialog was just dismissed..
    }
});

// (optional) set whether to dismiss dialog when touching outside
dialogBuilder.setCanceledOnTouchOutside(false);

// Show the dialog
dialogBuilder.show();

A la vôtre,

Yuvi

0 votes

Joli. Mais ça n'a pas marché pour moi. Le dialogue est rejeté malgré tout.

0 votes

Mmm, ça semble étrange. J'utilise cette méthode dans mon application et seuls les boutons pour lesquels j'appelle explicitement dialog.dismiss() font disparaître la boîte de dialogue. Sur quelle version d'Android testez-vous ? Pouvez-vous montrer votre code où vous avez utilisé le CustomAlertDialogBuilder ?

0 votes

Je pense que c'est dû à ceci : (appel de dialog.show() après onClickListener) pastebin.com/uLnSu5v7 Si je clique sur le bouton positif, ils sont renvoyés si le booléen est vrai...

30voto

Zhuiguang Liu Points 101

Voici quelque chose si vous utilisez DialogFragment - ce qui est de toute façon la manière recommandée de gérer les boîtes de dialogue.

Que se passe-t-il avec la méthode de l'AlertDialog setButton() (et j'imagine la même chose avec la méthode AlertDialogBuilder 's setPositiveButton() et setNegativeButton() ) est que le bouton que vous avez défini (par ex. AlertDialog.BUTTON_POSITIVE ) avec elle va en fait déclencher DEUX OnClickListener lorsqu'on appuie dessus.

Le premier étant DialogInterface.OnClickListener qui est un paramètre de setButton() , setPositiveButton() et setNegativeButton() .

L'autre est View.OnClickListener qui sera configuré pour renvoyer automatiquement votre AlertDialog lorsque l'un de ses boutons est pressé - et est réglé par AlertDialog lui-même.

Ce que vous pouvez faire est d'utiliser setButton() avec null comme le DialogInterface.OnClickListener pour créer le bouton, puis appelez votre méthode d'action personnalisée à l'intérieur du bouton View.OnClickListener . Par exemple,

@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
    AlertDialog alertDialog = new AlertDialog(getActivity());
    // set more items...
    alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, "OK", null);

    return alertDialog;
}

Ensuite, vous pouvez remplacer l'option par défaut AlertDialog Les boutons "s". View.OnClickListener (qui, sinon, ferait disparaître la boîte de dialogue) dans le champ DialogFragment 's onResume() méthode :

@Override
public void onResume()
{
    super.onResume();
    AlertDialog alertDialog = (AlertDialog) getDialog();
    Button okButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
    okButton.setOnClickListener(new View.OnClickListener() { 
        @Override
        public void onClick(View v)
        {
            performOkButtonAction();
        }
    });
}

private void performOkButtonAction() {
    // Do your stuff here
}

Vous devrez définir ce paramètre dans la section onResume() car getButton() retournera null jusqu'à ce que le dialogue ait été montré !

Ainsi, votre méthode d'action personnalisée ne sera appelée qu'une seule fois, et la boîte de dialogue ne sera pas rejetée par défaut.

8voto

Steve Points 68

Pour la pré API 8, j'ai résolu le problème en utilisant un drapeau booléen, un écouteur de rejet et en appelant dialog.show à nouveau si le contenu de l'editText n'était pas correct. Comme ceci :

case ADD_CLIENT:
        LayoutInflater factoryClient = LayoutInflater.from(this);
        final View EntryViewClient = factoryClient.inflate(
                R.layout.alert_dialog_add_client, null);

        EditText ClientText = (EditText) EntryViewClient
                .findViewById(R.id.client_edit);

        AlertDialog.Builder builderClient = new AlertDialog.Builder(this);
        builderClient
                .setTitle(R.string.alert_dialog_client)
                .setCancelable(false)
                .setView(EntryViewClient)
                .setPositiveButton("Save",
                        new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog,
                                    int whichButton) {
                                EditText newClient = (EditText) EntryViewClient
                                        .findViewById(R.id.client_edit);
                                String newClientString = newClient
                                        .getText().toString();
                                if (checkForEmptyFields(newClientString)) {
                                    //If field is empty show toast and set error flag to true;
                                    Toast.makeText(getApplicationContext(),
                                            "Fields cant be empty",
                                            Toast.LENGTH_SHORT).show();
                                    add_client_error = true;
                                } else {
                                    //Here save the info and set the error flag to false
                                    add_client_error = false;
                                }
                            }
                        })
                .setNegativeButton("Cancel",
                        new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog,
                                    int id) {
                                add_client_error = false;
                                dialog.cancel();
                            }
                        });
        final AlertDialog alertClient = builderClient.create();
        alertClient.show();

        alertClient
                .setOnDismissListener(new DialogInterface.OnDismissListener() {

                    @Override
                    public void onDismiss(DialogInterface dialog) {
                        //If the error flag was set to true then show the dialog again
                        if (add_client_error == true) {
                            alertClient.show();
                        } else {
                            return;
                        }

                    }
                });
        return true;

0 votes

Étrange que onDismiss ne soit pas appelé, le mien est au niveau api 21

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X