# Java Swing 測試 : JFrame
JMenuBar 位於 JRootPane 的 layeredPane 的 FRAME_CONTENT_LAYER 的上面, 此 FRAME_CONTENT_PANE 包含了 contentPane 與可有可無的 menuBar, 利用 JFrame, JApplet, JDialog, JRootPane, JInternalFrame 的 setMenuBar() 方法將一個選單加上去時, menuBar 就會出現在 contentPane 上面.
Swing 的選項功能表主要是用到 JMenuBar, JMenu, 以及 JMenuItem 這三個類別. 比較花俏部分則會用到 JCheckBoxMenuItem, JRadioButtonMenuItem 這兩個選項元件. JMenuBar 是 JFrame/JApplet 標題下面用來放選項的長條列 (bar), 而 JMenu 則是放在這長條列上的選單, 或者是選單套疊時, 作為選項的子選單; 真正觸發實際功能的是選項, 即 JMenuItem, JCheckBoxMenuItem, 以及 JRadioButtonMenuItem 這三個元件.
JRadioButtonMenuItem 功能與 JRadionButton 一樣, 是單選按鈕; 而 JCheckBoxMenuItem 則與 JCheckBox 功能一樣, 用作複選. 但 JRadioButtonMenuItem 與 JRadioButton 都必須使用 ButtonGroup 綑綁在一起才會具有整體單選作用, 否則是各自獨立的按鈕, 相關 API 如下 :
# JMenuBar
# JMenu
# JMenuItem
# JRadioButtonMenuItem
# JCheckBoxMenuItem
# ButtonGroup
# LinkedHashMap
# LookAndFeel
# MetalLookAndFeel
# UIManager
# SwingUtilities
此處要把 Swing 的內建 7 種 Look and Feel 介面以及 2 種主題 (theme) 做成 JRadioButtonMenuItem 來選擇, 我使用 LinkedHashMap 來模擬關聯式陣列 (不用 HashMap 的原因是它的元素順序無法控制), 儲存 7 種 Look&Feel 標題與類別名稱, 所以必須匯入 java.util 類別庫, 如下範例所示 :
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.util.*;
import javax.swing.plaf.metal.MetalLookAndFeel;
import javax.swing.plaf.metal.MetalTheme;
import javax.swing.plaf.metal.OceanTheme;
import javax.swing.plaf.metal.DefaultMetalTheme;
public class menutest implements ActionListener {
JFrame f;
JMenuBar mb;
JMenu configMenu,LFmenu,themeMenu;
LinkedHashMap
JRadioButtonMenuItem[] LFitem;
JRadioButtonMenuItem[] themeItem;
public static void main(String argv[]) {
new menutest();
}
public menutest() {
//Setup JFrame
JFrame.setDefaultLookAndFeelDecorated(true);
JDialog.setDefaultLookAndFeelDecorated(true);
f=new JFrame("JMenuBar Test");
f.setSize(400,300);
f.setLocationRelativeTo(null);
Container cp=f.getContentPane();
cp.setLayout(null);
//Build MenuBar
LF=new LinkedHashMap
String plaf="com.sun.java.swing.plaf";
LF.put("Metal","javax.swing.plaf.metal.MetalLookAndFeel");
LF.put("CDE/Motif", plaf + ".motif.MotifLookAndFeel");
LF.put("Windows XP", plaf + ".windows.WindowsLookAndFeel");
LF.put("Windows Classic", plaf + ".windows.WindowsClassicLookAndFeel");
LF.put("Nimbus","javax.swing.plaf.nimbus.NimbusLookAndFeel");
LF.put("GTK+", plaf + ".gtk.GTKLookAndFeel");
LF.put("Mac", plaf + ".mac.MacLookAndFeel");
mb=new JMenuBar();
configMenu=new JMenu("Config");
LFmenu=new JMenu("Look & Feel");
LFitem=new JRadioButtonMenuItem[LF.size()];
ButtonGroup LFgroup=new ButtonGroup(); //包裹選項形成單選效果
int i=0;
for (String key:LF.keySet()) {
LFitem[i]=new JRadioButtonMenuItem(key);
LFitem[i].setEnabled(isLookAndFeelSupported(LF.get(key))); //是否有支援此介面
LFmenu.add(LFitem[i]); //加入選單
LFgroup.add(LFitem[i]); //加入單選群組
LFitem[i].addActionListener(this); //註冊動作事件
if (i==0) {LFitem[i].setSelected(true);} //預設為 Metal 介面
++i;
}
configMenu.add(LFmenu);
themeMenu=new JMenu("Theme");
ButtonGroup themeGroup=new ButtonGroup();
themeItem=new JRadioButtonMenuItem[2];
themeItem[0]=new JRadioButtonMenuItem("Metal");
themeItem[1]=new JRadioButtonMenuItem("Ocean");
themeItem[0].addActionListener(this);
themeItem[1].addActionListener(this);
themeItem[0].setSelected(true);
themeMenu.add(themeItem[0]);
themeMenu.add(themeItem[1]);
themeGroup.add(themeItem[0]);
themeGroup.add(themeItem[1]);
configMenu.add(themeMenu);
mb.add(configMenu);
f.setJMenuBar(mb);
f.setVisible(true);
//Close JFrame
f.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
int result=JOptionPane.showConfirmDialog(f,
"確定要結束程式嗎?",
"確認訊息",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
if (result==JOptionPane.YES_OPTION) {System.exit(0);}
}
});
}
public void actionPerformed(ActionEvent e) {
String cmd=e.getActionCommand();
boolean isLF=cmd.equals("Metal")||cmd.equals("CDE/Motif")||
cmd.equals("Windows XP")||cmd.equals("Windows Classic")||
cmd.equals("Nimbus")||cmd.equals("GTK+")||
cmd.equals("Mac");
JOptionPane.showConfirmDialog(f,cmd,"Info",-1);
if (isLF) {
if (cmd.equals("Metal")) { //此 if-else 無效可刪除
JFrame.setDefaultLookAndFeelDecorated(true);
JDialog.setDefaultLookAndFeelDecorated(true);
}
else {
JFrame.setDefaultLookAndFeelDecorated(false);
JDialog.setDefaultLookAndFeelDecorated(false);
}
try {
for (String key:LF.keySet()) {
if (cmd.equals(key)) {
UIManager.setLookAndFeel(LF.get(key)); //設定介面
SwingUtilities.updateComponentTreeUI(f); //更新 UI 設定
//f.pack();
}
}
}
catch(Exception uie) {uie.printStackTrace();}
}
boolean isTheme=cmd.equals("Metal")||cmd.equals("Ocean");
if (isTheme) {
if (cmd.equals("Metal")) { //更新主題
MetalLookAndFeel.setCurrentTheme(new DefaultMetalTheme());
}
else {MetalLookAndFeel.setCurrentTheme(new OceanTheme());}
try {
UIManager.setLookAndFeel(new MetalLookAndFeel()); //設定 Metal 介面
SwingUtilities.updateComponentTreeUI(f); //更新 UI 設定
}
catch(Exception uie) {uie.printStackTrace();}
LFitem[0].setSelected(true);
}
}
public boolean isLookAndFeelSupported(String lnfname) { //檢查作業系統是否支持
try {
Class lnfclass=Class.forName(lnfname);
LookAndFeel lnf=(LookAndFeel)(lnfclass.newInstance());
return lnf.isSupportedLookAndFeel();
}
catch(Exception e) {return false;}
}
}
選項功能表的製作很簡單, 就是用 JMenu 選單物件的 add() 方法加入 JMenuItem 選項物件, 再用JMenuBar 選單列物件的 add() 方法加入選單, 最後用 JFrame 的 setMenuBar() 方法加入選單列即可. 選單也可以當作選項加入另一個選單中 (套疊), 形成子選單效果.
下圖為預設 Metal 介面之兩個內建主題, Ocean 與 Metal, 只在 Metal 介面有效 :
但是如果從預設的 Metal 介面切換到別的介面, JFrame 的邊框與標題列就會消失, 如下圖所示 :
這樣就沒辦法移動, 縮放, 關閉視窗了, 但只要切回 Metal 介面就會恢復了. 原因出在上列程式中, 在 JFrame 起始之前, 將 JFrame 與 JDialog 的預設裝飾設為 true 之故, 這原先是為了讓 JFrame 與 JDialog 的外框徹底 Swing 化, 不套用作業系統視窗外框的緣故 :
JFrame.setDefaultLookAndFeelDecorated(true);
JDialog.setDefaultLookAndFeelDecorated(true);
但在切換至預設之 Metal 以外的介面時, 即使將其設為 false 也沒用, 無法恢復套用作業系統視窗外框. 如果拿掉這兩行 (或改為 false), 就會正常套用作業系統視窗外框, 但切換至 Metal 時, 即使程式中將其設為 true, 也不會套用純 Java 外框. 可見這兩個指令在 JFrame 建立後再改就無效了. 以下是將上面兩個指令 remark 掉後之執行結果 :
Swing 的預設介面 Metal 除了內建的 Ocean 與 Metal 兩種主題外, 也可以自訂主題. 只要繼承 DefaultMetalTheme 類別, 並覆寫其屬性與方法即可, 相關類別之 API 如下 :
# DefaultMetalTheme
# ColorUIResource
# OceanTheme
# MetalTheme
我將上面範例做些修改, 加入一個 GreenTheme 類別, 繼承 DefaultMetalTheme 類別, 建立 ColorUIResource 物件來設定新主題的配色, 並覆寫 getPrimary1(), getPrimary2(), getPrimary3(), getSecondary1(), getSecondary2(), getSecondary3() 這六個方法, 但要注意, 此六方法權限為 protected, 覆寫時必須加上去, 否則編譯失敗 :
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.util.*;
//for theme
import javax.swing.plaf.*;
import javax.swing.plaf.metal.*;
import javax.swing.border.*;
public class menutest implements ActionListener {
JFrame f;
JMenuBar mb;
JMenu configMenu,LFmenu,themeMenu;
LinkedHashMap
JRadioButtonMenuItem[] LFitem;
JRadioButtonMenuItem[] themeItem;
public static void main(String argv[]) {
new menutest();
}
public menutest() {
//Setup JFrame
JFrame.setDefaultLookAndFeelDecorated(true);
JDialog.setDefaultLookAndFeelDecorated(true);
f=new JFrame("JMenuBar Test");
f.setSize(400,300);
f.setLocationRelativeTo(null);
Container cp=f.getContentPane();
cp.setLayout(null);
//Build MenuBar
LF=new LinkedHashMap
String plaf="com.sun.java.swing.plaf";
LF.put("Metal","javax.swing.plaf.metal.MetalLookAndFeel");
LF.put("CDE/Motif", plaf + ".motif.MotifLookAndFeel");
LF.put("Windows XP", plaf + ".windows.WindowsLookAndFeel");
LF.put("Windows Classic", plaf + ".windows.WindowsClassicLookAndFeel");
LF.put("Nimbus","javax.swing.plaf.nimbus.NimbusLookAndFeel");
LF.put("GTK+", plaf + ".gtk.GTKLookAndFeel");
LF.put("Mac", plaf + ".mac.MacLookAndFeel");
mb=new JMenuBar();
configMenu=new JMenu("Config");
LFmenu=new JMenu("Look & Feel");
LFitem=new JRadioButtonMenuItem[LF.size()];
ButtonGroup LFgroup=new ButtonGroup();
int i=0;
for (String key:LF.keySet()) {
LFitem[i]=new JRadioButtonMenuItem(key);
LFitem[i].setEnabled(isLookAndFeelSupported(LF.get(key)));
LFmenu.add(LFitem[i]);
LFgroup.add(LFitem[i]);
LFitem[i].addActionListener(this);
if (i==0) {LFitem[i].setSelected(true);}
++i;
}
configMenu.add(LFmenu);
themeMenu=new JMenu("Theme");
ButtonGroup themeGroup=new ButtonGroup();
themeItem=new JRadioButtonMenuItem[3];
themeItem[0]=new JRadioButtonMenuItem("Metal");
themeItem[1]=new JRadioButtonMenuItem("Ocean");
themeItem[2]=new JRadioButtonMenuItem("Green");
themeItem[0].addActionListener(this);
themeItem[1].addActionListener(this);
themeItem[2].addActionListener(this);
themeItem[0].setSelected(true);
themeMenu.add(themeItem[0]);
themeMenu.add(themeItem[1]);
themeMenu.add(themeItem[2]);
themeGroup.add(themeItem[0]);
themeGroup.add(themeItem[1]);
themeGroup.add(themeItem[2]);
configMenu.add(themeMenu);
mb.add(configMenu);
f.setJMenuBar(mb);
f.setVisible(true);
//Close JFrame
f.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
int result=JOptionPane.showConfirmDialog(f,
"確定要結束程式嗎?",
"確認訊息",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
if (result==JOptionPane.YES_OPTION) {System.exit(0);}
}
});
}
public void actionPerformed(ActionEvent e) {
String cmd=e.getActionCommand();
boolean isLF=cmd.equals("Metal")||cmd.equals("CDE/Motif")||
cmd.equals("Windows XP")||cmd.equals("Windows Classic")||
cmd.equals("Nimbus")||cmd.equals("GTK+")||
cmd.equals("Mac");
JOptionPane.showConfirmDialog(f,cmd,"Info",-1);
if (isLF) {
if (cmd.equals("Metal")) {
JFrame.setDefaultLookAndFeelDecorated(true);
JDialog.setDefaultLookAndFeelDecorated(true);
}
else {
JFrame.setDefaultLookAndFeelDecorated(false);
JDialog.setDefaultLookAndFeelDecorated(false);
}
try {
for (String key:LF.keySet()) {
if (cmd.equals(key)) {
UIManager.setLookAndFeel(LF.get(key));
SwingUtilities.updateComponentTreeUI(f);
//f.pack();
}
}
}
catch(Exception uie) {uie.printStackTrace();}
}
boolean isTheme=cmd.equals("Metal")||cmd.equals("Ocean")||
cmd.equals("Green");
if (isTheme) {
if (cmd.equals("Metal")) {
MetalLookAndFeel.setCurrentTheme(new DefaultMetalTheme());
}
else if (cmd.equals("Ocean")) {
MetalLookAndFeel.setCurrentTheme(new OceanTheme());
}
else {MetalLookAndFeel.setCurrentTheme(new GreenTheme());}
try {
UIManager.setLookAndFeel(new MetalLookAndFeel());
SwingUtilities.updateComponentTreeUI(f);
}
catch(Exception uie) {uie.printStackTrace();}
LFitem[0].setSelected(true);
f.setTitle("Theme : " + cmd);
}
}
public boolean isLookAndFeelSupported(String lnfname) {
try {
Class lnfclass=Class.forName(lnfname);
LookAndFeel lnf=(LookAndFeel)(lnfclass.newInstance());
return lnf.isSupportedLookAndFeel();
}
catch(Exception e) {return false;}
}
}
class GreenTheme extends DefaultMetalTheme {
ColorUIResource primary1=new ColorUIResource(50, 100, 50);
ColorUIResource primary2=new ColorUIResource(100, 150, 100);
ColorUIResource primary3=new ColorUIResource(155, 205, 155);
ColorUIResource secondary1=new ColorUIResource(110, 110, 110);
ColorUIResource secondary2=new ColorUIResource(160, 160, 160);
ColorUIResource secondary3=new ColorUIResource(205, 230, 205);
protected ColorUIResource getPrimary1() {return primary1;}
protected ColorUIResource getPrimary2() {return primary2;}
protected ColorUIResource getPrimary3() {return primary3;}
protected ColorUIResource getSecondary1() {return secondary1;}
protected ColorUIResource getSecondary2() {return secondary2;}
protected ColorUIResource getSecondary3() {return secondary3;}
}
參考資料 :
# http://docs.oracle.com/javase/7/docs/api/
# JFrame setDefaultLookAndFeelDecorated(true)
# Changing Look and Feel of Swing Application
# [原創]Swing技巧8:完美的LookAndFeel解決方案
# 关于HashMap和LinkedHashMap的工作心得
# HashMap的應用及資料排序
# Getting the Default Values for a Look and Feel
# Java Look and Feel Design Guidelines (pdf)
# Application Graphics Java look and feel Graphics Repository
版主您好
回覆刪除我在執行您的程式碼時
LF.keySet() 有關這個函示這個都會有問題 並顯示
Type mismatch: cannot convert from element type Object to String
可以請教是甚麼問題嗎
Sorry, 我重新編譯得到的卻是 :
回覆刪除error: incompatible types
for (String key:LF.keySet()) {
^
required: String
found: Object
以前是 OK 的, 可能因為 JDK 不斷改版的緣故. 我已經不再使用 Java 了, 改用 Python.