JFrame wrong location with Ubuntu (Unity ?)

2019-07-12 19:42发布

It seems that there is a bug with Ubuntu (maybe only unity). The decoration of the JFrame is taken into account for getLocation() and getSize(), but not for setLocation() and setSize(). This leads to weird behaviour. For instance, if you use pack() after the frame is displayed and the dimensions changed, the frame will go down 20 pixels...

To illustrate a concrete case when it becomes really annoying, I made a SSCCE. It's a JFrame with a basic JPanel. If you drag the panel, the JFrame is supposed to move along.

Under Windows, it works as expected. Under Ubuntu, if I do setUndecorated(true) it will also work fine, but if I let the decoration, the JFrame turn crazy !

public class Test {
    private static JFrame mainFrame;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                mainFrame = new JFrame("test");
                mainFrame.setSize(300,20);
                mainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                mainFrame.setVisible(true);

                Container pane = mainFrame.getContentPane();
                pane.addMouseMotionListener(new MouseMotionListener() {
                    private int posX = 0, posY = 0;

                    @Override
                    public void mouseDragged(MouseEvent e) {
                        int x = e.getX() - posX + mainFrame.getX();
                        int y = e.getY() - posY + mainFrame.getY();
                        mainFrame.setLocation(x, y);
                    }

                    @Override
                    public void mouseMoved(MouseEvent e) {
                        posX = e.getX();
                        posY = e.getY();
                    }

                });

            }
        });
    }
}

I don't know how I can fix that. How can I get the size of the windows decoration ? And I have no idea about which versions of Ubuntu are concerned. And if it is only a Unity problem, I don't even know how to find out if my user is using Unity...

Any idea for a workaround ?

Edit : Ok, MadProgrammer did provide a better code, but the bug still occurs sometimes. I edited my MouseListener accordingly to track the bug :

            pane.addMouseMotionListener(new MouseMotionListener() {
                private int posX = 0, posY = 0;

                @Override
                public void mouseDragged(MouseEvent e) {
                    int x = e.getXOnScreen() - posX;
                    int y = e.getYOnScreen() - posY;
                    mainFrame.setLocation(x, y);
                    System.out.println("drag   :  border ignored / border considered : "+(mainFrame.getY()+e.getY())+"   /   "+e.getYOnScreen());
                }

                @Override
                public void mouseMoved(MouseEvent e) {
                    posX = e.getXOnScreen() - mainFrame.getX();
                    posY = e.getYOnScreen() - mainFrame.getY();
                    System.out.println("move  :  border ignored / border considered : "+e.getY()+"   /   "+posY);
                }
            });

Each time that the 2 values are identical, it means that the bug will occur on the next click. Otherwise, the values are different. On other OS, the values are always the same. Actually, they should be or the same always, or always different. I don't understand how they can be sometimes equal and sometimes different...

1条回答
Rolldiameter
2楼-- · 2019-07-12 19:49

I don't have Ubuntu to test with, but I've used something similar to this on both MacOS and Windows

import java.awt.Container;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class Test {

    private static JFrame mainFrame;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                mainFrame = new JFrame("test");
                mainFrame.setSize(300, 100);
                mainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                mainFrame.setVisible(true);

                Container pane = mainFrame.getContentPane();
                MouseAdapter ma = new MouseAdapter() {
                    private Point offset;

                    @Override
                    public void mouseDragged(MouseEvent e) {
                        if (offset != null) {
                            Point pos = e.getLocationOnScreen();

                            int x = pos.x - offset.x;
                            int y = pos.y - offset.y;

                            System.out.println(x + "x" + y);

                            SwingUtilities.getWindowAncestor(e.getComponent()).setLocation(x, y);
                        }
                    }

                    @Override
                    public void mousePressed(MouseEvent e) {

                        Point pos = SwingUtilities.getWindowAncestor(e.getComponent()).getLocation();

//                      Point pos = e.getComponent().getLocationOnScreen();
                        offset = new Point(e.getLocationOnScreen());
                        System.out.println(pos + "/" + offset);
                        offset.x -= pos.x;
                        offset.y -= pos.y;

                        System.out.println(offset);
                    }
                };
                pane.addMouseListener(ma);
                pane.addMouseMotionListener(ma);
            }
        });
    }
}

This should work for both decorated and undecorated windows, as it takes the difference between the positions of the component (on the screen) and the windows current position. When dragged, it calculates the distance of movement from the click point and updates the window's location accordingly (allowing for the original offset of the click)

查看更多
登录 后发表回答