Dialogs/AlertDialogs: 如何在对话框启动时“阻止执行”(. NET 风格)

由于来自.NET 环境,我现在希望了解在 Android 中 Dialogs 是如何工作的。

进去。NET,当调用创建并显示弹出对话框的 MessageBox.Show(...)时。在对 Show 的调用中,我可以指定弹出窗口中应该有哪些按钮,例如:

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

如您所见,当弹出窗口中按下一个按钮时,Show 调用返回一个 DialogResult,通知我单击了哪个按钮。注意。NET 中,执行在调用 Show(...)的行停止,因此当按下按钮时它可以返回值。

如果我在上面的例子中按“ No”,myDialogResult 将等于

myDialogResult == DialogResult.No

自从我发现。NET 的方式使用/创建弹出窗口非常容易和直观,我想这种方式创建弹出窗口在 Android 太。

因此,问题是是否有人知道如何像使用 MessageBox.Show那样“暂停执行”,然后在按下按钮时返回一个值(对话框消失) ?

编辑1

说得更清楚一点:

我需要的执行停止和等待,直到用户选择了一个按钮,以点击弹出式窗口。显示对话框的调用后面的代码取决于在对话框中单击了哪个按钮。

这就是为什么我不能使用 Erich 和 Alex 的建议,因为按照下面的建议在 onClick-method 中编写代码是行不通的。原因是我不能继续“正常执行”。让我举个例子:

让我举个例子:

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-method 中使用,这对我来说就像地狱一样。

编辑2

另一个明显的例子是弹出窗口询问带有选项 “是的”“不”“你还想继续吗”

如果用户按“是”,整个方法应该被终止,否则它应该继续执行。你要怎么解决这个问题?

105938 次浏览

你设置 onclick 监听器到你的按钮。消除对话框,做你的动作。不需要停止任何事情

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
}


})
}

调用-ex-showDialog (DIALOG _ ERROR _ PREF) ;

更多 Http://developer.android.com/guide/topics/ui/dialogs.html

在 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非常紧密地交织在一起,所以到最后,它可能会带来更多的麻烦,而不是它的价值。其他对话框引用是 给你

这是最简单的方法:

new AlertDialog.Builder(this).setTitle("title").setMessage("message").create().show();

为了优化 Android 中的内存和性能对话框,它们是异步的(也是因为这个原因)。来自 Windows 世界的您已经习惯了模态对话框。Android 对话框是模态的,但在执行时更像是非模态的。显示对话框后执行不会停止。

我所见过的最好的 Android 对话框描述是在“ Pro Android”http://www.apress.com/book/view/1430215968

这不是一个完美的解释,但是它应该能够帮助你理解 Windows 和 Android 中对话框之间的区别。在 Windows 中,你想做 A,用对话框提问,然后做 B 或 C。在 android 设计 A 中,在对话框的 OnClickListener 的 onClick ()中包含 B 和 C 所需的所有代码。然后做 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

Ted,你不会想这么做的,真的:)最大的原因是如果你在显示对话框的时候阻塞了 UI 线程,你就会阻塞负责绘制和处理对话框事件的线程。这意味着您的对话将无响应。如果用户单击对话框需要花费超过几秒钟的时间,您还将导致 ANR。

埃里克的回答正是你需要的。我知道这不是你的 想要,但这不重要。我们设计 Android 是为了防止开发人员编写同步对话框,所以你实际上没有太多的选择。

在 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
}

所以一个方法现在变成了四个。也许看起来不那么整洁,但恐怕这就是它的运作方式。

只是为了回答你的问题... 顺便说一下,对不起,我迟到了9个月: D... 有一个“变通”4这种问题。即。

new AlertDialog.Builder(some_class.this).setTitle("bla").setMessage("bla bla").show();
wait();

只需加入 等等() ;

然后在 OnClickListener 中用类似于 通知()的东西再次启动类

@Override
public void onClick(DialogInterface dialog, int item) {
Toast.makeText(getApplicationContext(), "test", Toast.LENGTH_LONG).show();
**notify**();
dialog.cancel();
}

同样的解决方案可以在 android 中进行4次祝酒和其他异步调用

