Dialogs / AlertDialogs: как «заблокировать выполнение», когда диалог открыт (в стиле .NET) - PullRequest
68 голосов
/ 08 января 2010

Исходя из .NET-среды, я сейчас пытаюсь понять, как работают диалоги в Android.

В .NET при вызове MessageBox.Show(...), который создает и показывает всплывающее диалоговое окно. В вызове Show я могу указать, какие кнопки должны быть доступны во всплывающем окне, например:

DialogResult myDialogResult = MessageBox.Show("My text here", "My caption here", MessageBoxButtons.YesNoCancel);

Как видите, вызов Show возвращает DialogResult при нажатии кнопки во всплывающем окне, сообщая мне, какая кнопка была нажата. Обратите внимание, что в .NET выполнение останавливается в строке, где выполняется вызов Show(...), поэтому он может возвращать значение при нажатии кнопки.

Если в приведенном выше примере нажать «Нет», myDialogResult будет равен

myDialogResult == DialogResult.No

Поскольку я считаю, что .NET-способ использования / создания всплывающих окон очень прост и интуитивно понятен, я бы хотел, чтобы этот способ также создавал всплывающие окна в Android.

Итак, вопрос в том, знает ли кто-нибудь, как «остановить выполнение», как с помощью MessageBox.Show, и затем возвращать значение при каждом нажатии кнопки (и диалог исчезает)?

Редактировать 1

Чтобы быть немного яснее:

Мне нужно, чтобы выполнение остановилось и подождите, пока пользователь не выберет кнопку для нажатия во всплывающем окне. Код, который следует за вызовом для отображения диалога, зависит от того, какая кнопка была нажата в диалоге.

Вот почему я не могу использовать то, что предлагают Эрих и Алекс, так как написание кода в методах onClick, как предлагается ниже, не будет работать. Причина в том, что я не могу продолжать «нормальное исполнение». Позвольте мне привести пример:

Позвольте мне привести пример:

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

Если бы я хотел, чтобы выполнение зависело от выбора во всплывающем окне, я бы как-то должен был сделать все переменные в «нормальном исполнении» (в данном случае nextStep) доступными в методах onClick, и это звучит как ад для меня.

Редактировать 2

Другим очевидным примером будет всплывающее окно с вопросом «Хотите продолжить» с параметрами «Да» и «Нет» .

Если пользователь нажимает «Да», весь метод должен быть прерван, в противном случае он должен продолжить выполнение. Как вы решаете это красиво?

Ответы [ 18 ]

46 голосов
/ 08 января 2010

Тед, ты не хочешь этого делать, на самом деле :) Основная причина в том, что если ты заблокируешь поток пользовательского интерфейса во время отображения диалога, ты заблокируешь поток, отвечающий за рисование и обработку событий ваш диалог. Что означает, что ваш диалог будет не отвечать. Вы также будете вызывать ANR, если пользователю потребуется более нескольких секунд, чтобы щелкнуть диалоговое окно.

Ответ Эриха именно то, что вам нужно. Я знаю, что это не то, что вы хотите , но это не имеет значения. Мы разработали Android, чтобы запретить разработчикам писать синхронные диалоги, чтобы у вас не было особого выбора.

19 голосов
/ 28 апреля 2012

Упрощенная версия ответа Даниила выше. Эта функция получает «да» или «нет» от пользователя в диалоговом окне с предупреждением, но ее можно легко изменить, чтобы получить другой ввод.

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;
}
19 голосов
/ 08 января 2010

В Android структура отличается от .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();

Откроется диалоговое окно с двумя кнопками, и вы будете обрабатывать нажатия кнопок с помощью обратных вызовов. Возможно, вам удастся написать некоторый код, чтобы синтаксис был более похож на .NET, но жизненный цикл диалога довольно тесно связан с Activity, так что в итоге может возникнуть больше проблем, чем оно того стоит. Дополнительные ссылки на диалоги: здесь .

11 голосов
/ 08 января 2010

В Android диалоги асинхронные, поэтому вам придется структурировать код немного по-другому.

Итак, в C # ваша логика запускает что-то вроде этого в псевдокоде:

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
}

Для Android это должно быть менее аккуратно. Думайте об этом так. У вас будет OnClickListener вот так:

public void onClick(DialogInterface dialog, int whichButton) {
   if (whichButton == BUTTON_POSITIVE) {
      doOptionYes();
   }
   else if (whichButton == BUTTON_NEGATIVE) {
      doOptionNo();
   }
}

