android - pie - Diálogos/AlertDialogs: cómo "bloquear la ejecución" mientras el diálogo está activo(estilo.NET)
android pie (18)
En Android, la estructura es diferente de .NET:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Hello!")
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// Handle Ok
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// Handle Cancel
}
})
.create();
Te conseguirá un diálogo con dos botones y manejarás los clics del botón con devoluciones de llamada. Es posible que pueda escribir algún código para hacer que la sintaxis se parezca más a .NET, pero el ciclo de vida del diálogo está muy entrelazado con la Activity
, por lo que al final puede ser más problemático de lo que vale. Las referencias de diálogo adicionales están here .
Procedente del entorno .NET. Ahora estoy buscando comprender cómo funcionan los diálogos en Android.
En .NET, al llamar a MessageBox.Show (...) que crea y muestra un cuadro de diálogo emergente. En la llamada a Mostrar, puedo especificar qué botones deberían estar disponibles en la ventana emergente, por ejemplo:
DialogResult myDialogResult = MessageBox.Show("My text here", "My caption here", MessageBoxButtons.YesNoCancel);
Como puede ver, la llamada a Mostrar devuelve un DialogResult cuando se presiona un botón en la ventana emergente, informándome en qué botón se hizo clic. Tenga en cuenta que en .NET, la ejecución se detiene en la línea donde se realiza la llamada a Mostrar (...) , por lo que puede devolver el valor cuando se presiona un botón.
Si en el ejemplo anterior presiono "No", myDialogResult será igual a
myDialogResult == DialogResult.No
Desde que encuentro que la forma de .NET de usar / crear ventanas emergentes es muy fácil e intuitiva, me gustaría esa manera de crear ventanas emergentes en Android también.
Entonces, la pregunta es si alguien sabe cómo "detener la ejecución" como con el MessageBox.Show, y luego devolver un valor cada vez que se presiona el botón (y el diálogo desaparece).
Saludos
EDICION 1: Para ser un poco más claro:
Necesito detener la ejecución y esperar hasta que el usuario haya elegido un botón para hacer clic en la ventana emergente. El código que sigue a la llamada para mostrar el cuadro de diálogo depende de en qué botón se haga clic en el cuadro de diálogo.
Es por eso que no puedo usar lo que sugieren Erich y Alex, ya que escribir código en los métodos onClick como se sugiere a continuación no va a funcionar. La razón es que no puedo continuar la "ejecución normal". Déjame tomar un ejemplo:
Déjame tomar un ejemplo:
int nextStep = 0; // this variable will not be reached from within the onClick-methods
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Hello!")
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
nextStep = 1; // *** COMPILER ERROR!! ***
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
nextStep = 2; // *** COMPILER ERROR!! ***
}
})
.create().show();
if (nextStep == 1)
{
// then do some damage
}
else if (nextStep == 2
// dont do damage
Si quisiera que la ejecución dependiera de la opción en la ventana emergente, de alguna manera tendría que hacer todas las variables en la "ejecución normal" (en este caso nextStep ) disponible en los métodos onClick, y eso me suena como el infierno. .
EDICION 2 :
Otro ejemplo obvio sería una ventana emergente que pregunta "¿Desea continuar?" Con las opciones "Sí" y "No" .
Si el usuario presiona "Sí", el método completo debería abortarse, de lo contrario debería continuar la ejecución. ¿Cómo lo resuelves bien?
Saludos
En Android, los cuadros de diálogo son asincrónicos, por lo que tendrá que estructurar su código de forma un poco diferente.
Entonces en C # tu lógica funcionaba algo así en pseudocódigo:
void doSomeStuff() {
int result = showDialog("Pick Yes or No");
if (result == YES) {
//do stuff for yes
}
else if (result == NO) {
//do stuff for no
}
//finish off here
}
Para Android, tendrá que ser menos prolijo. Piénsalo así. Tendrás un OnClickListener
así:
public void onClick(DialogInterface dialog, int whichButton) {
if (whichButton == BUTTON_POSITIVE) {
doOptionYes();
}
else if (whichButton == BUTTON_NEGATIVE) {
doOptionNo();
}
}
Que luego es compatible con los siguientes métodos:
void doOptionYes() {
//do stuff for yes
endThings();
}
void doOptionNo() {
//do stuff for no
endThings();
}
void endThings() {
//clean up here
}
Entonces, ¿cuál era un método ahora es cuatro. Puede que no parezca tan ordenado, pero así es como funciona, me temo.
En un intento de optimizar la memoria y los diálogos de rendimiento en Android son asincrónicos (también se gestionan por este motivo). Viniendo del mundo de Windows, estás acostumbrado a los diálogos modales. Los diálogos de Android son modales, pero más como no modales en lo que respecta a la ejecución. La ejecución no se detiene después de mostrar un diálogo.
La mejor descripción de Diálogos en Android que he visto está en "Pro Android" http://www.apress.com/book/view/1430215968
Esta no es una explicación perfecta, pero debería ayudarte a enfocar tu cerebro en las diferencias entre los diálogos en Windows y Android. En Windows, usted desea hacer A, hacer una pregunta con un diálogo, y luego hacer B o C. En el diseño A de Android con todo el código que necesita para B y C en onClick () de OnClickListener (s) para el diálogo . Luego, haz A y ejecuta el diálogo. ¡Terminaste con A! Cuando el usuario hace clic en un botón, B o C se ejecutarán.
Windows
-------
A code
launch dialog
user picks B or C
B or C code
done!
Android
-------
OnClick for B code (does not get executed yet)
OnClick for C code (does not get executed yet)
A code
launch dialog
done!
user picks B or C
Esta es la forma más simple:
new AlertDialog.Builder(this).setTitle("title").setMessage("message").create().show();
Estoy usando Xamarin.Android (MonoDroid), y tengo requisitos para desarrollar el cuadro de confirmación de bloqueo de UI. No voy a discutir con el cliente porque puedo ver buenas razones de por qué lo quieren ( detalles aquí ), así que necesito implementar esto. Intenté @Daniel y @MindSpiker arriba, pero estos no funcionaron en MonoForAndroid, en el momento en que se envía el mensaje entre los hilos, la aplicación se cuelga. Supongo que tiene algo que ver con el mapeo de Xamarin.
Terminé creando un hilo separado del hilo de la interfaz de usuario y luego bloqueándolo y esperando la respuesta del usuario de la siguiente manera:
// (since the controllers code is shared cross-platforms)
protected void RunConfirmAction(Action runnableAction)
{
if (runnableAction != null)
{
if (Core.Platform.IsAndroid)
{
var confirmThread = new Thread(() => runnableAction());
confirmThread.Start();
}
else
{
runnableAction();
}
}
}
// The call to the logout method has now changed like this:
RunConfirmAction(Logout);
// the implemtation of the MessageBox waiting is like this:
public DialogResult MessageBoxShow(string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
{
if (_CurrentContext != null && _CurrentContext.Screen != null && MainForm.MainActivity != null)
{
Action<bool> callback = OnConfirmCallBack;
_IsCurrentlyInConfirmProcess = true;
Action messageBoxDelegate = () => MessageBox.Show(((Activity)MainForm.MainActivity), callback, message, caption, buttons);
RunOnMainUiThread(messageBoxDelegate);
while (_IsCurrentlyInConfirmProcess)
{
Thread.Sleep(1000);
}
}
else
{
LogHandler.LogError("Trying to display a Message box with no activity in the CurrentContext. Message was: " + message);
}
return _ConfirmBoxResult ? DialogResult.OK : DialogResult.No;
}
private void OnConfirmCallBack(bool confirmResult)
{
_ConfirmBoxResult = confirmResult;
_IsCurrentlyInConfirmProcess = false;
}
private bool _ConfirmBoxResult = false;
private bool _IsCurrentlyInConfirmProcess = false;
Todos los detalles sobre cómo hacer esto se pueden encontrar en mi blog aquí
La solución más limpia y simple es usar su propia interfaz de escucha para que cuando el usuario haga clic en el botón Aceptar, se llame a su oyente con el valor de retorno. Este método no hace nada sofisticado o complicado y respeta los principios de android.
Defina su interfaz de escucha de la siguiente manera:
public interface EditListener
/* Used to get an integer return value from a dialog
*
*/
{
void returnValue(int value);
}
Para mi aplicación, creé una clase EditValue que utiliza AlertDialog y a la que llamo cada vez que deseo editar un valor entero. Observe cómo la interfaz EditListener se pasa como un argumento para este código. Cuando el usuario hace clic en el botón Aceptar, el valor se devolverá a su código de llamada a través del método EditListener:
public final class EditValue
/* Used to edit a value using an alert dialog
* The edited value is returned via the returnValue method of the supplied EditListener interface
* Could be modified with different constructors to edit double/float etc
*/
{
public EditValue(final Activity parent, int value, String title, String message,
final EditListener editListener)
{AlertDialog.Builder alert= new AlertDialog.Builder(parent);
if(title==null) title= message;
else if(message==null) message= title;
if(title!=null) alert.setTitle(title);
if(message!=null) alert.setMessage(message);
// Set an EditText view to get user input
final EditText input = new EditText(parent);
input.setText(String.valueOf(value));
input.setInputType(InputType.TYPE_CLASS_NUMBER);
alert.setView(input);
alert.setPositiveButton("OK",new DialogInterface.OnClickListener()
{public void onClick(DialogInterface dialog, int which)
{try
{int newValue= Integer.valueOf(input.getText().toString());
editListener.returnValue(newValue);
dialog.dismiss();
}catch(NumberFormatException err) { }
}
});
alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener()
{public void onClick(DialogInterface dialog, int which)
{dialog.dismiss();
}
});
alert.show();
}
}
Finalmente, cuando usa EditValue necesita declarar EditListener y ahora puede acceder al valor de retorno y hacer lo que desea hacer:
new EditValue(main,AnchorManager.anchorageLimit,
main.res.getString(R.string.config_anchorage_limit),null,
new EditListener()
{public void returnValue(int value) {AnchorManager.anchorageLimit= value;}
}
);
Los desarrolladores de Android e iOS decidieron que son lo suficientemente poderosos e inteligentes como para rechazar la concepción de Modal Dialog (que ya llevaba muchos años en el mercado y no molestaba a nadie antes), desafortunadamente para nosotros. Creo que existe una solución para Android, ya que puede mostrar el diálogo desde un subproceso que no es de la interfaz de usuario mediante la clase Ejecutable, debe haber una forma de esperar en ese subproceso (no-ui) hasta que finalice el diálogo.
Editar: Aquí está mi solución, funciona genial:
int pressedButtonID;
private final Semaphore dialogSemaphore = new Semaphore(0, true);
final Runnable mMyDialog = new Runnable()
{
public void run()
{
AlertDialog errorDialog = new AlertDialog.Builder( [your activity object here] ).create();
errorDialog.setMessage("My dialog!");
errorDialog.setButton("My Button1", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
pressedButtonID = MY_BUTTON_ID1;
dialogSemaphore.release();
}
});
errorDialog.setButton2("My Button2", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
pressedButtonID = MY_BUTTON_ID2;
dialogSemaphore.release();
}
});
errorDialog.setCancelable(false);
errorDialog.show();
}
};
public int ShowMyModalDialog() //should be called from non-UI thread
{
pressedButtonID = MY_BUTTON_INVALID_ID;
runOnUiThread(mMyDialog);
try
{
dialogSemaphore.acquire();
}
catch (InterruptedException e)
{
}
return pressedButtonID;
}
Pruebe esto en un subproceso (no en el UI-Thread):
final CountDownLatch latch = new CountDownLatch(1);
handler.post(new Runnable() {
@Override
public void run() {
OnClickListener okListener = new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
latch.countDown();
}
};
AlertDialog dialog = new AlertDialog.Builder(context).setTitle(title)
.setMessage(msg).setPositiveButton("OK", okListener).create();
dialog.show();
}
});
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
Reescribiendo:
Existen diferencias radicales entre el entorno móvil y de escritorio, así como también entre la forma en que se desarrollaron las aplicaciones hasta hace unos años y en la actualidad:
a) los dispositivos móviles necesitan conservar energía. Parte del valor que ofrecen. Entonces necesitas guardar recursos. Los hilos son un recurso costoso. Detener el progreso de un hilo es un desperdicio inaceptable de este recurso.
b) Hoy en día el usuario es mucho más exigente. Para ayudarlos, creemos que merece tener una CPU completa y el gasto de energía más pequeño posible. Su aplicación no está sola en el dispositivo, hay un número desconocido de otras aplicaciones que se ejecutan al mismo tiempo y su aplicación no es necesariamente la más urgente.
c) los bloqueos a nivel de sistema no son una opción: un dispositivo móvil funciona con un número de eventos y servicios en segundo plano y no es correcto que una aplicación bloquee alguno de ellos.
Piense en el usuario que recibe una llamada mientras su "bloqueo del sistema" está funcionando ...
En base a los hechos anteriores, la respuesta a la pregunta propuesta es:
- ¿Existe una forma viable de construir un diálogo que bloquee el hilo principal hasta que el usuario responda?
No. Las soluciones provisionales empeoran la experiencia del usuario y puede cometer el error de culpar al sistema en sí. Esto es injusto y penaliza a la plataforma y a todos sus desarrolladores.
- ¿Hay alguna manera de bloquear todo el sistema con un diálogo?
No. Esto está estrictamente prohibido en la plataforma. Ninguna aplicación puede interferir con el funcionamiento del sistema u otras aplicaciones.
- Necesito refactorizar mi aplicación, o repensar mi forma de programar para adaptarme a la arquitectura del sistema móvil Android.
Sí. Incluyendo este aspecto.
Soy nuevo en el mundo Android / Java y me sorprendí al descubrir aquí (a menos que no entienda lo que leo) que los diálogos modales no funcionan. Por algunas razones muy oscuras para mí en este momento, obtuve este equivalente a "ShowMessage" con un botón ok que funciona en mi tableta de una manera muy modal.
Desde mi módulo TDialogs.java:
class DialogMes
{
AlertDialog alertDialog ;
private final Message NO_HANDLER = null;
public DialogMes(Activity parent,String aTitle, String mes)
{
alertDialog = new AlertDialog.Builder(parent).create();
alertDialog.setTitle(aTitle);
alertDialog.setMessage(mes) ;
alertDialog.setButton("OK",NO_HANDLER) ;
alertDialog.show() ;
}
}
Aquí hay parte del código de prueba:
public class TestDialogsActivity extends Activity implements DlgConfirmEvent
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button btShowMessage = (Button) findViewById(R.id.btShowMessage);
btShowMessage.setOnClickListener(new View.OnClickListener() {
public void onClick(View view)
{
DialogMes dlgMes = new DialogMes( TestDialogsActivity.this,"Message","life is good") ;
}
});
También implementé un diálogo modal Sí / No siguiendo el enfoque de interfaz sugerido anteriormente por JohnnyBeGood, y funciona bastante bien también.
Corrección:
Mi respuesta no es relevante para la pregunta que malentendí. Por alguna razón, interpreté a M. Romain Guy "no quieres hacer eso" como no no a los diálogos modales. Debería haber leído: "no quieres hacer eso ... de esta manera".
Me disculpo.
Ted, como probablemente descubras, desafortunadamente no puedes hacer eso en Android. Los cuadros de diálogo son modales, pero asíncronos, y definitivamente interrumpirán la secuencia que intenta establecer como lo hubiera hecho en .NET (o Windows para el caso). Tendrá que girar su código y romper una lógica que habría sido muy fácil de seguir según su ejemplo.
Otro ejemplo muy simple es guardar datos en un archivo, solo para descubrir que el archivo ya está allí y pedir que se sobrescriba o no. En lugar de mostrar un diálogo y tener una instrucción if para actuar sobre el resultado (Sí / No), tendrá que usar devoluciones de llamada (llamadas escuchas en Java) y dividir su lógica en varias funciones.
En Windows, cuando se muestra un cuadro de diálogo, la bomba de mensajes continúa en segundo plano (solo el mensaje actual que se está procesando está en espera), y eso funciona bien. Eso permite a los usuarios mover su aplicación y dejar que se vuelva a pintar mientras visualiza un cuadro de diálogo, por ejemplo. WinMo admite diálogos modales sincrónicos, también BlackBerry, pero no Android.
Ted, no quieres hacer esto, realmente :) La razón más importante es que si bloqueas el hilo de UI mientras estás mostrando un Diálogo, bloquearás el hilo que está a cargo de dibujar y manejar los eventos de tu Diálogo. Lo que significa que su diálogo no responderá. También provocará ANR si el usuario tarda más de unos pocos segundos en hacer clic en el cuadro de diálogo.
La respuesta de Erich es exactamente lo que necesitas. Sé que no es lo que quieres , pero eso no importa. Diseñamos Android para evitar que los desarrolladores escriban diálogos sincrónicos, por lo que no tiene muchas opciones.
Una versión simplificada de la respuesta de Daniel anterior. Esta función obtiene un sí o un no del usuario en un diálogo de alerta, pero podría modificarse fácilmente para obtener otra información.
private boolean mResult;
public boolean getYesNoWithExecutionStop(String title, String message, Context context) {
// make a handler that throws a runtime exception when a message is received
final Handler handler = new Handler() {
@Override
public void handleMessage(Message mesg) {
throw new RuntimeException();
}
};
// make a text input dialog and show it
AlertDialog.Builder alert = new AlertDialog.Builder(context);
alert.setTitle(title);
alert.setMessage(message);
alert.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
mResult = true;
handler.sendMessage(handler.obtainMessage());
}
});
alert.setNegativeButton("No", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
mResult = false;
handler.sendMessage(handler.obtainMessage());
}
});
alert.show();
// loop till a runtime exception is triggered.
try { Looper.loop(); }
catch(RuntimeException e2) {}
return mResult;
}
solo para responder a su pregunta ... por cierto que tengo 9 meses de retraso: D ... hay una "solución alternativa" 4 este tipo de problemas. es decir
new AlertDialog.Builder(some_class.this).setTitle("bla").setMessage("bla bla").show();
wait();
simplemente agregue wait ();
y ellos en OnClickListener comienzan la clase de nuevo con notify () algo como esto
@Override
public void onClick(DialogInterface dialog, int item) {
Toast.makeText(getApplicationContext(), "test", Toast.LENGTH_LONG).show();
**notify**();
dialog.cancel();
}
la misma solución va 4 tostadas y otras llamadas asincrónicas en Android
usted hace clic en oyentes a sus botones. disimular el diálogo y hacer su acción. No es necesario detener nada
protected Dialog onCreateDialog(int id) {
return new AlertDialog.Builder(this).setTitle(R.string.no_connection).setIcon(android.R.drawable.ic_dialog_alert).setView(textEntryView).setPositiveButton(R.string.exit, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int whichButton) {
// Here goes what you want to do
}
})
}
llamar a - ex - showDialog (DIALOG_ERROR_PREF);
Más here
PasswordDialog dlg = new PasswordDialog(this);
if(dlg.showDialog() == DialogResult.OK)
{
//blabla, anything your self
}
public class PasswordDialog extends Dialog
{
int dialogResult;
Handler mHandler ;
public PasswordDialog(Activity context, String mailName, boolean retry)
{
super(context);
setOwnerActivity(context);
onCreate();
TextView promptLbl = (TextView) findViewById(R.id.promptLbl);
promptLbl.setText("Input password/n" + mailName);
}
public int getDialogResult()
{
return dialogResult;
}
public void setDialogResult(int dialogResult)
{
this.dialogResult = dialogResult;
}
/** Called when the activity is first created. */
public void onCreate() {
setContentView(R.layout.password_dialog);
findViewById(R.id.cancelBtn).setOnClickListener(new android.view.View.OnClickListener() {
@Override
public void onClick(View paramView)
{
endDialog(DialogResult.CANCEL);
}
});
findViewById(R.id.okBtn).setOnClickListener(new android.view.View.OnClickListener() {
@Override
public void onClick(View paramView)
{
endDialog(DialogResult.OK);
}
});
}
public void endDialog(int result)
{
dismiss();
setDialogResult(result);
Message m = mHandler.obtainMessage();
mHandler.sendMessage(m);
}
public int showDialog()
{
mHandler = new Handler() {
@Override
public void handleMessage(Message mesg) {
// process incoming messages here
//super.handleMessage(msg);
throw new RuntimeException();
}
};
super.show();
try {
Looper.getMainLooper().loop();
}
catch(RuntimeException e2)
{
}
return dialogResult;
}
}
UserSelect =null
AlertDialog.Builder builder = new Builder(ImonaAndroidApp.LoginScreen);
builder.setMessage("you message");
builder.setPositiveButton("OK", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
UserSelect = true ;
}
});
builder.setNegativeButton("Cancel", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
UserSelect = false ;
}
});
// in UI thread
builder.show();
// wait until the user select
while(UserSelect ==null);