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

9
我正在使用一个附有MouseAdapter的单行JTabel。表格模型由一些随机值填充。右键单击表格后,将出现一个带有多个JMenuItems的JPopupMenu。如果弹出菜单的某些部分曾经绘制在其所附加的面板之外,则会出现视觉伪影。有趣的是,只有当弹出菜单没有许多项目附加时,才会发生这种情况。对于我而言,任何具有超过七个项目的弹出窗口都一直正常工作。

仅在Windows 10 64位和Java 1.8.0_112-b15上进行了测试。

为什么会这样,是否有解决方法?

JPopupMenu artifact

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

将表格放入滚动窗格中是否有区别? - MadProgrammer
不,它并没有。在我的原始代码中,表格被添加到了一个JScrollPane中,并且它的行为方式完全相同。 - Emiel
3
我尝试了提供的代码(顺便说一下,非常棒),使用JDK 7/8在Windows 10上无法复现问题。这可能是显示驱动程序或类似问题引起的吗? - Mick Mnemonic
1
同样在 Debian 上的 Java 1.8.0_111 中无法再现,在内核为 3.16.0-4-amd64,Gnome 3.14.1 经典版,Intel GMA i965 显卡的环境中。我倾向于赞同 Mick 的观点,认为这是驱动问题。设置一些 Java2D 属性,比如 -Dsun.java2d.d3d=false-Dsun.java2d.opengl=true,可能会有所帮助。 - VGR
更改外观风格可能会有所帮助。将其更改为本地或_Motif_... - Kuznetsov S.A.
显示剩余2条评论
1个回答

1
COM/DCOM层和Java Swing应用程序之间存在一小段延迟。基本上,当Swing在Windows上运行时,它会为事件注册一个本地操作系统监听器,因为操作系统的图形桌面环境是第一个检测鼠标移动和按钮按下的应用程序。然后,Swing(实际上是AWT)创建一个相应的事件对象来报告该操作,并将此对象放置在内部事件处理线程上。一旦事件被分派到线程,它就会被事件处理子系统处理,找到可能对事件做出反应的Java代码块并运行这些块(例如指定弹出菜单的位置)。弹出菜单再次不直接绘制到屏幕上,而是合成到像素缓冲窗口中(如果是JPopupMenu),并通过JNI转换成请求分派到操作系统绘图库,或者(如果是AWT弹出菜单)Java端的COM/DCOM对象包装器直接分派请求(无需在Java端处理额外的绘制)。
所有这些意味着您的操作系统提供的窗口系统与Java虚拟机之间存在通信循环(以及其处理与窗口项目对应的Java侧对象)。该循环意味着在响应“有工作要做”和“等待其他程序提供更多工作”的情况下,程序被放置和从核心中移除时会出现延迟处理。
此外,为了提高性能,鼠标光标并不总是由操作系统的窗口环境处理。根据计算机的配置细节,图形卡可能会在操作系统呈现其桌面的顶部绘制鼠标,而不总是通知操作系统鼠标位置,只要在其他事件(如单击)需要正确位置之前报告鼠标位置即可。这种额外的优化意味着即使您的操作系统也可能在“四处移动”屏幕的短时间内不知道鼠标位置。
在某些操作系统上,可以通过编程进行调整。在某些操作系统上,它们无法调整,但可以在程序之外重新配置。在一些操作系统上,甚至默认行为也无法配置。
Swing隐藏了很多细节,使得图形编程比没有Swing要容易得多;而且,有许多技术可以用来减少往返时间。首先,我会再次检查您在事件处理程序中的EventDispatch线程上所做的最小工作量。之后,如果需要更快的速度,我建议参考this article
如果您需要比这更快,最终您的代码将更像是其他绘图系统中的绘图代码的包装器,这就是SWT解决问题的方式;或者,您将放弃在Java程序中进行绘图,并在平台库的语言中进行绘图,该语言将通过JNI绑定到您的应用程序。如果您正在考虑超出“在EventDispatch线程中尽可能少地工作”的选项,请注意,您将为非常小的收益付出越来越多的努力,这种努力将导致基本上更难以维护、调试和移动到“即使只是稍微不同”的操作系统(甚至是同一操作系统的版本)。

抱歉答案很长,但对于大多数人来说,JPopupMenu的绘制方式实际上是个谜,必须在提出改进方法之前进行讨论。


哦,还有水平线撕裂的问题?那是因为你的循环光标位置占用了太多时间,以至于你小小的意外拉动滚动条区域没有足够的优先级重新绘制你刚刚向下或向上拖动的小部件的整个演示。 - Edwin Buck

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接