預設 JFrame 已內建標題欄拖曳移動視窗功能。但若為了自訂 UI 隱藏標題欄(setUndecorated(true)),或直接使用無邊框的 JWindow,就必須自行實作滑鼠拖曳邏輯。直接在 mouseDragged 中呼叫 setLocation(e.getX(), e.getY()) 會導致視窗閃爍、位置跳動,本文提供正確解法。
正確公式:
新視窗位置 = 原視窗位置 + (目前滑鼠位置 - 按下時滑鼠位置)
需記錄滑鼠按下瞬間的相對座標(相對於元件原點),拖曳時計算位移量累加到視窗原位置。
package com.jebysun.test.globalhotkey;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.event.MouseEvent;
import javax.swing.JLabel;
import javax.swing.JWindow;
import javax.swing.event.MouseInputListener;
/**
* 自訂程式視窗,支援滑鼠拖曳移動
* @author Jeby Sun
*/
public class MyFrame extends JWindow {
private static final long serialVersionUID = 1L;
JLabel titleLbl;
public MyFrame() {
// JWindow 背景色需設定給 ContentPane
this.getContentPane().setBackground(new Color(0x99FF66));
this.setBounds(100, 100, 600, 400);
this.setLayout(null);
// 自訂標題欄區域
titleLbl = new JLabel(" 自訂視窗標題欄");
titleLbl.setOpaque(true);
titleLbl.setBackground(new Color(0x66CC00));
titleLbl.setBounds(0, 0, 600, 30);
this.add(titleLbl);
// 滑鼠事件監聽
MouseEventListener mouseListener = new MouseEventListener(this);
titleLbl.addMouseListener(mouseListener);
titleLbl.addMouseMotionListener(mouseListener);
this.setVisible(true);
}
/** 滑鼠事件處理內部類別 */
class MouseEventListener implements MouseInputListener {
Point origin; // 記錄按下時滑鼠相對座標
MyFrame frame; // 目標視窗
public MouseEventListener(MyFrame frame) {
this.frame = frame;
origin = new Point();
}
@Override public void mouseClicked(MouseEvent e) {}
/** 按下瞬間記錄相對座標 */
@Override
public void mousePressed(MouseEvent e) {
origin.x = e.getX();
origin.y = e.getY();
}
@Override public void mouseReleased(MouseEvent e) {}
/** 進入區域顯示移動游標 */
@Override
public void mouseEntered(MouseEvent e) {
frame.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
}
/** 離開區域恢復預設游標 */
@Override
public void mouseExited(MouseEvent e) {
frame.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
/** 拖曳時計算新位置 */
@Override
public void mouseDragged(MouseEvent e) {
Point p = frame.getLocation();
frame.setLocation(
p.x + (e.getX() - origin.x),
p.y + (e.getY() - origin.y)
);
}
@Override public void mouseMoved(MouseEvent e) {}
}
public static void main(String[] args) {
new MyFrame();
}
}
| 方法 | 用途 |
|---|---|
mousePressed | 記錄滑鼠相對於標題欄左上角的座標 (origin) |
mouseDragged | 視窗新位置 = 目前視窗位置 + (目前滑鼠座標 - origin) |
mouseEntered/Exited | 切換游標樣式提升 UX |
setUndecorated(true) | JFrame 隱藏原生標題欄時同樣適用此邏輯 |
setLocation(e.getXOnScreen(), e.getYOnScreen()) → 視窗左上角跳到滑鼠位置,造成劇烈跳動e.getXOnScreen() 而非元件相對座標 e.getX()