Why does a small JPopupMenu cause visual artifacts whereas a larger one doesn't?
Asked Answered
L

1

9

I'm using a single row JTabel with a MouseAdapter attached to it. The table model is populated with some random values. Upon right-clicking the table a JPopupMenu with several JMenuItems will appear. Visual artifacts start showing if part of the popup was drawn outside of the panel it's attached to at some point. Interestingly enough this only seems to happen if the popup doesn't have many items attached to it. Any popup with more than seven items has been working consistently for me.

Only tested on Windows 10 64 bit with Java 1.8.0_112-b15.

Why does this happen and is there a workaround?

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);
        }
    }
}
Literalminded answered 2/2, 2017 at 23:9 Comment(7)
Does putting the table in scrollpane make a differenceDysteleology
Nope it does not. In my orignal code the table is added to a JScrollPane and it behaves the exact same way.Literalminded
I tried the code provided (which was excellent, BTW) with JDK 7/8, but was unable to reproduce the problem on Windows 10. Could this be a display driver issue or similar?Tailspin
Also not reproducible in Java 1.8.0_111 on Debian, kernel 3.16.0-4-amd64, Gnome 3.14.1 Classic, Intel GMA i965 graphics. I am inclined to agree with Mick that it’s a driver issue. Setting some Java2D properties, like -Dsun.java2d.d3d=false or -Dsun.java2d.opengl=true, may help.Interminable
I've updated Java and all drivers neither fixes the issue. I'm unable to reproduce the issue on a Ubuntu VM and on another desktop running the same Windows version.Literalminded
I can't reproduce this painting artefact, by using Win10Prof_64 - JDK8_060 or (latest) JDK8_121, excelemt question +1Weigle
Changing Look and Feel might help. Change it to native or Motif...Acupuncture
F
1

There is a small delay in the COM/DCOM layer and the Java Swing application.

Basically, when Swing is running on windows, it registers a native Operating System listener for events, because the Operating System's Graphical desktop environment is the first application that detects the mouse movement and the button press.

Then Swing (actually AWT) creates a corresponding Event object for the reported action and places this object on the internal event handling thread. Once the event is dispatched to the thread, it is processed by the event handling subsystem, finding the blocks of Java code that might react to the event and running those blocks (like the one that says to position the pop-up menu). The pop-up menu again doesn't directly draw to the screen, it gets composted into a pixel buffer window (if a JPopupMenu) and turned into a request dispatch by way of JNI to the operating system drawing libraries, or (if a AWT popup menu) the Java-side wrapper for the COM / DCOM object directly dispatches the request (without the additional drawing handled in the Java-side).

All of this means that there is a loop of communication between the windowing system provided by your operating system and the Java Virtual Machine (and its handling of the Java side objects corresponding to window items). This loop means a delay in processing as the programs get placed and removed from the cores in response to "having work to do" and "waiting on another program for more work to do".

In addition, the mouse cursor (for performance reasons) doesn't always get handled by the operating system's windowing environment. Depending on your computer's configuration details the graphics card may be drawing the mouse on top of what the operating system presents as its desktop without always informing the operating system of mouse position, just as long as it reports mouse position before other events (like clicks) need the position to be correct. This additional optimization can mean that even your operating system doesn't know the mouse position for short periods of time of just "mousing around" the screen.

On some operating systems, these items can be programmatically tuned. On some they cannot be tuned, but can be (outside of the program) reconfigured. On a few, the default behaviors are not even configurable.

Swing hides a lot of details, making graphics programming much easier than it would be without Swing; and, there are many techniques which can be used to decrease the round trip timing. For a start, I'd double-check that you are doing the minimal amount of work on your EventDispatch Thread in your Event handlers. Afterwards, if you need more speed, I'd recommend this article.

If you need to go faster than that, eventually your code will look more like wrappers around drawing code in other drawing systems, which is how SWT approached the problem; or, you'll discard doing the drawing in your Java program and do the drawing in the platform library's language which would be bound to your application through JNI. If you are considering any of the options beyond "do the minimal amount of work in your EventDispatch Thread" be aware that you are going to expend more and more effort for very small gains, and the effort will lead to code that is fundamentally harder to maintain, debug, and move to "even just a little different" operating systems (or even versions of the same operating system.

Sorry the answer is so long, but for most, it's a mystery how the JPopupMenu is actually drawn, and that had to be covered before improved approaches could be suggested.

Fugitive answered 13/2, 2020 at 16:1 Comment(1)
Oh, and the horizontal lines tearing? That's because your looping for cursor position is taking up so much of the time that you're small accidental pull of the scroll bar area isn't getting enough priority to redraw the whole presentation of the widgets you just dragged down or up.Fugitive

© 2022 - 2024 — McMap. All rights reserved.