Swing

Swing

Swing是一個爲Java設計的圖形用戶界面(GUI)工具包。Swing是Java API的一部分。Swing包括了GUI的元器件,如:文本框,按鈕,分隔窗格和表。
Swing用於提供一組“輕量級”(全部是 Java 語言)組件,它們用純Java寫成,所以同樣可以跨平臺使用。
輕量級元件的缺點則是執行速度較慢,優點就是可以在所有平臺上採用統一的行爲。

1. 30分鐘快速上手

1.1 Swing和AWT的關係與區別
  • 關係
    Swing是一個用於開發JAVA應用程序用戶界面的開發工具包。以抽象窗口包(AWT)爲基礎使跨平臺應用程序可以使用任何可插拔的外觀風格。
    Swing API的大部分是AWT的補充擴展而不是直接的替代。Swing用來繪製輕量級組件的核心渲染功能是由Java 2D提供的,這是AWT的一部分。然而,輕量級和重量級組件在同一個應用中使用會導致Z-order不兼容。
  • 區別
    Swing爲基於窗體的GUI應用開發設計,爲Java跨平臺特性提供了卓越的支持,它完全沒有本地代碼,不受操作系統的影響,做到了真正的跨平臺應用,甚至能夠提供本地窗口系統不支持的其他特性。因此比AWT具有更強的實用性,同時比AWT程序擁有更加精緻的外觀感受。

AWT只提供基本的組件,使很多設計變得複雜,且無法在不同的平臺下保持顯示風格的一致性。
例如:如果建立一個按鈕(Button)對象,就會有一個按鈕對象會請求底層操作系統創建一個真正的按鈕。即在WindowsNT上執行那麼創建的就是WindowsNT按鈕;在Linux上執行,那麼創建的就是Linux按鈕。因此AWT組件外觀會受到底層操作系統的影響。

1.2 Swing操作步驟
1.2.1 導入Swing包
import javax.swing.*;

大部分的Swing程序用到AWT的基礎底層結構和事件模型,因此需要導入兩個包:

import java.awt.*;
import java.awt.event.*;

如果圖形界面中包括了事件處理,那麼還需要導入事件處理包:

import.javax.swing.event.*;
1.2.2 選擇界面風格

Swing允許選擇程序的圖形界面風格常用的有Java風格,Windows風格等。
下面的代碼用於選擇圖形界面風格,這裏選擇的是跨平臺的Java界面風格。

try{
  UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
}catch(Exception e){

}
1.2.3 設置頂層容器

圖形界面至少要有一個頂層Swing容器(詳見:2.1 容器)。頂層Swing容器爲其他Swing組件在屏幕上繪製和處理事件提供支持。
例如:一個框架(JFrame)包括邊界、菜單欄、工具欄、狀態欄。以及中間佔主要部分的窗格。
窗格也可以看作是一種面板,但它是框架的一個組成部分。
組件不會直接放到框架上,而是放在若干面板(JPanel)上,這些面板再放到窗格上。
用框架對象的getContentPane()函數來獲得窗格,在調用窗格的add()函數放置面板。

// 聲明一個名爲SwingApplication的框架
JFrame frame = new JFrame("SwingApplication");
// 聲明一個空的面板
JPanel panel = new JPanel();
// 將面板添加到指定的框架中,定義添加面板的佈局方式
frame.getContentPane().add(panel, BorderLayout.Center);
...
// 調整此窗口的大小,以適合其子組件的首選大小和佈局。
frame.pack();
// 設置聲明框架可見,否則框架不顯示
frame.setVisible(true);
1.2.3 添加組件

組件(詳見:2.2 常用組件)添加需要容器的支持,添加方式與面板添加的方式相類似,面板也有用於添加元件或子面板的add()函數。

// 聲明一個按鈕組件
JButton button = new JButton("button");
// 將按鈕組件添加到之前聲明的面板上,定義添加組件在面板上的佈局方式
panel.add(button, BorderLayout.Center);
1.2.4 爲組件添加事件

組件通過添加事件來實現用戶與系統之間的交互,通過監聽鍵盤鍵入事件和鼠標操作事件來進行相應的處理。

