在 Swing 中,只有 JDialog 可直接設定為模態:
new JDialog(Frame owner, boolean modal)dialog.setModal(true)(繼承自 Dialog)JFrame 無法直接設為模態。網路上常見變通做法:在 windowDeactivated 事件中呼叫 requestFocus() 強制搶回焦點,模擬模態行為。但此法存在焦點閃爍、跨平台不穩定等問題,**不推薦生產使用**。
public class MyModalFrame extends JFrame implements WindowListener {
private JFrame parent;
private boolean modal;
public MyModalFrame(JFrame parent, boolean modal, String title) {
super(title);
this.parent = parent;
this.modal = modal;
addWindowListener(this);
if (modal) parent.setEnabled(false); // 禁用父視窗
}
@Override public void windowClosing(WindowEvent e) {
if (modal) parent.setEnabled(true); // 關閉時恢復父視窗
}
@Override public void windowDeactivated(WindowEvent e) {
if (modal) this.requestFocus(); // 失去焦點立即搶回
}
// 其他 WindowListener 方法留空...
}
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
Dimension size = frame.getSize();
int x = (screen.width - size.width) / 2;
int y = (screen.height - size.height) / 2;
frame.setLocation(x, y);
frame.setLocationRelativeTo(null); // null 表示相對螢幕置中
API 文檔行為: 若元件未顯示或參數為 null,視窗置於螢幕中央;若指定元件,視窗置於該元件中心,並自動避開螢幕邊界。
⚠️ 必須先呼叫
setSize()或pack(),再呼叫setLocationRelativeTo()。
若順序顛倒,視窗左上角雖對準中心,但整體視覺會偏向右下角。
// 正確順序
frame.pack(); // 或 frame.setSize(w, h);
frame.setLocationRelativeTo(null); // 置中
frame.setVisible(true);
public class LoginDialog extends JDialog {
public LoginDialog(JFrame parent) {
super(parent, "用戶登錄", true); // true = 模態
initUI();
pack();
setLocationRelativeTo(parent); // 相對父視窗置中
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
}
private void initUI() {
setLayout(new GridBagLayout());
// ... 添加用戶名/密碼欄位、登錄按鈕 ...
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("主視窗");
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.setSize(400, 300);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
new LoginDialog(frame).setVisible(true); // 模態阻塞,直到關閉
});
}
}
| 需求 | 推薦方案 | 避坑指南 |
|---|---|---|
| 模態視窗 | 直接使用 JDialog(owner, true) | JFrame 不支援原生模態,勿用焦點搶佔大法 |
| 螢幕/父視窗置中 | setLocationRelativeTo(null/parent) | 先 pack()/setSize() 再置中,否則偏移 |