为什么 JPopupMenu 在渲染之前需要 2 次 UIThread 传递?

Why does JPopupMenu take 2 passes of UIThread before rendering?

第一遍似乎使可绘制区域无效或绘制背景。第二遍呈现菜单。如果有任何延迟(如下面的例子加剧),那么你会得到一个灰色的方形闪烁效果。

这是 Linux 上的 JDK8。

如何停止这种闪烁效果?

public class MenuTester {

  public static void main(String[] args) {
    final JFrame frame = new JFrame();
    frame.setBounds(100,  100,  300,  200);


    final JButton button = new JButton("Show Menu");
    button.addActionListener(new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            final JPopupMenu popupMenu = new JPopupMenu();
            popupMenu.add(new JMenuItem("aaaa"));
            popupMenu.add(new JMenuItem("bbbb"));
            popupMenu.add(new JMenuItem("cccc"));
            popupMenu.setLocation(100, 100);
            popupMenu.setVisible(true);
            try {
                Thread.sleep(2000); // Leave enough time to clearly see the ?invalidated/background? area.
            } catch (InterruptedException ex) {
                // Nothing to do
            }
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    // Hide after 1 second
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException ex) {
                        // Nothing to do
                    }
                    popupMenu.setVisible(false);
                }
             });
         }
    });

    frame.add(button);
    frame.setVisible(true);
  }
}

Swing 在显示 windows 时有一个 "delay",这可能与 OS 实现的帧之间的时间有关以及本地消息和事件队列的连接,但这是纯粹的观察

我给你写了代码,只需将框架的创建包装到一个 EventQueue.invokeLater 中就可以获得类似的行为

根据系统启动和配置,您将在不同的系统上获得不同的结果

What is the event that causes the window to be rendered in the first pass?

我所做的只是获取了您的代码并将 UI 的创建包装在 EventQueue.invokeLater 中,例如...

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class MenuTester {

    public static void main(String[] args) {
        new MenuTester();
    }

    public MenuTester() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                final JFrame frame = new JFrame();
                frame.setBounds(100, 100, 300, 200);

                final JButton button = new JButton("Show Menu");
                button.addActionListener(new ActionListener() {

                    @Override
                    public void actionPerformed(ActionEvent e) {
                        final JPopupMenu popupMenu = new JPopupMenu();
                        popupMenu.add(new JMenuItem("aaaa"));
                        popupMenu.add(new JMenuItem("bbbb"));
                        popupMenu.add(new JMenuItem("cccc"));
                        popupMenu.setLocation(100, 100);
                        popupMenu.setVisible(true);
//                        try {
//                            Thread.sleep(2000); // Leave enough time to clearly see the ?invalidated/background? area.
//                        } catch (InterruptedException ex) {
//                            // Nothing to do
//                        }
//                        SwingUtilities.invokeLater(new Runnable() {
//                            @Override
//                            public void run() {
//                                // Hide after 1 second
//                                try {
//                                    Thread.sleep(1000);
//                                } catch (InterruptedException ex) {
//                                    // Nothing to do
//                                }
//                                popupMenu.setVisible(false);
//                            }
//                        });
                    }
                });
                frame.add(button);
                frame.setVisible(true);
            }
        });
    }
}

And is there a way to render the window as transparent during that first pass and opaque during the 2nd?

这不是一个新问题,自从我在 Java 1.3 开始使用 Swing 以来,情况一直如此。你问的意思是你知道油漆通道何时完成并完成。 Swing 并不完全愚蠢,它可以做出一些聪明的决定来优化渲染过程(比如不绘制可见的组件)

另一个问题是,对于 JPopupMenu,您实际上不知道它是否显示在 window 中(或者只是作为组件显示在玻璃窗格上示例)所以整个事情非常复杂