泰德,你可能已经发现了很不幸,在安卓系统上是做不到的。对话框是模态的,但是是异步的,它肯定会破坏您试图建立的顺序,就像您在上面做的那样。NET (或者 Windows)。您将不得不扭转您的代码,并打破一些逻辑,这将是非常容易遵循的基于您的例子。

另一个非常简单的例子是将数据保存到一个文件中,却发现该文件已经存在并要求覆盖它。与显示对话框和使用 if 语句处理结果(Yes/No)不同,您将不得不使用回调(在 Java 中称为侦听器)并将逻辑分割为几个函数。

在 Windows 中,当显示对话框时,消息泵在后台继续运行(只有当前正在处理的消息处于等待状态) ,这样工作得很好。这允许用户移动您的应用程序,让是重新绘制,而您显示一个对话框,例如。WinMo 支持同步模式对话框,BlackBerry 也支持,但 Android 不支持。

在线程(而不是用户界面线程)中尝试:

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();
}
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;
}


}

最简洁的解决方案是使用您自己的侦听器接口,这样当用户单击 ok 按钮时,您的侦听器就会被返回值调用。这种方法没有任何花哨或复杂的,并尊重机器人的原则。

定义侦听器接口如下:

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

对于我的应用程序,我创建了一个使用 AlertDialog 的 EditValue 类,每当我想编辑一个整数值时,我都会调用它。注意如何将 EditListener 接口作为参数传递给此代码。当用户单击 OK 按钮时,该值将通过 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;}
}
);

以上是丹尼尔答案的简化版。该函数在警告对话框中从用户那里获得是或否,但是可以很容易地修改以获得其他输入。

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;
}

我是 Android/Java 世界的新手,很惊讶地发现这里(除非我不明白我读的是什么)模态对话框不起作用。由于一些对我来说非常模糊的原因,我得到了这个“ ShowMessage”等同于一个 OK 按钮,它在我的平板电脑上以一种非常模式化的方式工作。

来自我的 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() ;
}
}

下面是测试代码的一部分:

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") ;
}
});

我还按照 JohnnyBeGood 建议的接口方法实现了一个模态 Yes/No 对话框,它也工作得很好。

更正:

我的回答与我误解的问题无关。出于某种原因,我把 M。 Romain Guy 的“你不会想这么做的”理解为对模态对话的拒绝。我应该读一下: “你不会想那样做的... ... 这样做”。

我道歉。

Android 和 iOS 的开发人员认为他们足够强大和聪明,可以拒绝 Modal Dialog 的概念(这个概念已经在市场上出现了很多年,之前从未困扰过任何人) ,这对我们来说很不幸。我相信 Android 还是有办法的——因为你可以使用 Runnable 类从非 ui 线程显示对话框,所以应该有一种方法可以在该线程(非 ui)中等待对话框完成。

编辑: 这是我的解决方案,效果很好:

    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;
}
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);

改写:

移动环境和桌面环境之间,以及应用程序开发到几年前和今天的方式之间,存在着根本的差异:

A)流动装置需要节省能源。他们提供的价值的一部分。所以你需要节约资源。线程是一种昂贵的资源。停止线程的进程是对此资源的不可接受的浪费。

B)现在用户要求更高了。为了帮助他们,我们相信它应该有完整的工作 CPU 和最小的能源消耗。它的应用程序并不是唯一在设备上运行的应用程序,同时运行的其他应用程序数量不详,你的应用程序也不一定是最紧急的。

C)系统级锁不是一个选项: 一个移动设备在后台处理许多事件和服务,并且它们中的任何一个都可以被应用程序锁定,这是不正确的。

想象一下,当你的“系统锁”工作时,用户正在接听电话..。

基于上述事实,对拟议问题的答复如下:

  • 有一个 可行的方式来建立一个对话框,阻塞主线程,直到从用户的响应?

没有。变通方法使用户体验变得更差,并且可能会错误地将责任归咎于系统本身。这是不公平的,并且惩罚了平台及其所有开发人员。

  • 有没有办法通过对话屏蔽整个系统?

没有。这是严格禁止在站台上进行的。任何应用程序都不能干扰系统或其他应用程序的操作。

  • 我需要重构我的应用程序,或者重新思考我的编程方式,以适应 Android 移动系统架构。

是的,包括这方面。

