Understanding Swing’s Model

作者:IT286 日期:2006-6-20 來源:網絡蒐集


經常用Swing 開發Java GUI 程序的人一定聽過這樣的說法,Swing 控件是按MVC結構設計的。更準確地說,SwingModel-driven的結構。但不同Swing控件的Model,其作用是否相同呢?比如當你在使用JButton時,你很少需要關心ButtonModel的存在,但在JTable使用時,你卻總是需要用到 TableModel。更進一步,當你頻繁的使用JTable,你會發現你可能不僅用到了TableModel,還用到TableColumnModel, ListSelectionModel。這使我們意識到,Model存在不同的種類,不同類型的Model實現不同的功能。

 

GUI-State Model

 

首先,我們討論第一種Model GUI–State ModelGUI-State Model的作用在於標識控件的視覺狀態 (visual status)。例如按鈕是否被點擊,列表中的Item是否被選中。Swing的控件會代理對GUI-State Model的操作,通常我們不要直接操作GUI–State Model

 

ButtonModel

 

最常見的GUI-State ModelButtonModel,屬於這個範疇的控件有JButton JToggleButton JCheckBox JRadioButton JMenu JMenuItem JCheckBoxMenuItem JRadioButtonMenuItem(所有AbstractButton的子類)

ButtonModel需要標識的狀態有:

  1. PRESSED: Button是否被點擊了

  2. ENABLED: Button能否被點擊(是否顯示呈灰色)

  3. ROLLOVER: 鼠標是否從Button上劃過。Button通過判斷這個屬性判斷是否要顯示RolloverIcon,當然前提是Button通過setRolloverIcon,設置了RolloverIcon

  4. SELECTED: 只對RadioButton or Checkbox 有用

  5. ARMED: 鼠標點擊Button後,是否在Button該釋放

顯而易見,這些屬性都只和顯示有關。對於GUI-State Model,只有以下兩種情況我們需要關心它的處在 :(1)我們想改變控件缺省的視覺行爲(假定這種情況很少發生) (2)出於某種顯示目的共用Model,操作一個控件會改變另外一個控件的狀態(下面會討論到這種情況),其他情況下我們可以忽視它。當然還有一種情況我們需要注意,這就是在使用JRadioButton時。因爲使用JRadioButton時,一組JRadioButton同時只能有一個被選中(SELECTED),這當然只有通過操作ButtonModelSELECTED屬性來實現。不過,Swing針對這個問題引入了ButtonGroup類,通過ButtonGroup.add()方法設置同一個 button group,因此我們同樣不需要直接操作ButtonModel

 

BoundedRangeModel

 

另一個常見的GUI-State ModelBoundedRangeModel,屬於這個範疇的控件有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的部分。常見的做法是監聽BoundedRangeModelChangeEvent事件(addChangeListener(ChangeListener l)),JSlider改變了Model的值時在ChangeListener對顯示作相應的調整。

 

TableColumnModel

 

TableColumnModelJTable特有的GUI-State ModelTableColumnModel用於管理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標識的狀態有:

  1. SINGLE_SELECTION

  2. SINGLE_INTERVAL_SELECTION

  3. MULTIPLE_INTERVAL_SELECTION

 

我們分析一下其他需要判斷用戶選擇幾種控件

  1. JList, 和 JTable一樣用的是ListSelectionModel

  2. JTree, JTree需要的Selection Model最複雜,因此它有專門Selection Model: TreeSelectionModel

  3. JComboBox只能是單選,因此不需要有專門Selection Model

  4. JFileChooser,通過設置 FILES_ONLY DIRECTORIES_ONLY FILES_AND_DIRECTORIES (int) 來設定用戶是否可選擇文件,目錄,或兩者都可以。通過設定multiSelectionEnabled屬性來決定是否單選或多選,通過File數組 selectedFiles記錄當前的選擇

  5. JTabbedPane, JTabbedPane的情況有些特殊,雖然JTabbedPane也只能單選,但它能有專門的Selection Model: SingleSelectionModel

對待Selection Model的方式和其他GUI-State Model一樣,相應Jcomponent都提供專門的函數屏蔽我們對它的直接操作。

 

Application-data model

 

這類的Model決定了顯示在控件中的內容,因此往往需要我們直接的操作。他們分別是:

  1. JList: ListModel
  2. JTable: TableModel
  3. JComboBox: ComboBoxModel
  4. JTree: TreeModel
  5. 各類Text控件:Document

 

ListModel

 

Swing首先定義了接口ListModel

然後定義了抽象類AbstractListModel實現這個接口。在抽象類裏沒有定義實際數據的存儲方式。因此要實現AbstractListModel,用戶還需要定義這兩個函數

  1. public int getSize();
  2. 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,應該在ArrayListLinkedList(thread-unsafe collection)之間選擇:

  1. ArrayList也有內部容量的概念,但它提供了隨機存取的功能 (random access), 使用它時可以預先申請一塊較大的內存,以免以後重新分配內存。

  2. LinkedList沒有內部容量的概念,因此不會重新分配內存,但它不提供隨機存取的功能。

 

TableModel

 

SwingTableModel的處理和ListModel類似:

首先定義了接口TableModel

然後定義了抽象類實現這個接口AbstractTableModel。在抽象類裏沒有定義實際數據的存儲方式。

要實現AbstractTableModel,用戶還需要定義這三個函數

  1. public getColumnCount()
  2. public Object getValueAt(int rowIndex, int columnIndex)
  3. 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

 

JComboBoxJList很相似,都是用來顯示一個列表項,並可以接受用戶的選擇 (JRadioButton也實現這個功能)。但他們也有本質的不同,JComboBox可以接受用戶的輸入,並可以編輯已有的選項。通常在使用JComboBox是把它分成兩類:可編輯的和不可編輯的。缺省狀態是不可編輯的,通過調用JComboBox.setEditable(true)可將JComboBox設置成可編輯。對應這兩種狀態JComboBox定義了兩類data-model接口,ComboBoxModelMutableComboBoxModel

 

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

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章