// 添加指定的操作監聽器,以接收發自此組件的操作事件。
button.addActionListener(...);
// 添加指定的鼠標偵聽器,以接收發自此組件的鼠標事件。
button.addMouseListener(...);
// 添加指定的鼠標移動偵聽器,以接收發自此組件的鼠標移動事件。
button.addMouseMotionListener(...);
// 添加指定的鼠標滾輪偵聽器,以接收發自此組件的鼠標滾輪事件。容器還接收發自子組件的鼠標滾輪事件。
button.addMouseWheelListener(...);
// 添加指定的按鍵偵聽器,以接收發自此組件的按鍵事件。
button.addKeyListener(...);
1.3 Swing 的線程策略

通常 Swing 不是線程安全的。除非另行說明,否則所有 Swing 組件及相關類都必須在事件調度線程上訪問。
典型的 Swing 應用程序執行處理以響應用戶動作所生成的事件。例如,單擊 JButton 通知所有添加到JButton 的 ActionListener。由於用戶動作所生成的所有事件都在調度線程上指派,所以大部分開發人員不受該限制的影響。

但是,影響存在於構造以及顯示 Swing 的應用程序中。對應用程序的 main 方法或 Applet 中方法的調用不在事件調度線程上調用。因此,構造和顯示應用程序或 applet 時,必須注意要將控件轉移到事件調度線程。
轉移控件和開始處理 Swing 的首選方法是使用 invokeLater。invokeLater 方法安排Runnable 在事件調度線程上處理。
以下兩個示例都同樣很好地用於轉移控件和啓動 Swing 應用程序:

public class MyApp implements Runnable {
    public void run() {
        // Invoked on the event dispatching thread.
        // Construct and show GUI.
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new MyApp(args));
    }
}
public class MyApp {
    MyApp(String[] args) {
        // Invoked on the event dispatching thread. Do any initialization
        // here.
    }

    public void show() {
        // Show the UI.
    }

    public static void main(final String[] args) {
        // Schedule a job for the event-dispatching thread:
        // creating and showing this application's GUI.
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new MyApp(args).show();
            }
        });
    }
}

2. 技術點說明

2.1 容器

Swing中一些組件繼承JComponent類,JComponent類繼承於Container類,所以凡是繼承此類的組件都可以作爲容器使用。

容器從功能上可以分爲:
1) 頂層容器:

JFrame——框架,主要用來設計應用程序的圖像界面。
JApplet——小應用程序,主要用來設計嵌入式網頁中運行的JAVA程序。
JDialog——對話框,通常用來設計具有依賴關係的窗口。
JWindow——窗口。

2) 普通容器:

JPanel——面板,通常只有背景顏色的普通容器。
JScrollPane——滾動窗格,具有滾動條。
JToolBar——工具條,通常將多個組件排成一排或一列。
JSplitPane——分裂窗格,用來裝兩個組件的容器。
JTabbedPane——選項卡窗格,允許多個組件共享相同的界面空間。

3) 專用容器:

JInternalFrame——內部窗格,可以在一個窗口內顯示若干個類似於框架的窗口。
JLayeredPanel——分層窗格,給窗格增加了深度的概念。
JRootPane——根窗格,一般是自動創建的容器。

2.2 常用組件
控件從功能上可以分爲:
1) 基本控件,實現人機交互的組件:

JButton——按鈕控件
JComboBox——下拉選擇框
JList——列表控件
JMenu——菜單控件
JSlider——滾動條控件
JTextField——文本控件,是一個輕量級組件,它允許編輯單行文本。

2) 不可編輯信息的顯示控件,向用戶顯示不可編輯信息的控件:

JLabel——標籤,用於短文本字符串或圖像或二者的顯示區。
JProgressBar——以可視化形式顯示某些任務進度的組件。
JToolTip——用來顯示 Component 的“提示”。

3) 可編輯信息的顯示控件,向用戶顯示可編輯的格式化信息的控件:

JColorChooser——顏色選取器,提供一個用於允許用戶操作和選擇顏色的控制器窗格。
JFileChooser——文件選擇器,爲用戶選擇文件提供了一種簡單的機制。
JTable——用來顯示和編輯常規二維單元表。有很多用來自定義其呈現和編輯的工具,同時提供了這些功能的默認設置,從而可以輕鬆地設置簡單表。
JTextArea——顯示純文本的多行區域。
JTree——將分層數據集顯示爲輪廓的控件。有關面向任務的文檔和使用樹的示例。

2.3 佈局

2.3.1 Swing佈局管理器介紹

提供佈局邏輯(一句不同的佈局管理器和UI內容)

