为什么小的JPopupMenu会导致视觉伪影,而较大的则不会?

Emi*_*iel 7 java swing jpopupmenu jmenuitem

我正在使用单行JTabel并附加了MouseAdapter.表模型填充了一些随机值.右键单击表格后,将出现带有多个JMenuItems的JPopupMenu.视觉工件开始显示弹出窗口的一部分是否在某个点附着到它所连接的面板之外.有趣的是,如果弹出窗口没有附加很多项目,这似乎只会发生.任何超过七个项目的弹出窗口一直在为我工作.

仅在使用Java 1.8.0_112-b15的Windows 10 64位上进行了测试.

为什么会发生这种情况并且有解决方法?

JPopupMenu神器

public class PopupTest {

    private static final int NUM_POPUP_ITEMS = 3;

    private JFrame frame = new JFrame();
    private JPanel panel = new JPanel();
    private TableModel model = new TableModel();
    private JTable table = new JTable();

    public static void main(String[] a) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new PopupTest();
            }
        });
    }

    public PopupTest() {
        panel.setLayout(new BorderLayout());
        panel.add(table, BorderLayout.CENTER);
        panel.setPreferredSize(new Dimension(400, 500));
        table.setModel(model);
        table.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent event) {
                popup(event);
            }
        });
        frame.setLocation(150, 150);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setContentPane(panel);
        frame.pack();
        frame.setVisible(true);
    }

    private void popup(MouseEvent e) {
        if (SwingUtilities.isRightMouseButton(e)) {
            JPopupMenu menu = new JPopupMenu();
            for (int i = 0; i < NUM_POPUP_ITEMS; i++) {
                menu.add(new JMenuItem(String.valueOf(i)));
            }
            menu.show(panel, e.getX(), e.getY());
        }
    }

    private class TableModel extends AbstractTableModel {

        private List<Double> dataList = new ArrayList<>();

        public TableModel() {
            for (int i = 0; i < 40; i++) {
                dataList.add(Math.random());
            }
        }

        @Override
        public int getRowCount() {
            return dataList.size();
        }

        @Override
        public int getColumnCount() {
            return 1;
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            return dataList.get(rowIndex);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Edw*_*uck 1

COM/DCOM 层和 Java Swing 应用程序中存在少量延迟。

基本上,当 Swing 在 Windows 上运行时,它会注册一个本机操作系统事件侦听器,因为操作系统的图形桌面环境是第一个检测鼠标移动和按钮按下的应用程序。

然后Swing(实际上是AWT)为报告的动作创建一个相应的Event对象,并将该对象放置在内部事件处理线程上。一旦事件被分派到线程,事件处理子系统就会对其进行处理,找到可能对事件做出反应的 Java 代码块并运行这些块(例如定位弹出菜单的块)。弹出菜单同样不会直接绘制到屏幕上,它会被合成到像素缓冲区窗口(如果是 JPopupMenu),并通过 JNI 转换为请求分派到操作系统绘图库,或者(如果是 AWT弹出菜单)COM / DCOM 对象的 Java 端包装器直接分派请求(无需在 Java 端处理额外的绘图)。

所有这些意味着操作系统提供的窗口系统和 Java 虚拟机(及其对与窗口项对应的 Java 端对象的处理)之间存在一个通信循环。此循环意味着处理延迟,因为程序被放置在内核中或从内核中删除,以响应“有工作要做”和“等待另一个程序做更多工作”。

此外,鼠标光标(出于性能原因)并不总是由操作系统的窗口环境处理。根据计算机的配置详细信息,显卡可能会将鼠标绘制在操作系统呈现为桌面的顶部,而不总是通知操作系统鼠标位置,只要它在其他事件(例如单击)之前报告鼠标位置即可需要位置正确。这种额外的优化可能意味着即使您的操作系统在短时间内“将鼠标悬停在”屏幕上也不知道鼠标的位置。

在某些操作系统上,可以通过编程方式调整这些项目。在某些上它们无法调整,但可以(在程序之外)重新配置。在某些情况下,默认行为甚至不可配置。

Swing 隐藏了很多细节,使得图形编程比没有 Swing 容易得多;并且,有许多技术可用于减少往返时间。首先,我会仔细检查您是否在事件处理程序中的 EventDispatch 线程上做了最少的工作。之后,如果您需要更快的速度,我会推荐这篇文章

如果您需要比这更快,最终您的代码将看起来更像其他绘图系统中的绘图代码的包装器,这就是 SWT 解决该问题的方式;或者,您将放弃在 Java 程序中进行绘图,而使用平台库的语言进行绘图,该语言将通过 JNI 绑定到您的应用程序。如果您正在考虑“在 EventDispatch 线程中做最少的工作”之外的任何选项,请注意,您将花费越来越多的努力来获得非常小的收益,并且这些努力将导致代码从根本上更难编写维护、调试并迁移到“即使只是稍微不同”的操作系统(甚至是同一操作系统的不同版本)。

抱歉,答案太长了,但对于大多数人来说,JPopupMenu 的实际绘制方式是一个谜,在提出改进方法之前必须先解决这个问题。