作者:IT286 日期:2006-6-20 來源:網絡蒐集
經常用Swing 開發Java GUI 程序的人一定聽過這樣的說法,Swing 控件是按MVC結構設計的。更準確地說,Swing是Model-driven的結構。但不同Swing控件的Model,其作用是否相同呢?比如當你在使用JButton時,你很少需要關心ButtonModel的存在,但在JTable使用時,你卻總是需要用到 TableModel。更進一步,當你頻繁的使用JTable時,你會發現你可能不僅用到了TableModel,還用到TableColumnModel, ListSelectionModel。這使我們意識到,Model存在不同的種類,不同類型的Model實現不同的功能。
GUI-State Model
首先,我們討論第一種Model, GUI–State Model。GUI-State Model的作用在於標識控件的視覺狀態 (visual status)。例如按鈕是否被點擊,列表中的Item是否被選中。Swing的控件會代理對GUI-State Model的操作,通常我們不要直接操作GUI–State Model。
ButtonModel
最常見的GUI-State Model是ButtonModel,屬於這個範疇的控件有JButton ,JToggleButton ,JCheckBox, JRadioButton, JMenu, JMenuItem, JCheckBoxMenuItem, JRadioButtonMenuItem。(所有AbstractButton的子類)
ButtonModel需要標識的狀態有:
- PRESSED: Button是否被點擊了
- ENABLED: Button能否被點擊(是否顯示呈灰色)
- ROLLOVER: 鼠標是否從Button上劃過。Button通過判斷這個屬性判斷是否要顯示RolloverIcon,當然前提是Button通過setRolloverIcon,設置了RolloverIcon
- SELECTED: 只對RadioButton or Checkbox 有用
- ARMED: 鼠標點擊Button後,是否在Button該釋放
顯而易見,這些屬性都只和顯示有關。對於GUI-State Model,只有以下兩種情況我們需要關心它的處在 :(1)我們想改變控件缺省的視覺行爲(假定這種情況很少發生) (2)出於某種顯示目的共用Model,操作一個控件會改變另外一個控件的狀態(下面會討論到這種情況),其他情況下我們可以忽視它。當然還有一種情況我們需要注意,這就是在使用JRadioButton時。因爲使用JRadioButton時,一組JRadioButton同時只能有一個被選中(SELECTED),這當然只有通過操作ButtonModel的SELECTED屬性來實現。不過,Swing針對這個問題引入了ButtonGroup類,通過ButtonGroup.add()方法設置同一個 button group,因此我們同樣不需要直接操作ButtonModel。
BoundedRangeModel
另一個常見的GUI-State Model是BoundedRangeModel,屬於這個範疇的控件有JProgressBar JScrollBar JSlider。
BoundedRangeModel標識的主要狀態有:min,max,value(int),同樣的,我們很少直接操作BoundedRangeModel。使用JProgressBar 最常見的方式是在構造函數裏指定min,max或是通過get/set讀寫min,max,value。而控件再把這些請求轉發給BoundedRangeModel。
前面提到出於某種顯示目的,我們有可能需要直接操作GUI-State Model。以下是一種可能的情況(scenario):當我們把一幅面積較大的圖像放在JScrollPane,同時希望通過移動滑桿(JSlider)來控制顯示圖像顯示在JScrollPane的部分。常見的做法是監聽BoundedRangeModel的ChangeEvent事件(addChangeListener(ChangeListener l)),當JSlider改變了Model的值時在ChangeListener對顯示作相應的調整。
TableColumnModel
TableColumnModel是JTable特有的GUI-State Model。TableColumnModel用於管理TableColumn。而TableColumn代表了JTable中的每一列數據的視覺屬性,比如該列對應的data-model index(這決定了要顯示的內容,參見後面敘述),該列的寬度是否可變,列的最大、最小、首選寬度;該列的繪製器TableCellRenderer和編輯器TableCellEditor(JTable是面向列的,它基於每一列進行繪製和編輯)
Selection Model
考慮這樣一個問題,當使用JTable時,如何設定從X行到Y行處於選擇狀態呢?
我們可以通過調要JTable.setRowSelectionInterval(int index0, int index1)來實現。再進一步,如果想實現反轉選擇(Toggle Selection),即單擊齊數次處於選擇狀態,偶數次則處於非選擇狀態,JTable沒有提供直接的方法來實現。因爲JTable將選擇的工作交由Selection Model來實現。察看setRowSelectionInterval的實現
public void setRowSelectionInterval(int index0, int index1) {
selectionModel.setSelectionInterval(boundRow(index0), boundRow(index1));
}
要實現Toggle Selection就只有直接對Selection Model進行編程。
Selection Model也屬於GUI- State Model的範疇,因爲它標識也是一種視覺的狀態,選中的Item會加亮(high light)。和其他GUI- State Model一樣,通常我們不需要直接操作Selection Model。
Selection Model標識的狀態有:
- SINGLE_SELECTION
- SINGLE_INTERVAL_SELECTION
- MULTIPLE_INTERVAL_SELECTION
我們分析一下其他需要判斷用戶選擇幾種控件
- JList, 和 JTable一樣用的是ListSelectionModel
- JTree, JTree需要的Selection Model最複雜,因此它有專門Selection Model: TreeSelectionModel。
- JComboBox只能是單選,因此不需要有專門Selection Model
- JFileChooser,通過設置 FILES_ONLY ,DIRECTORIES_ONLY FILES_AND_DIRECTORIES (int) 來設定用戶是否可選擇文件,目錄,或兩者都可以。通過設定multiSelectionEnabled屬性來決定是否單選或多選,通過File數組 selectedFiles記錄當前的選擇
- JTabbedPane, JTabbedPane的情況有些特殊,雖然JTabbedPane也只能單選,但它能有專門的Selection Model: SingleSelectionModel
對待Selection Model的方式和其他GUI-State Model一樣,相應Jcomponent都提供專門的函數屏蔽我們對它的直接操作。
Application-data model
這類的Model決定了顯示在控件中的內容,因此往往需要我們直接的操作。他們分別是:
- JList: ListModel
- JTable: TableModel
- JComboBox: ComboBoxModel
- JTree: TreeModel
- 各類Text控件:Document
ListModel
Swing首先定義了接口ListModel
然後定義了抽象類AbstractListModel實現這個接口。在抽象類裏沒有定義實際數據的存儲方式。因此要實現AbstractListModel,用戶還需要定義這兩個函數
- public int getSize();
- public Object getElementAt(int index);
因爲沒有定義實際數據的存儲方式,當然沒有辦法提供這兩個函數的實現。
最後Swing提供缺省類DefaultListModel實現抽象類,缺省類以Vector作爲存儲數據的方式。
構造一個JList的實例有四種方式:
JList()
JList(final Object[] listData)
JList(final Vector listData)
JList(ListModel dataModel)
前三種構造函數裏會分別生成相應的ListModel。還可以在構造完後JList還可以用以下的函數來制定ListModel
void setListData(final Object[] listData)
void setListData(final Vector listData)
void setModel(ListModel model)
JList沒有提供編輯其Item的方法,用戶是無法直接編輯其Item的(這點和JComboBox不同,JComboBox提供了直接編輯其Item的方法),要改變Item的內容需要直接操作ListModel(用數組和Vector生成JList不適合用來顯示可變內容的數據)。
要顯示可變內容的JList,最方便的方法是用DefaultListModel,但由於它用Vcetor作爲其內部的存儲數據的方式,決定他們在處理大數據量的顯示時是不適宜的。首先Vector有內部容量的概念,當容量不足以容納更多的數據時,它需要重新分配一塊內存,複製原內存的東西,並把原來的內存丟棄,這是非常耗時的動作;其次,Vector是線程安全的容器(thread-safe collection),所有對容器的操作都需要同步(synchronized),對於包含大數據量的collection這也是非常耗時的。
因此對於大數據量的Application-data,用戶如果想用collection,應該在ArrayList和LinkedList(thread-unsafe collection)之間選擇:
- ArrayList也有內部容量的概念,但它提供了隨機存取的功能 (random access), 使用它時可以預先申請一塊較大的內存,以免以後重新分配內存。
- LinkedList沒有內部容量的概念,因此不會重新分配內存,但它不提供隨機存取的功能。
TableModel
Swing對TableModel的處理和ListModel類似:
首先定義了接口TableModel,
然後定義了抽象類實現這個接口AbstractTableModel。在抽象類裏沒有定義實際數據的存儲方式。
要實現AbstractTableModel,用戶還需要定義這三個函數
- public getColumnCount()
- public Object getValueAt(int rowIndex, int columnIndex)
- public int getRowCount()
public String getColumnName(int column) 往往也需要定義,不然在表頭將顯示爲A,B,C,D …
最後Swing提供缺省類DefaultTableModel實現抽象類,缺省類以Vector作爲存儲數據的方式。因此對大數據量的操作(比如用JTable顯示數據庫查詢的結果)同樣不適合用DefaultTableModel。當用JTable顯示數據庫查詢的結果,最好是擴展
AbstractTableModel,並讓數據庫每次只返回批量的數據。
JTable的構造函數比起JList要複雜一些, 因爲它還需要指定TableColumnModel。但對於Application-data model 的處理和JList是一樣的。
ComboBoxModel
JComboBox和JList很相似,都是用來顯示一個列表項,並可以接受用戶的選擇 (JRadioButton也實現這個功能)。但他們也有本質的不同,JComboBox可以接受用戶的輸入,並可以編輯已有的選項。通常在使用JComboBox是把它分成兩類:可編輯的和不可編輯的。缺省狀態是不可編輯的,通過調用JComboBox.setEditable(true)可將JComboBox設置成可編輯。對應這兩種狀態JComboBox定義了兩類data-model接口,ComboBoxModel和MutableComboBoxModel。
ComboBoxModel的定義如下:
public interface ComboBoxModel extends ListModel {
void setSelectedItem(Object anItem);
Object getSelectedItem();
}
它擴展ListModel並只是定義了用於獲取和設置當前選項的辦法,這是因爲JComboBox沒有Selection Model。
MutableComboBoxModel,顧名思義,當然是定義修改data-model的方法,它的定義如下:
public interface MutableComboBoxModel extends ComboBoxModel {
public void addElement( Object obj );
public void removeElement( Object obj );
public void insertElementAt( Object obj, int index );
public void removeElementAt( int index );
}
Swing提供對MutableComboBoxModel的實現DefaultComboBoxModel,其內部Vector來存儲數據,當我們想提供自己的現實時,最方便的方法是可以擴展AbstractListModel, 並選擇是實現ComboBoxModel還是MutableComboBoxModel。
構造一個JComboBox實例有四種方法:
public JComboBox()
public JComboBox(final Object items[])
public JComboBox(Vector items)
public JComboBox(ComboBoxModel aModel)
前三種構造函數裏會分別生成相應的DefaultComboBoxModel。(個人覺得這樣構造JComboBox並不好,由於沒有通過構造函數建立不變性 (invariants) ,即該JComboBox是否可編輯的,當需要修改data-model時,JComboBox都需要首先判斷data-model是否可編輯,對於不可編輯的JComboBox調用編輯函數會丟出RuntimeException ())
TreeModel
TreeModel時最複雜的一種data-model,參考文獻一有詳細說明
各類Text控件
各類Text控件是比較獨立的主題,這裏不再詳述。
參考文獻
[1] Understanding the TreeModel :
http://java.sun.com/products/jfc/tsc/articles/jtree/
[2] A Swing Architecture Overview :http://java.sun.com/products/jfc/tsc/articles/architecture/index.html
[3] JGuru Faq:
http://www.jguru.com/faq/
[4] « Swing » by Matthew Robinson & Pavel Vorobiev