我在用 Xamarin。Android (MonoDroid) ,我有开发 UI 阻塞确认框的要求。我不打算与客户争论,因为我可以看到他们为什么想要这个(详情请浏览此网页)的好理由,所以我需要实现这一点。我尝试了上面的@Daniel 和@MindSpiker,但是这些在 MonoForAndroid 上不起作用,一旦消息在线程之间发送,应用程序就会崩溃。我假设它与 Xamarin 映射有关。

最后,我从 UI 线程创建了一个单独的线程,然后阻止它,并等待用户响应,如下所示:

// (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;

关于如何做到这一点的详细信息可以在我的博客文章 给你中找到

作为对 StackOverflow 社区的感谢,让我和你们分享一些好的东西,我在 Android 代码中使用了上面的例子来进行 HTTP 调用,现在它转换成了 MODAL,而不是通常的额外线程,以及通过使用一些奇怪的线程来合并线程的复杂性。(2020年10月15日)

public JSONArray genericHttpModal(Context context, final String url, final JSONObject request) {


this.url = url;
genericHttpRequestQueue = Volley.newRequestQueue(context);
class MyJsonReturn {
JSONArray returnJsonArray;


public void set(JSONArray i) {
returnJsonArray = i;
}


public void set(String i) {
try {
returnJsonArray.put(0, i);
} catch (JSONException e) {
e.printStackTrace();
}
}


public JSONArray get() {
return returnJsonArray;
}
}


final MyJsonReturn mymy = new MyJsonReturn();


// Positive Response / HTTP OK.
final Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
throw new RuntimeException();
}
};


final Response.Listener responseListener = new Response.Listener<String>() {
@Override
public void onResponse(String response) {
try {
mymy.set(new JSONArray(response));
} catch (JSONException e) {
mymy.set("[{\"JSONException:\"" + e.getMessage() + "\"}]");
}
handler.sendMessage(handler.obtainMessage());
}
};


// Negative Response / HTTP NOT OK
final Response.ErrorListener errorListener = new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
result = "fail";


try {
mymy.set(new JSONArray("[{\"JSONException:\"" + result + "\"}]"));
} catch (JSONException e) {
mymy.set("[{\"JSONException:\"" + e.getMessage() + "\"}]");
}
handler.sendMessage(handler.obtainMessage());
}
};




final StringRequest httpRequest = new StringRequest(Request.Method.POST, URL_POINTER + url,
responseListener,
errorListener) {


// Here the mRequestQueue handler will get the parameters for this request here.
// Ref: https://stackoverflow.com/questions/33573803/how-to-send-a-post-request-using-volley-with-string-body#33578202
// Ref: Multi Threaded solution 14 Oct 2020 (David Svarrer) : https://stackoverflow.com/questions/2028697/dialogs-alertdialogs-how-to-block-execution-while-dialog-is-up-net-style (This stackoverflow here)
@Override
protected java.util.Map<String, String> getParams() throws AuthFailureError {
return jsonObjectToMap(request);
}
};
httpRequest.setShouldCache(false); // Elijah: Could work on dropping the cache !!!
genericHttpRequestQueue.add(httpRequest);


try {
Looper.loop();
} catch (RuntimeException re) {
}
return mymy.get();
}

呼!该死,我一整天都在挣扎。不知怎么的,我想到了一个解决办法,尽管我不建议这么做。您必须使用 Handler 来实现这一点。
调用 getDialogBack函数获取

boolean result =  getDialogBack(this);
Log.d(TAG, "onCreate: "+result);

getDialogBack编写对话框和处理程序代码,使其同步

public int getDialogBack(Context context,int i) {
final Handler handler = new Handler(Looper.getMainLooper()) {


@Override
public void handleMessage(Message mesg) {
throw new RuntimeException();
}
};


AlertDialog.Builder alert = new AlertDialog.Builder(context);
alert.setTitle("Title");
alert.setMessage("Message");
alert.setPositiveButton("Want to print next", new
DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
handler.sendMessage(handler.obtainMessage());
}
});
alert.setNegativeButton("Return False", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
handler.sendMessage(handler.obtainMessage());
}
});
alert.show();


try {
Looper.loop();
} catch (RuntimeException e) {
}


return true;
}