【Swing 7】坐标陷阱与单个组件地拖动
之前模仿QQ界面的时候,一直很苦恼布局的问题。虽说绝对定位相对于JFrame默
认的BorderLayout(布局管理器),JPanel的FlowLayout(流式管理器)方便了不少。可
以通过setBounds()直接设置坐标,可要是组件一多起来。非把你累死不可。
好了,不多说,为什么说会有陷阱呢。大家看看下面这两个有界面。源代码贴在这。
两个界面就差了句setUndecorated(true); 可以看出,无论有无边框,它们的大小都是
一样的。到这里倒是还没出现问题!接着往下看。
1 package demo; 2 3 import javax.swing.*; 4 import java.awt.*; 5 6 public class DrawPanel { 7 public static void main(String[] args) { 8 new DrawPanel(); 9 } 10 public DrawPanel() { 11 JFrame f = new JFrame(); 12 13 f.setUndecorated(true); // 去掉界面的边框 14 f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 关掉界面时结束javaw.exe进程 15 f.setBounds(200, 200, 400, 300); // 参数分别是界面左上角的横, 纵坐标, 长和宽 16 f.setVisible(true); 17 } 18 }
上面是什么都没加,空空如也的情况。接下来我们给它们加一个按钮,看看会有什么变化。
1 package demo; 2 3 import javax.swing.*; 4 import java.awt.*; 5 6 public class DrawPanel { 7 public static void main(String[] args) { 8 new DrawPanel(); 9 } 10 public DrawPanel() { 11 JFrame f = new JFrame(); 12 f.setUndecorated(true); // 去掉界面的边框 13 f.setLayout(null); // null即不使用JFrame默认的边界布局, 而是设置布局为绝对定位 14 15 JButton b = new JButton("点击"); 16 b.setBounds(50, 50, 60, 20); // 参数分别是界面左上角的横, 纵坐标, 长和宽 17 f.add(b); 18 f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 关掉界面时结束javaw.exe进程 19 f.setBounds(200, 200, 400, 300); 20 f.setVisible(true); 21 } 22 }
问题来了,来两张对比的图片。一目了然。
水平坐标(x值)的对比。
纵坐标(y值)的对比
从上面两幅图很容易看出,有边框的按钮无论是左上角的x值或是y值都比没边框的
大了点。当然,这是相对于窗口来说的。现在,让我们来寻找真相。
System.out.println("按钮在界面上的坐标: " + b.getX() + ", " + b.getY());
很不幸,输出都是50, 50
这和前面我们设置按钮的位置时用的代码相符的。横纵坐标均是50。
b.setBounds(50, 50, 60, 20); // 参数分别是界面左上角的横, 纵坐标, 长和宽
咦,那就奇怪了。明明有边框的按钮横纵坐标明显都偏大。(事实上,那个多出来的坐
标就是左边框和上边框啦!),我们再来证明一下。先要解决怎么获得坐标的问题,也就
是怎么得到界面上任一点的坐标呢?
有没有想到监听窗口呢?其实就是使用鼠标事件!
1 import java.awe.event.*; // 别忘了导入 2 3 b.addMouseListener(new MouseAdapter() { 4 public void mousePressed(MouseEvent e); 5 System.out.println("按钮在界面上的实际坐标: " + e.getX() + ", " + e.getY()); 6 });
大家在按钮周围随便点点,便会发现,实际上按钮的x值是接近9,y值是接近36的。事
实上,这也是左边框和上边框的宽度。别问我为什么,这是我昨晚点了好久才得出来的数据。
好了,说了这么多,结论:
1. e.getX()和e.getY()得到的坐标才是真正的坐标。监听界面时,得到的是界面的点坐标。
而监听按钮时,得到的是在按钮上的点坐标。(而不是在界面上的坐标)。
2. b.getX()和b.getY()得到的不过是去除边框后的坐标,也就是setBounds()或setLocation
()里面设置的坐标。
3. 当没有边框时,上面两者是相等的。当有边框时,要分别加上左边框和上边框的宽度。
即b.getX() + 9和b.getY() + 36。
---------------------------------------------------------------------------------------------
解决了上面两个易错的问题,接下来就是讲讲拖动的原理了。和移动界面的原理差不多。好比
我们知道鼠标按住按钮上的那一点在按钮上的坐标 (下图中蓝色粗线),然后用该点在屏幕上的坐
标,(红色线到蓝色粗线的长度)减去该点在按钮上(蓝色粗线)的坐标。再减去界面的左上角坐标
(红色粗线),万事大吉,搞定!
1. 先来个没有边框的版本
1 package demo; 2 3 import javax.swing.*; 4 import java.awt.event.*; 5 import java.awt.*; 6 7 public class DrawPanel { 8 int pointXOnButton, pointYOnButton; 9 JFrame f; 10 public static void main(String[] args) { 11 new DrawPanel(); 12 } 13 public DrawPanel() { 14 f = new JFrame("移动按钮"); 15 f.setLayout(null); // 不使用JFrame默认的边界布局而是使用直接设置坐标的绝对定位 16 f.setUndecorated(true); // 去掉窗口自带的边框 17 18 // 获取电脑屏幕的尺寸(如15.6寸的屏幕一般是1920 * 1080像素), 使窗口在屏幕上居中显示 19 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); 20 int xOnScreen = screenSize.width / 2 - 400; // 窗口左上角的x坐标 21 int yOnScreen = screenSize.height / 2 - 300; // 窗口左上角的y坐标 22 init(); 23 f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 关闭窗口后退出javaw.exe进程 24 f.setBounds(xOnScreen, yOnScreen, 800, 600); 25 f.setVisible(true); 26 } 27 public void init() { 28 JButton b = new JButton("点我"); 29 b.setBounds(200, 100, 60, 20); 30 b.addMouseListener(new MouseAdapter() { 31 public void mousePressed(MouseEvent e) { 32 pointXOnButton = e.getX(); // 点在按钮上的x坐标 33 pointYOnButton = e.getY(); 34 } 35 }); 36 b.addMouseMotionListener(new MouseMotionAdapter() { 37 public void mouseDragged(MouseEvent e) { 38 int pointXOnScreen = e.getXOnScreen(); // 点在屏幕上的x坐标 39 int pointYOnScreen = e.getYOnScreen(); 40 int frameXOnScreen = f.getX(); // 窗口左上角的x坐标 41 int frameYOnScreen = f.getY(); 42 int buttonXOnFrame = pointXOnScreen - pointXOnButton - frameXOnScreen; 43 int buttonYOnFrame = pointYOnScreen - pointYOnButton - frameYOnScreen; 44 b.setLocation(buttonXOnFrame, buttonYOnFrame); 45 } 46 }); 47 f.add(b); 48 } 49 }
2. 再来个有边框的版本(要考虑边框宽度了)
1 package demo; 2 3 import javax.swing.*; 4 import java.awt.event.*; 5 import java.awt.*; 6 7 public class DrawPanel { 8 int pointXOnButton, pointYOnButton; 9 JFrame f; 10 public static void main(String[] args) { 11 new DrawPanel(); 12 } 13 public DrawPanel() { 14 f = new JFrame("移动按钮"); 15 f.setLayout(null); // 不使用JFrame默认的边界布局而是使用直接设置坐标的绝对定位 16 17 // 获取电脑屏幕的尺寸(如15.6寸的屏幕一般是1920 * 1080像素), 使窗口在屏幕上居中显示 18 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); 19 int xOnScreen = screenSize.width / 2 - 400; // 窗口左上角的x坐标 20 int yOnScreen = screenSize.height / 2 - 300; // 窗口左上角的y坐标 21 init(); 22 f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 关闭窗口后退出javaw.exe进程 23 f.setBounds(xOnScreen, yOnScreen, 800, 600); 24 f.setVisible(true); 25 } 26 public void init() { 27 JButton b = new JButton("点我"); 28 b.setBounds(200, 100, 60, 20); 29 b.addMouseListener(new MouseAdapter() { 30 public void mousePressed(MouseEvent e) { 31 pointXOnButton = e.getX(); // 点在按钮上的x坐标 32 pointYOnButton = e.getY(); 33 } 34 }); 35 b.addMouseMotionListener(new MouseMotionAdapter() { 36 public void mouseDragged(MouseEvent e) { 37 int pointXOnScreen = e.getXOnScreen(); // 点在屏幕上的x坐标 38 int pointYOnScreen = e.getYOnScreen(); 39 int frameXOnScreen = f.getX(); // 窗口左上角的x坐标 40 int frameYOnScreen = f.getY(); 41 int buttonXOnFrame = pointXOnScreen - pointXOnButton - frameXOnScreen - 9; // 9是左边边框的宽度 42 int buttonYOnFrame = pointYOnScreen - pointYOnButton - frameYOnScreen - 36; // 36是上边边框的厚度 43 b.setLocation(buttonXOnFrame, buttonYOnFrame); 44 } 45 }); 46 f.add(b); 47 } 48 }