Что затем поддерживается следующими методами:

void doOptionYes() {
    //do stuff for yes
    endThings();
}

void doOptionNo() {
    //do stuff for no
    endThings();
}

void endThings() {
    //clean up here
}

Так что одним методом было четыре. Боюсь, это может показаться не таким аккуратным, но вот как это работает.

8 голосов
/ 30 сентября 2011
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;
    }

}
5 голосов
/ 07 июля 2012

Разработчики Android и iOS решили, что они достаточно мощные и умные, чтобы отказаться от концепции Modal Dialog (которая была на рынке уже много-много лет и раньше никого не беспокоила), к сожалению для нас. Я считаю, что для Android есть обходной путь - поскольку вы можете показывать диалог из не-пользовательского потока, используя класс Runnable, в этом потоке должен быть способ подождать, пока диалог не будет завершен.

Edit: Вот мое решение, оно прекрасно работает:

    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;
    }
5 голосов
/ 08 января 2010

При попытке оптимизировать память и производительность диалоги в Android являются асинхронными (по этой причине они также управляются). Исходя из мира Windows, вы привыкли к модальным диалогам. Диалоги Android являются модальными, но больше похожи на немодальные, когда дело доходит до исполнения. Выполнение не останавливается после отображения диалогового окна.

Лучшее описание Dialogs в Android, которое я видел, - «Pro Android» http://www.apress.com/book/view/1430215968

Это не идеальное объяснение, но оно должно помочь вам разобраться в различиях между диалогами в Windows и Android. В Windows вы хотите сделать A, задать вопрос с помощью диалогового окна, а затем сделать B или C. В android design A со всем кодом, необходимым для B и C в onClick () OnClickListener (s) для диалога , Затем сделайте A и запустите диалог. Вы сделали с A! Когда пользователь нажимает кнопку B или C, выполняется.

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
4 голосов
/ 16 декабря 2010

Тед, как вы, наверное, узнали, к сожалению, вы не можете сделать это на Android. Диалоги являются модальными, но асинхронными, и это определенно нарушит последовательность, которую вы пытаетесь установить, как вы бы сделали в .NET (или Windows в этом отношении). Вам нужно будет перевернуть свой код и сломать некоторую логику, которой было бы очень легко следовать на основе вашего примера.

Еще один очень простой пример - сохранение данных в файле, только чтобы узнать, что файл уже существует и просит перезаписать его или нет. Вместо отображения диалога и наличия оператора if для воздействия на результат (Да / Нет) вам придется использовать обратные вызовы (называемые слушателями в Java) и разделить логику на несколько функций.

В Windows, когда отображается диалоговое окно, насос сообщений продолжает работать в фоновом режиме (удерживается только текущее обрабатываемое сообщение), и это прекрасно работает. Это позволяет пользователям перемещать ваше приложение и позволяет перекрашивать, когда вы, например, отображаете диалоговое окно. WinMo поддерживает синхронные модальные диалоги, как и BlackBerry, но не Android.

2 голосов
/ 30 декабря 2011

Самое чистое и простое решение - использовать собственный интерфейс слушателя, чтобы при нажатии кнопки «ОК» ваш слушатель вызывался с возвращаемым значением. Этот метод не делает ничего особенного или сложного и уважает принципы Android.

Определите ваш интерфейс слушателя следующим образом:

public interface EditListener
/* Used to get an integer return value from a dialog
 * 
 */
{
 void returnValue(int value); 
}

Для своего приложения я создал класс EditValue, который использует AlertDialog и который я вызываю всякий раз, когда хочу изменить целочисленное значение. Обратите внимание, как интерфейс EditListener передается в качестве аргумента этому коду. Когда пользователь нажимает кнопку ОК, значение будет возвращено в ваш код вызова с помощью метода 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();
 }
}

Наконец, когда вы используете EditValue, вам нужно объявить свой EditListener, и теперь вы можете получить доступ к возвращаемому значению и делать то, что вы хотите сделать:

 new EditValue(main,AnchorManager.anchorageLimit,
               main.res.getString(R.string.config_anchorage_limit),null,
               new EditListener()
 {public void returnValue(int value) {AnchorManager.anchorageLimit= value;}
  }
 );
1 голос
/ 07 марта 2012
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...