2014年5月16日 星期五

Java Swing 測試 : 內部框架

Java Swing 提供多重文件介面 (MDI, Multiple Document Interface) 功能, 可以在 GUI 視窗中開啟多個文件, 這主要是透過 JInternalFrame 內部框架類別, 以及 JDesktopPane 桌面面板類別的輔助, JDesktopPane 是 JLayeredPane 的子類別, 主要是用來建立虛擬桌面, 利用其圖層功能 (z-order) 協助管理所開啟的多個內部框架之間的層次關係. 相關的 API 如下 :

# JInternalFrame
# JDesktopPane

JInternalFrame 與 JFrame 用法完全一樣, 都有 ContentPane, 預設都是 BorderLayout, 但兩者還是有差別的 :
  1. JFrame 為重量級元件, 是 Swing 的四個最上層容器之一 (另外三個是 JWindow, JApplet 與 JDialog), 是獨立的視窗. 而 JInternalFrame 則為輕量級元件, 不是獨立視窗, 必須依附於四個最上層容器.
  2. 內部框架是以與平台無關方式實做, 比 JFrame 擁有更大彈性, 我們對 JInternalFrame 擁有較多控制權.
  3. 內部框架不是視窗, 因此不會觸發視窗事件 (window event), 而是觸發內部框架事件, 但其處理方式與視窗事件幾乎一樣 (使用 InternalFrameListener). 
Java Swing 的最上層容器繼承圖如下 :


JinternalFrames 相關類別繼承關係如下 :

java.awt.Container
    |__ javax.swing.JComponent
    |         |__ javax.swing.JLayeredPane
    |         |         |__ javax.swing.JDesktopPane
    |         |__ javax.swing.JInternalFrame
    |__ java.awt.Window
              |__ java.awt.Frame
                        |__ javax.swing.JFrame

JInternalFrame 的建構子通式如下 :

JInternalFrame(String title [, boolean resizable
                                             [,boolean closable
                                             [, boolean maximizable
                                             [, boolean iconifiable]]]])

後面四個參數乃選項,  通常全給 true, 分別表示可縮放, 可關閉, 可最大化, 以及可最小化 :

JInternalPane inf=new JInternalPane(“標題”, true, true, true, true);

內部框架的使用方式 :

先建立 JDesktopPane 與 JInternalFrame 物件, 然後用 JDesktopPane 之 add() 方法將內部視窗放進桌面面板即可. 內部框架與 JFrame 一樣, 有各自的內容面板用來擺放元件, 一樣是用 getContentPane() 取得內容面板. 在下列範例中, 我在 JFrame 中放了一個按鈕, 每按一次就新增一個內部框架 :

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

public class JIF1 implements ActionListener {
  JFrame f;
  JDesktopPane dp;
  int count=0;
  public static void main(String argv[]) {
    new JIF1();
    }
  public JIF1() {
    //Setup JFrame
    JFrame.setDefaultLookAndFeelDecorated(true);
    JDialog.setDefaultLookAndFeelDecorated(true);
    f=new JFrame("JInternalFrame Test");
    f.setSize(400,300);
    f.setLocationRelativeTo(null);
    Container cp=f.getContentPane();
    cp.setLayout(new BorderLayout());  
    f.setVisible(true);

    //Build Elements      
    JButton newInf=new JButton("新增內部框架");
    newInf.addActionListener(this);
    cp.add(newInf, BorderLayout.NORTH);
    dp=new JDesktopPane();
    cp.add(dp);

    //Close JFrame
    f.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
    f.addWindowListener(new WindowHandler(f));
    }
  public void actionPerformed(ActionEvent e) {
    if (e.getActionCommand()=="新增內部框架") {
      //Setup JInternalFrame
      JInternalFrame inf;
      inf=new JInternalFrame("內部框架 " + (++count),true,true,true,true);
      inf.setSize(200,100);
      inf.setLocation(25*count,25*count);
      inf.setVisible(true);
      dp.add(inf);
      //Build Elements
      Container icp=inf.getContentPane();
      final JTextArea ta=new JTextArea("Hello World! #" + count);
      JButton btn=new JButton("清除內容");
      btn.addActionListener(new ActionListener(){
        public void actionPerformed(ActionEvent e) {ta.setText("");}
        });
      icp.add(ta,"Center"); //也可用 BorderLayout.CENTER
      icp.add(btn,"South");
      try {inf.setSelected(true);}
      catch (Exception ine) {ine.printStackTrace();}
      }
    }
  }
class WindowHandler extends WindowAdapter {
  JFrame f;
  public WindowHandler(JFrame f) {this.f=f;}
  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);}
    }  
  }


由於 JFrame 與 JInternalFrame 預設都是 BorderLayout, 其實不需要用 setLayout() 方法特別去設定為 BorderLayout. 我在 JFrame 上方放置一個新增內部框架的按鈕, 每按一次就會在 CENTER 位置產生一個內部框架, 利用計數器 count 讓 setLocation() 把內部框架左上角位置下移 25px, 就能顯現層次堆疊效果, 否則全部新增的內部框架將重疊在一起, 感覺不到有新增.

另外 JInternalFrame 也有自己的 ContentPane, 我在每個內部框架產生時, 放一個 JTextArea 在 CENTER, 一個 JButton 在 SOUTH, 而且用內部類別方式為此按鈕註冊一個動作事件監聽器, 按下時清除 JTextArea 內預設的文字.

上面範例沒有處理 JInternalFrame 的事件, 所以關閉時就直接關掉了. JInternalFrame 的事件不是視窗事件, 而是要用 InternalFrameListener 來監聽內部框架事件.

參考 :

# Writing Event Listeners: Examples

參考資料 :

精通java swing開發[7]









沒有留言 :