public staic void addComponentsToPane(Container pane){...}

實例化一個容器,通過它的ContentPane加載佈局邏輯內容

private static void createAndShowGUI(){
  JFrame frame = new JFrame("...");
  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

  addComponentsToPane(frame.getContentPane());

  frame.pack();
  frame.setVisible(true);
}

main()程序入口,單獨起一個線程,實例化UI。

public static void main(String[] args){
  javax.swing.SwingUtilities,invokeLater(new Runnable(){
    public void run(){
      createAndShowGUI();
    }
  });
}
2.3.2 常用的佈局管理器

常用的佈局管理器有:FlowLayout、BorderLayout、BoxLayout、CardLayout、GridLayout和GridBagLayout。

1) FlowLayout

FlowLayout類是最簡單的佈局管理器。按照:從左到右,直到沒有多餘的空間,然後,轉到下一行。

public static void addComponentsToPane(Container pane){
  pane.setLayout(new FlowLayout());
  pane.add(new JButton("button1"));
  pane.add(new JButton("button2"));
  ...
}
2) BorderLayout

BorderLayout將界面分成上下左右中五大區域:PAGE_START、PAGE_END、LINE_START、LINE_END、CENTER。

public static void addComponentsToPane(Container pane) {
  JButton button = new JButton("button1");
  pane.add(button, BorderLayout.PAGE_START);

  button = new JButton("button2");
  button.setPreferredSize(new Dimension(200, 100));
  pane.add(button, BorderLayout.CENTER)
}
3) BoxLayout

BoxLayout可以將組件由上至下或由左至右依次加入當前面板。

public static void addComponentsToPane(Container pane) {
  JPanel xPanel = new JPanel();
  xPanel.setLayout(new BoxLayout(xPanel, BoxLayout.X_AXIS));
  xPanel.add(new JButton("button1"));
  xPanel.add(new JButton("button2"));

  pane.add(xPanel, BorderLayout.PAGE_START);
}
4) CardLayout

卡片佈局和其他佈局不同,它隱藏了一些組件。卡片佈局就是一組容器或組件,它們一次僅僅顯示一個,組中每一個容器稱作卡片。

public static void addComponentsToPane(Container pane){
  final JPanel contentPanel = new JPanel();
  JPanel controlPanel = new JPanel();
  final CardLayout CardLayout  cardLayout=new CardLayout();
  pane.add(contentPanel, BorderLayout.CENTER);
  pane.add(controlPanel, BorderLayout.PAGE_END);
  controlPanel.setLayout(new FlowLayout());

  JButton[] b = new JButton[10];
  for (int i = 0; i < 10; i++) {
    b[i] = new JButton("No" + i);
    contentPanel.add(b[i]);
  }

  contentPanel.setLayout(cardLayout);
  JButton nextButton = new JButton("next");
  nextButton.addActionListener(new ActionListener(){
    public void actionPerformed(ActionEvent e){
      cardLayout.next(contentPanel);
    }
  });
}
5) GridLayout

GridLayout建立一個組件表格,並且當組件加入時,會一次從左到右,從上到下填充每個格子,並不能指定填充某一個格子。

public static void addComponentsToPane(Container pane){
  JButton[] b = new JButton[10];
  pane.setLayout(new GridLayout(3, 3));
  for (int i = 0; i < b.length; i++) {
    b[i] = new JButton("No" + i);
    pane.add(b[i]);
  }
}
6) GridBagLayout

GridBagLayout是所有AWT佈局中最複雜的,同時也是最強大的。GridBagLayout同GridLayout一樣,在容器中以網格的形式來管理組件。但GridBagLayout功能要強大得多。
1. GridBagLayout管理的所由有行和列可以大小不同。
2. GridLayout把每個組件限制在一個單元格。而GridBagLayout中的組件可以佔據任意大小的矩形區域。

public static void addComponentsToPane(Container pane){
  JButton button;
  pane.setLayout(new GridBagLayout());
  GridBagConstraints c = new GridBagConstraints();

  button = new JButton("button1");
  c.fill = GridBagConstraints.HORIZONTAL;
  c.gridx = 0;
  c.gridy = 0;
  pane.add(button, c);

  button = new JButton("button2");
  c.fill = GridBagConstraints.HORIZONTAL;
  c.weightx = 0.5;
  c.gridx = 1;
  c.gridy = 0;
  pane.add(button, c);

  ...
}

