【问题标题】:JDialog invisible, components clickableJDialog不可见,组件可点击
【发布时间】:2013-12-26 06:10:33
【问题描述】:

我在应用程序启动时使用JDialog 来多次显示消息。有时对话框及其控件是不可见的,但可以单击。

JDialog 仅实例化一次,并在每次显示消息时设置为可见“真”,然后设置为可见“假”,直到显示下一条消息。

为了排除多线程相关问题,当线程创建消息并显示对话框时,我总是使用SwingUtilities.invokeLater(...) 进行ui 调用。

因为它是一个庞大的项目,而且我的问题与任何特定代码无关,所以我不发布代码而是描述问题。这些问题似乎无法重现,但有时会发生,因此尽管在 EDT 上运行了每个相关调用,但它可能是一个线程问题。

我做错了什么?

public class MessageHandler {

private volatile static MessageHandler messageHandler = null;
private List<Message>messages = null;
private volatile WeakReference<MessagesPanelControl> view = null;

private final Object viewSynchronizationObject = new Object();

private MessageHandler() {
    messages = new ArrayList<Message>();
}

public static MessageHandler getInstance() {
    MessageHandler result = messageHandler;
    if (result == null) {
        synchronized (MessageHandler.class) {
            result = messageHandler;
            if (result == null)
                messageHandler = result = new MessageHandler();
        }
    }
    return result;
}


public void registerView(MessagesPanelControl view) {
    this.view = new WeakReference<MessagesPanelControl>(view);
}

public void addMessage(final Message message) {
        synchronized (viewSynchronizationObject) {
           messages.add(message);
           Collections.sort(messages);
           updateView();
        }
}

    private void updateView() {
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            synchronized (viewSynchronizationObject) {
                if (view == null) {
                    return;
                }
                MessagesPanelControl mpc = view.get();
                if (mpc != null) {
                    mpc.updateView();
                } 
            }
        }
    });
}
}

在 MainFrame 类中,我在启动时进行了一次初始化:

MessagesPanelControl mp = new MessagesPanelControl();
MessageHandler.getInstance().registerView(mp);
LockPane messageBasicPane = new LockPane(this, mp);

然后在不同的线程中调用它以通过MessageHandler Singleton 显示消息:

MessageHandler.getInstance().addMessage(Message.getSimpleMessage("Error", "Fatal error occured", Message.MessageIcon.ERROR));

我没有发布所有细节,但所有必要的部分都是为了理解整个问题,希望它能让它更容易理解。

MessagePanelControl (mpc) 是一个扩展 JPanel 的类。它的updateView() 方法基于MessageHandler's 消息列表创建消息控件,如按钮、标签和图标。最后,该方法向主框架发送Delegatelike 命令以显示包含MessagePanelControlJDialog总结如下:

  • messageList.size()>0:为MessageHandler中列表中的每条消息创建消息面板
  • messageList.size()>0:显示带有MessagePanelControl 的JDialog
  • messageList.size()MessagePanelControl隐藏JDialog

    公共无效更新视图(){ 同步(viewMPCSynchronizationObject){ Utils.throwExceptionWhenNotOnEDT();

        JPanel messagesListPanel = new JPanel();
        scrollPane.setViewportView(messagesListPanel);
        scrollPane.setBorder(null);
        messagesListPanel.setLayout(new BoxLayout(messagesListPanel, BoxLayout.Y_AXIS));
        if (MessageHandler.getInstance().getMessages() != null &&  MessageHandler.getInstance().getMessages().size() > 0) {
                      [...]
                      //Create buttons, text icons... for each message
                      [...]
          SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    MainFrame().showMessageBoard();
                }
            }); 
        } else {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    MainFrame().closeMessageBoard();
                }
            });
        }
        repaint();
    }
    

    }

主框架:

 //Show Messageboard
 public void showMessageBoard() {
    if (messageBasicPane != null) {
        messageBasicPane.setVisible(true);
        messageBasicPane.repaint();
    }
 }

[...]  
 //Close Messageboard
 public void closeMessageBoard() {
    if (messageBasicPane != null) {
        messageBasicPane.setVisible(false);
    }
 }

此行创建JDialog,详细:

[...]
    public LockPane(JFrame parentFrame, JComponent componentToShow, Dimension paneSize, float opacity, ModalityType modality) {
        super(parentFrame, true);
        Utils.throwExceptionWhenNotOnEDT();
        createDialog(paneSize, opacity, modality);
        if (componentToShow != null) {
            add(componentToShow);
        }
        pack();
    }

    private void createDialog(Dimension paneSize, float opacity, ModalityType modality) {
        Utils.throwExceptionWhenNotOnEDT();
        setUndecorated(true);
        setModalityType(modality);
        if (opacity < 1 && opacity >= 0)
            com.sun.awt.AWTUtilities.setWindowOpacity(this, opacity);
        setSize(paneSize);
        setPreferredSize(paneSize);
        setMaximumSize(paneSize);
        setBounds(0, 0, paneSize.width, paneSize.height);
        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
    }   
[...]

Java VisualVM 的一个新观察结果是,AWT-EventQueue 没有被阻塞,只是有时会有一小段“等待”但没有阻塞。另一个奇怪的事情是,有时我的JDialog 是完全透明的(不可见),有时它是白色的,具有所需的不透明度。

【问题讨论】:

  • 问题在于,我们实际上需要查看代码:不是您的项目代码,而是您实际查看和消失的代码。好吧,试着做一个SSCCE
  • 你说得对,我将涉及的代码提取到一个小概述中。据我所知,制作SSCCE是不可能的,因为问题与从不同部分/线程调用MessageHandler有关。
  • 您的代码不是线程安全的;当其他线程调用 addMessage 时,在 UI 线程上执行 synchronized(viewSynchronizationObject) 无济于事,这会在没有任何同步的情况下修改消息列表。 所有线程访问同一资源必须同步,否则毫无意义。请注意,在大多数情况下,如果 UI 未绘制但仍可点击,则表明在重绘过程​​中出现异常。
  • @Holger:感谢您的评论,您说得对,我更正了 addMessage 方法。没有发生异常,我一直在看堆栈跟踪,不幸的是那里没有异常
  • @alex:如果不知道mpc.updateView() 的真正作用,就不可能提供进一步的帮助。顺便提一句。我对你给invokeLaterfalse 参数感到恼火。

标签: java multithreading swing


【解决方案1】:

在此函数中,您实际上是在等待 Runnable 传递给 SwingUtilities.invokeLaterinvokeLater 将其提交给 EDT 以执行。如果您在 viewSynchronizationObject 上持有的锁被其他应用程序线程锁定,则 EDT 将阻塞它,这实际上从您的代码中可以明显看出,因为您在其他几个地方使用了此变量。

private void updateView() {
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            synchronized (viewSynchronizationObject) {
                if (view == null) {
                    return;
                }
                MessagesPanelControl mpc = view.get();
                if (mpc != null) {
                    mpc.updateView();
                } 
            }
        }


 },false);
}

我们不应该阻止 EDT 执行它的任务,否则我们的应用程序会冻结。请阅读我的posted answer here for details,了解 EDT 和 EventQueue 如何执行 Swing 事件和渲染任务。

虽然我们不知道您的应用程序逻辑,但您可以从invokeLater 中删除synchronized (viewSynchronizationObject) {},而是可以将SwingUtilities.invokeLater(new Runnable() {} 放在这个同步块中:

synchronized (viewSynchronizationObject) 
{
   SwingUtilities.invokeLater(new Runnable() { /* your code */ } );
}

【讨论】:

  • 好的,谢谢,这是我错过的好点。我更正了我的应用程序并尝试在白天重现它,因为该问题无法直接重现。但是您的观点以及@Holger 的评论可以解决我的问题。补充一点:当问题发生时,所有组件都是可点击+响应但不可见(所以没有冻结的 ui 线程)
  • @alex,隐身表明 EDT 将在不久的将来冻结,因为您目前正在阻止它,如果您阻止它的时间再长一点,它就会冻结。
  • 有史以来最糟糕的建议。如果您只是将synchronized 放在invokeLater 周围,则事件调度线程执行的代码不是 线程安全的。事实上,synchronized 完全没用,因为invokeLater 本身不需要同步。您建议完全删除线程安全。
  • 但是因为mpc.updateView 方法是线程安全的,所以这synchronized 是否有必要?更进一步我对invokeLater的理解是/是Runnable在EDT上排队,所以不存在并发问题。有错吗?
  • @alex,正如我所说,我不知道你的内部通信代码。当我不知道您是否需要先发生关系时,您不能指望我解决问题
猜你喜欢
  • 2013-12-07
  • 2011-08-07
  • 2011-08-04
  • 2018-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-26
  • 2020-11-24
相关资源
最近更新 更多