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:不支持跨窗口獲取焦點,不受系統平臺限制