Java:如何从另一个线程启动 UI 对话框,例如对于身份验证器

Posted

技术标签:

【中文标题】Java:如何从另一个线程启动 UI 对话框,例如对于身份验证器【英文标题】:Java: How to launch a UI dialog from another thread, e.g. for Authenticator 【发布时间】:2011-08-31 08:30:52 【问题描述】:

简而言之我的问题:我的 GUI 应用程序需要执行冗长的网络下载。下载在单独的线程中处理。远程站点可能需要身份验证,所以我想定义一个身份验证器,它会弹出一个“输入您的用户名和密码”对话框。我意识到这个对话框需要从 UI 线程运行。

我确定我不是第一个这样做的人。让后台线程在 UI 线程中启动对话框并阻塞直到该对话框被解除的最佳做法是什么?

附言后台线程非常大,不仅仅是从网上下载文件。换句话说,此时将其转换为 SwingWorker 可能不切实际,而且无论如何,我也不确定如何从 SwingWorker 解决这个问题。

【问题讨论】:

带有 PropertyChangeListener 的 SwingWorker 可以在 EDT 上正确调用 JDialog 或 JOptionPane 【参考方案1】:

您需要SwingUtlities.invokeLater 来呈现对话框,并需要一个同步/通知对象来“暂停”并等待用户响应。

基本上在您的工作人员(非 gui)线程中:

final Object obj = new Object() ; // or something to receive your dialog's answer(s)
Runnable r = new Runnable() 

   void run() 
     Dialog d = new Dialog() ;

     Button b = new JButton("ok") ;
     b.addActionListener(new ActionListener() 
         void actionPerformed(ActionEvent e) 
             synchronize(obj)  // can lock when worker thread releases with wait
                obj.notify() ; // signals wait
             
         
     ) ;

   
 ;

synchronize( obj ) 
   SwingUtilites.invokeLater(r) ; // executs r's run method on the swing thread
   obj.wait() ; // releases obj until worker thread notifies

【讨论】:

哦,非常好。我有点希望 Java 有 android 的消息传递功能,但这非常接近。 其实,看起来 invokeLater() 也会做我想做的。【参考方案2】:

Edward Falk 写道 Actually, it looks like invokeLater() will also do what I want

不,这是错误的,因为您必须计算 EDT 存在,并且 SwingUtilites.invokeLater() 如果有运行 EDT 则有效,如果没有,则 SwingUtilites.invokeLater() 没有任何通知,任何弹出窗口都会被显示,可能只是空的 Rectangle

1/ 使用java.swing.Action创建EDT

2/ debug this idea by trashgod 我认为这个逻辑是正确的并且最适合这个

【讨论】:

我同意。我的后台线程只能从 UI 启动,因此几乎可以肯定 EDT 存在。还是我错过了什么? 如果你不使用SwingWorker,你最终会重新发明它。我猜 mKorbel 的意思是“适应”,而不是“调试”example。 :-) 如果我从头开始,我可能会使用 SwingWorker,但我正在使用其他人的代码,而且这是一个非常大的线程。无论如何,invokeAndWait() 在我的情况下工作得很好。【参考方案3】:

根据 Andrew 的回答,这是我的最终解决方案:

final Authenticator authenticator =
   new Authenticator() 
        @Override
        public PasswordAuthentication getPasswordAuthentication() 
            try 
                SwingUtilities.invokeAndWait(new Runnable() 
                    public void run() 
                        // Launch the GUI dialog
                        queryUsernamePassword(getRequestingHost(), getRequestingPrompt());
                    
                );
                if (username == null)
                    return null;
                return new PasswordAuthentication(username,
                            password.toCharArray());
             catch (Exception e) 
                return null;
            
        
    ;

【讨论】:

@Edward Falk 请不要把我的工作当成 FlameWar(我的英语很差),1)如果可能的话,尽量避免在 EDT 上运行 Heavy Action(打开任何类型的连接)2)你可能没有根据主机不可用的事实来计算,然后超时取决于连接类型 30 秒 - 4 分钟,您的 GUI 将以不负责任的形式等待,谁会等待 3)设置超时或添加可能性取消这个长任务 @Edward Falk 如果我忽略/闭上眼睛 "if (username == null)" 代码 +1 @Edward Falk:感谢您发布此信息;这是一个合理的选择,只要您稍后同步访问使用 Authenticator 检索的数据。 @mKorbel:身份验证器在后台线程上运行。唯一在 UI 线程上运行的是 queryUsernamePassword,它只是启动一个模式对话框。 @Edward Falk 好吧,我认为如果你一次性只运行 BackGround 线程,那(有时是 backBox)就没有任何问题,因为对并发性的最深入了解确保我们如果这个具体的方法是不安全的,但在所有情况下 AbstractAction 都有效,如果不存在则创建 EDT

以上是关于Java:如何从另一个线程启动 UI 对话框,例如对于身份验证器的主要内容,如果未能解决你的问题,请参考以下文章

当 MainWindow 从另一个类(不在主函数中)启动时,QT ui 未显示

如何将调色板图像从另一个线程生成到 WPF UI 线程?

Gtkmm:如何从另一个线程更新 UI?连续不断

如何从另一个 QT 应用程序访问 Qt UI 的 QObject?

从另一个线程访问 UI 线程的视图

改变QLabel的大小时,PyQt5“定时器无法从另一个线程启动”错误