2.4 事件監聽

Java Swing組件自動產生各種事件來響應用戶行爲。Java將事件封裝成事件類,並且爲每個事件類定義一個事件監聽器。
一個組件註冊事件監聽器方法,表明該組件要響應指定事件。也就是說我們可以通過註冊監聽器,監聽事件源產生的事件,從而在事件處理程序中處理我們所需要處理的用戶行爲。

2.4.1 註冊事件的三種方法
1) 採用一個監聽多個if語句來實現

繼承ActionListener接口,並且實現actionPerformed方法。通過getActionCommend()方法來獲取
事件的事件源。
缺點:當程序比較複雜時,需要一大串if語句來實現。程序的代碼比較難閱讀和維護。

public class Test_01 extends JFrame implement ActionListener {
  Test_01(){
    JPanel panel = new JPanel();
    JButton button1 = new JButton("按鈕一");
    JButton button2 = new JButton("按鈕二");

    panel.add(button1);
    panel.add(button2);
    this.getContentPane().add(panel);
    this.setVisible(true);

    button1.addActionListener(this);
    button2.addActionListener(this);
  }

  public void actionPerformed(ActionEvent e){
    String source = e.getActionCommend();
    if(source.equals("按鈕一")){
      System.out.println("你按了按鈕一");
    }
    if(source.equals("按鈕二")){
      System.out.println("你按了按鈕二");
    }
  }

  public static void main(String args[]){
    new Test_01();
  }
}
2) 採用匿名內部類實現

缺點:由於匿名內部類和事件組是一起的。根據事件組在代碼中的位置不同,類的定義以及處理事件,不便於閱讀。如果事件處理程序比較複雜,內部類中的代碼會變得很長。

public class Test_02 extends JFrame implement ActionListener {
  Test_02(){
    JPanel panel = new JPanel();
    JButton button1 = new JButton("按鈕一");
    JButton button2 = new JButton("按鈕二");

    panel.add(button1);
    panel.add(button2);
    this.getContentPane().add(panel);
    this.setVisible(true);

    button1.addActionListener(
      new ActionListener(){
        public void actionPerformed(ActionEvent e){
          System.out.println("你按了按鈕一");
        }
    });

    button2.addActionListener(
      new ActionListener(){
        public void actionPerformed(ActionEvent e){
          System.out.println("你按了按鈕二");
        }
    });
  }

  public static void main(String args[]){
    new Test_02();
  }
}
3) 採用一般內部類實現

優點:單個事件處理,可以被工具欄、菜單欄等重複使用。

public class Test_03 extends JFrame implement ActionListener {
  Test_03(){
    JPanel panel = new JPanel();
    JButton button1 = new JButton("按鈕一");
    JButton button2 = new JButton("按鈕二");

    panel.add(button1);
    panel.add(button2);
    this.getContentPane().add(panel);
    this.setVisible(true);

    button1.addActionListener(new Button1ActionListener());
    button2.addActionListener(new Button2ActionListener());
  }

  private class Button1ActionListener implement ActionListener{
    public void actionPerformed(ActionEvent e){
      System.out.println("你按了按鈕一");
    }
  }

  private class Button2ActionListener implement ActionListener{
    public void actionPerformed(ActionEvent e){
      System.out.println("你按了按鈕二");
    }
  }

  public static void main(String args[]){
    new Test_02();
  }
}

2.5 焦點體系

2.5.1 窗口事件和焦點事件
1) FocusEvent
  • FocusEvent的應用:

指示 Component 已獲得或失去輸入焦點的低級別事件。此低級別事件由 Component(比如 TextField)生成。事件被傳遞給每一個 FocusListener 或 FocusAdapter 對象,這些對象使用 Component 的 addFocusListener 方法註冊,以接收這類事件。(FocusAdapter 對象實現 FocusListener 接口。)當發生該事件時,所有這類偵聽器對象都將獲得此 FocusEvent。

  • FocusEvent的事件類型:
事件類型 摘要
FocusEvent.FOCUS_GAINED 控件獲得焦點
FocusEvent.FOCUS_LOST 控件失去焦點
2) WindowEvent
  • WindowEvent的應用:

指示窗口狀態改變的低級別事件。當打開、關閉、激活、停用、圖標化或取消圖標化 Window 對象時,或者焦點轉移到 Window 內或移出 Window 時,由 Window 對象生成此低級別事件。 該事件被傳遞給每一個使用窗口的 addWindowListener 方法註冊以接收這種事件的 WindowListener 或 WindowAdapter 對象。(WindowAdapter 對象實現 WindowListener 接口。)發生事件時,所有此類偵聽器對象都將獲取此 WindowEvent。

  • WindowEvent焦點相關的事件類型:
事件類型 摘要
WindowEvent.WINDOW_ACTIVATED 窗口激活
WindowEvent.WINDOW_GAINED_FOCUS 窗口獲得焦點
WindowEvent.WINDOW_LOST_FOCUS 窗口失去焦點
WindowEvent.WINDOW_DEACTIVATED 窗口非激活
3) 通過FocusEvent.getOppositeComponent和WindowEvent.getOppositeWindow可以獲取焦點跳轉過程中的對立控件。
2.5.2 鍵盤事件的全局處理
  • KeyEventDispatcher:在鍵盤消息傳遞到焦點控件之前預先處理鍵盤消息,需向
    KeyboardFocusManager註冊
  • KeyEventPostProcessor:在鍵盤消息傳遞到焦點控件並由焦點空間處理之後處理鍵盤消息,需要向
    KeyboardFocusManager註冊。
  • KeyboardFocusManager以事件鏈的模式處理KeyEventDispatcher和KeyEventPostProcessor,
    並以自身作爲事件鏈的最後一個處理者。
2.5.3 鍵盤控制焦點的跳轉
  • Component提供了兩個方法用於控制鍵盤的焦點跳轉。

  • setFocusTraversalKeysEnabled(boolean)是否支持鍵盤控制焦點跳轉

參數值 摘要
true(默認) 支持鍵盤控制焦點,普通的KeyListener將接收不到焦點控制鍵。
flase 不支持鍵盤控制焦點,焦點控制鍵和其他鍵一致。

* setFocusTraversalKeys(int, Set)用指定按鍵控制焦點跳轉。
* int 參數指定跳轉方向
* KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS跳到上一個焦點控件。
* KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS跳到下一個焦點控件。
* KeyboardFocusManager.DOWN_CYCLE_TRAVERSAL_KEYS跳到下一個焦點循環體。
* KeyEventPostProcessor.UP_CYCLE_TRAVERSAL_KEYS跳到上一個焦點循環體。
* Set參數是鍵盤(AWTKeyStroke)事件的集合
* 可以多個鍵盤事件對應一個焦點跳轉操作,但不能一個事件對應多個焦點跳轉操作。
* 不能使用KEY_TYPED類型的鍵盤事件對應焦點跳轉操作,可以使用KEY_PRESSED或KEY_RELEASED。

  • Swing控制的默認情況:
    • 切換到下一個組件:
      文本域:CTRL+TAB控制焦點切換
      其他組件:TAB和CTRL+TAB都可以控制焦點切換
    • 切換到上一個組件:
      文本域:CTRL+SHIFT+TAB控制焦點切換
      其他組件:SHIFT+TAB/CTRL+SHIFT+TAB都可以控制焦點切換
2.5.4 焦點跳轉策略
  • 焦點跳轉策略(FocusTraversalPolicy)定義了焦點跳轉的順序。

  • Swing實現了兩個焦點跳轉策略

    • SortingFocusTraversalPolicy:通過一個Comparator來決定焦點跳轉
    • LayoutFocusTraversalPolicy:繼承於SortingFocusTraversalPolicy,通過控件的大小,
      位置和方向來決定焦點的跳轉。
  • 默認情況下Swing中的Containers使用LayoutFocusTraversalPolicy

2.5.5 程序控制焦點的跳轉
  • KeyboardFocusManager提供的接口:

    • KeyboardFocusManager.focusNextComponent(Component)
    • KeyboardFocusManager.focusPreviousComponent(Component)
    • KeyboardFocusManager.upFocusCycle(Component)
    • KeyboardFocusManager.downFocusCycle(Component)
  • Component提供接口:

    • Component.transferFocus()
    • Component.transferFocusBackward()
    • Component.transferFocusUpCycly()
    • Component.transferFocusDownCycle()
  • Component自身獲得焦點:

    • Component.requestFocus():支持跨窗口獲取焦點,受系統平臺限制
    • Component.requestFocusWindow:不支持跨窗口獲取焦點,不受系統平臺限制
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章