JFrame 預設標題欄支援拖曳移動,但自訂 UI 時常隱藏標題欄(setUndecorated(true))或直接用 JWindow,導致失去原生移動功能。若直接在 mouseDragged 中 setLocation(e.getX(), e.getY()),視窗會因座標系差異閃爍、跳動。
新位置 = 視窗原位置 + (滑鼠當前螢幕座標 - 滑鼠按下時螢幕座標)
關鍵:必須用 螢幕絕對座標(e.getXOnScreen() / e.getYOnScreen() 或 元件轉換),而非元件相對座標。
package com.jebysun.test.globalhotkey;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.MouseInputListener;
public class MyFrame extends JWindow {
private static final long serialVersionUID = 1L;
private JLabel titleBar;
private Point dragOrigin = new Point(); // 滑鼠按下時的螢幕座標
public MyFrame() {
// 1. 基本視窗設定
getContentPane().setBackground(new Color(0x99FF66));
setBounds(100, 100, 600, 400);
setLayout(null);
// 2. 自訂標題欄(作為拖曳手柄)
titleBar = new JLabel(" 自訂視窗標題欄");
titleBar.setOpaque(true);
titleBar.setBackground(new Color(0x66CC00));
titleBar.setBounds(0, 0, 600, 30);
add(titleBar);
// 3. 滑鼠事件監聽
MouseHandler handler = new MouseHandler();
titleBar.addMouseListener(handler);
titleBar.addMouseMotionListener(handler);
setVisible(true);
}
private class MouseHandler implements MouseInputListener {
@Override public void mousePressed(MouseEvent e) {
// 記錄按下時的螢幕絕對座標
dragOrigin.setLocation(e.getXOnScreen(), e.getYOnScreen());
}
@Override public void mouseDragged(MouseEvent e) {
// 計算位移並套用
int dx = e.getXOnScreen() - dragOrigin.x;
int dy = e.getYOnScreen() - dragOrigin.y;
Point loc = getLocation();
setLocation(loc.x + dx, loc.y + dy);
// 更新基準點,避免累積誤差
dragOrigin.setLocation(e.getXOnScreen(), e.getYOnScreen());
}
@Override public void mouseEntered(MouseEvent e) {
setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
}
@Override public void mouseExited(MouseEvent e) {
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
// 未使用事件保留空實作
@Override public void mouseClicked(MouseEvent e) {}
@Override public void mouseReleased(MouseEvent e) {}
@Override public void mouseMoved(MouseEvent e) {}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(MyFrame::new);
}
}
| 錯誤寫法 | 正確寫法 | 原因 |
|---|---|---|
| setLocation(e.getX(), e.getY()) | setLocation(loc.x + dx, loc.y + dy) | 元件座標隨視窗移動變化,導致抖動 |
| 未更新 dragOrigin | dragOrigin 更新為最新螢幕座標 | 避免長距離拖曳時誤差累積 |
| 直接 extends JFrame | extends JWindow + setUndecorated(true) | JWindow 無原生邊框,完全自訂 |
JPanel,監聽 mouseDragged 調整 setSize()。GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices() 取得各螢幕邊界。掌握「螢幕座標系 + 相對位移」核心邏輯,即可為任何無邊框視窗、自訂元件賦予原生般順滑的拖曳體驗。