Java; Desktop 的再介紹強調了今年的 JavaOne 大會對於那些抱怨 Swing 太慢太難使用界面太難看的開發人員來說Swing 和 GUI 開發所做的更新努力並沒有帶來什麼受人歡迎的好消息如果您最近沒有用過 Swing那麼您會很高興聽到其中的許多問題已經得到解決Swing 被重新設計它能執行得更好並能更好地利用 Java D APISwing 的開發者在 版甚至最新發布的 版中提高了外觀支持Swing 從沒像現在這麼好過
如果以前曾經用過 JTable那麼您可能也同時被迫使用了 TableModel您可能還注意到每個 TableModel 中的所有代碼與其他 TableModel 中的代碼幾乎是一樣的在編譯的 Java 類中有差異的代碼實際上是不存在的本文將分析 TableModel/JTable 目前的設計方法說明這種設計的不足展示為什麼它沒有實現模型視圖控制器(MVC)模式的真正目標您將看到框架和構成 TMF 框架的代碼 —— 我以前編寫的代碼與最常用的開放源代碼項目的組合使用該框架開發人員可以把 TableModel 的大小從數百行代碼減少到只有區區一行並把重要的表信息放在外部 XML 文件中在讀完本文之後只使用如下所示的一行代碼您就可以管理您的 JTable 數據
TableUtilities
setViewToModel(
tableconfig
xml
My Table
myJTable
CollectionUtilities
observableList(myData));
JTable 和 TableModel 存在的 MVC 問題
MVC 已經成為非常流行的 UI 設計模式因為它把業務邏輯清晰地從數據的視圖中分離了出來Struts 是 MVC 在 Web 上應用的一個非常好的例子最初Swing 最大的一個賣點是它采用了 MVC將視圖從模型中分離了出來代碼背後的想法是代碼的模塊化程度足夠高所以不用修改模型中的任何代碼就可以分離出視圖我想任何用過 JTables 和 TableModels 的人都會發笑告訴您這是絕對不可能的使用 MVC 設計模式的理想情況是在開發人員用 JList 或 JComboBox 替換 JTable 時可以不用修改表示數據的模式中的代碼但是在 Swing 中做不到這點Swing 使得把 JTable JList 和 JComboBox 熱交換到應用程序中成為不可能即使所有這三個組件都是用來為相同的數據模型提供視圖對於 Swing 中的 MVC 設計這是一個嚴重的不足如果您想為 JTable 交換 JList就必須重寫視圖背後的全部代碼才能實現該交換
JTable/TableModel 的另一個 MVC 缺陷是模型變化的時候視圖不會更新自身開發人員必須保持對模型的引用並調用一個函數這樣模型才會告訴視圖對自身進行更新;但是理想的情況應當是不需要任何額外的代碼就能實現自動更新
最後JTable 和 TableModel 組件設計的問題是它們彼此之間纏雜得過於密切如果您修改了 JTable 中的代碼那麼您需要確保您沒有破壞負責處理的 TableModel反之亦然對於一個被認為是在模塊化基礎上建立的設計模式來說目前的實現顯然是一種存在過多依賴關系的設計
TMF 框架更好地遵循了 MVC 的目標它把 JTable 中視圖和模型的工作更加清晰地分離開來雖然它還沒有達到讓組件能夠熱切換的更高目標但是它已經在正確方向上邁出了一步
讓我們來檢視 TMF 框架看看它是如何讓傳統 TableModel 過時的設計該框架的第一部分是學習 JTable 的使用 —— 開發人員如何使用它它顯示了什麼內容以便了理解哪些東西可以內化通用化哪些應當保留可配置狀態以便開發人員配置對於 TableModel也要進行同樣的思考我必須確定哪些東西可以從代碼中移出哪些必須留在代碼中一旦找出這些問題接下來要做的就是確定能夠讓代碼足夠通用的最佳技術以便所有人都能使用它但是還要讓代碼具備足夠的可配置性這也是為了讓每個人都能使用它
該框架分成三個基本部分一個能夠處理任何類型數據的通用 TableModel一個外部 XML 文件(負責對不同表中不同的表內容進行配置)以及模型與視圖之間的橋
在本文中您可以在 src 文件夾中找到文中介紹的所有源代碼特定於 TMF 的代碼位於 comibmjxswingtable 包中
comibmjxswingtableBeanTableModel
BeanTableModel 是框架的第一部分它充當的是通用 TableModel 您可以用它來處理任何類型的數據我知道您可能會說您怎麼這麼肯定它適用於所有的數據呢?確實很明顯我不能這麼肯定而且實際上我確信有一些它不適用的例子但是從我使用 JTables 的經驗來說我願意打賭(即使看起來我有點抬槓)實際使用中的 JTables% 都是用來顯示數據對象列表(也就是說JavaBeans 組件的 ArrayList)基於這個假設我建立了一個通用表模型它可以顯示任何數據對象列表它就是 BeanTableModel
BeanTableModel 大量使用了 Java 的內省機制來檢查 bean 中的字段顯示正確的數據它還使用了來自 Jakarta Commons Collections 框架的兩個類來輔助設計
在我深入研究代碼之前請讓我解釋來自類的幾個概念因為我可以在 bean 上使用內省機制所以我需要了解 bean 本身的信息主要是了解字段的名稱是什麼我可以通過普通的內省機制來完成這項工作我可以檢查 bean 找出其字段但是對於表來說這還不夠好因為多數開發人員想讓他們的表按照指定順序顯示字段除此之外還有一項表需要的信息我無法通過內省機制從 bean 中獲得即列名消息所以為了獲得正確顯示對於表中的每個列您需要兩條信息列名和將要顯示的 bean 中的字段我用鍵值對的格式表示該信息其中將列名用作鍵字段作為值
正因為如此我在這裡使用了來自 Collections 框架的適合這項工作的兩個類 BeanMap 用作實用工具類負責處理內省機制它接手了內省機制的所有繁瑣工作普通的內省機制開發需要大量的 try / catch 塊對於表來說這是沒有必要的 BeanMap 把 bean 作為輸入像處理 HashMap 那樣來處理它在這裡鍵是 bean 中的字段(例如 firstName )值是 get 方法(例如 getFirstName() )的結果BeanTableModel 廣泛地運用 BeanMap 消除了操作內省機制的麻煩也使得訪問 bean 中的信息更加容易
LinkedMap 是另外一個在 BeanTableModel 中全面應用的類我們還是回到為列名字段映射所進行的鍵值數據設置對於數據對象來說很明顯應當選擇 HashMap但是HashPap 沒有保留插入的順序對於表來說這是非常重要的一部分開發人員希望在每次顯示表的時候都能以指定的順序顯示列這樣插入的順序就必須保留解決方案是 LinkedMap 它是 LinkedList 與 HashMap 的組合它既保留了列也保留了列的順序信息參見清單 可以查看我是如何用 LinkedMap 和 BeanMap 來設置表的信息的
清單 用 LinkedMap 和 BeanMap 設置表信息
protected List mapValues = new ArrayList();
protected LinkedMap columnInfo = new LinkedMap();
protected void initializeValues(Collection values)
{
List listValues = new ArrayList(values);
mapValues
clear();
for (Iterator i=erator(); i
hasNext();)
{
mapValues
add(new BeanMap(i
next()));
}
}
在 BeanTableModel 中比較有趣的檢查代碼實際上是通用 TableModel 的那一部分這部分代碼擴展了 AbstractTableModel 將清單 中的代碼與您通常用來建立傳統 TableModel 的代碼進行比較您可以看到一些類似之處
清單 BeanTableModel 中的通用 TableModel 代碼
/**
* Returns the number of BeanMaps
therefore the number of JavaBeans
*/
public int getRowCount()
{
return mapValues
size();
}
/**
* Returns the number of key
value pairings in the column LinkedMap
*/
public int getColumnCount()
{
return columnInfo
size();
}
/**
* Gets the key from the LinkedMap at the specified index (and a
* good example of why a LinkedMap is needed instead of a HashMap)
*/
public String getColumnName(int col)
{
return columnInfo
get(col)
toString();
}
/**
* Gets the class of the column
A lot of developers wonder what
* this is even used for
It is used by the JTable to use custom
* cell renderers
some of which are built into JTables already
* (Boolean
Integer
String for example)
If you write a custom cell
* renderer it would get loaded by the JTable for use in display if that
* specified class were returned here
* The function uses the BeanMap to get the actual value out of the
* JavaBean and determine its class
However
because the BeanMap
* autoboxes things
it converts the primitives to Objects for you
* (e
g
ints to Integers)
the code needs to unautobox it
since the
* function must return a Class Object
Thus
it recognizes any primitives
* and converts them to their respective Object class
*/
public Class getColumnClass(int col)
{
BeanMap map = (BeanMap)mapValues
get(
);
Class c = map
getType(columnInfo
getValue(col)
toString());
if (c == null)
return Object
class;
else if (c
isPrimitive())
return nvertPrimitiveToObject(c);
else
return c;
}
/**
* The BeanTableModel automatically returns false
and if you
* need to make an editable table
you
ll have to subclass
* BeanTableModel and override this function
*/
public boolean isCellEditable(int row
int col)
{
return false;
}
/**
* The function that returns the value that you see in the JTable
It gets
* the BeanMap wrapping the JavaBean based on the row
it uses the
* column number to get the field from the column information LinkedMap
* and then uses the field to retrieve the value out of the BeanMap
*/
public Object getValueAt(int row
int col)
{
BeanMap map = (BeanMap)mapValues
get(row);
return map
get(columnInfo
getValue(col));
}
/**
* The opposite function of the getValueAt
it duplicates the work of the
* getValueAt
but instead puts the Object value into the BeanMap instead
* of retrieving its value
*/
public void setValueAt(Object value
int row
int col)
{
BeanMap map = (BeanMap)mapValues
get(row);
map
put(columnInfo
getValue(col)
value);
super
fireTableRowsUpdated(row
row);
}
/**
* The BeanTableModel implements the CollectionListener interface
* (
of the
parts of the framework) and thus listens for changes in the
* data it is modeling and automatically updates the JTable and the
* model when a change occurs to the data
*/
public void collectionChanged(CollectionEvent e)
{
initializeValues((Collection)e
getSource());
super
fireTableDataChanged();
}
正如您所看到的BeanTableModel 的整個 TableModel 足夠通用化可以在任何表中使用它充分利用了內省機制省去了所有特定於 bean 的編碼工作在傳統的 TableModel 中這類編碼工作絕對是必需的 —— 同時也是完全冗余的BeanTableModel 還可以在 TMF 框架之外使用雖然在外面使用會喪失一些威力和靈活性
看過這段代碼之後您會提出兩個問題首先BeanTableModel 從哪裡獲得列名字段與鍵值配對的信息?第二到底什麼是 ObservableCollection ?這些問題會將我們引入框架的接下來的兩個部分這些問題的答案以及更多的內容將在本文後面接下來的章節中出現
Castor XML 解析器
保存必需的列名字段信息的最合理的位置位於 Java 類之外這樣不需要再重新編譯 Java 代碼就可以修改這個信息因為關於列名和字段的信息是 TMF 框架中惟一明確與表有關的信息這意味著整個表格都可以在外部進行配置
顯然該解決方案會自然而然把 XML 作為配置文件的語言選擇配置文件必須為多種表模型保存信息;您還需要能夠用這個文件指定每個列中的數據配置文件還應當盡可能地易於閱讀因為開發人員之外的人員有可能要修改它
這些問題的最佳解決方案是 Castor XML 解析器查看 Castor 實際使用的最佳方法就是查看如何在框架中使用它
讓我們來考慮一下配置文件的目的保存表模型和表中列的信息 XML 文件應當盡可能簡單地顯示這些信息TMF 框架中的 XML 文件用清單 所示的格式來保存表模型信息
清單 TMF 配置文件示例
<model>
<className>demo
hr
TableModelFreeExample</className>
<name>Hire</name>
<column>
<name>First Name</name>
<field>firstName</field>
</column>
<column>
<name>Last Name</name>
<field>lastName</field>
</column>
</model>
與這個目的相反的目標是開發人員必須處理的 Java 對象應當像 XML 文件一樣容易理解通過 Castor XML 解析器用來存儲 target=_blank>存儲列信息的三個 Java 對象就可以看到這一點這三個對象是 TableData (存儲文件中的所有表模型) TableModelData (存儲特定於表模型的信息)和 TableModelColumnData (存儲列信息)這三個類提供了 Java 開發人員所需的所有包裝器以便得到有關 TableModel 的所有必要信息
將所有這些包裝在一起所缺少的一個環節就是 映射文件它是一個 XML 文件Castor 用它把簡單的 XML 映射到簡單的 Java 對象中在完美的世界中映射文件也應當很簡單但事實要比這復雜得多良好的映射文件要使別的一切東西都保持簡單;所以一般來說映射文件越復雜配置文件和 Java 對象就越容易處理映射文件所做的工作顧名思義就是把 XML 對象映射到 Java 對象清單 顯示了 TMF 框架使用的映射文件
清單 TMF 框架使用的 Castor 映射文件
Code highlighting produced by Actipro CodeHighlighter (freeware)
>
<?xml version=
?>
<mapping>
<description>A mapping file for externalized table models</description>
<class name=
com
ibm
j
x
swing
table
TableData
>
<map
to xml=
data
/>
<field name=
tableModelData
collection=
arraylist
type=
com
ibm
j
x
swing
table
TableModelData
>
<bind
xml name=
tableModelData
/>
</field>
</class>
<class name=
com
ibm
j
x
swing
table
TableModelData
>
<map
to xml=
model
/>
<field name=
className
type=
string
>
<bind
xml name=
className
/>
</field>
<field name=
name
type=
string
>
<bind
xml name=
name
/>
</field>
<field name=
columns
collection=
arraylist
type=
com
ibm
j
x
swing
table
TableModelColumnData
>
<bind
xml name=
columns
/>
</field>
</class>
<class name=
com
ibm
j
x
swing
table
TableModelColumnData
>
<map
to xml=
column
/>
<field name=
name
type=
string
>
<bind
xml name=
name
/>
</field>
<field name=
field
type=
string
>
<bind
xml name=
field
/>
</field>
</class>
</mapping>
僅僅通過觀察這段代碼您就可以看出映射文件清晰地勾劃出了每個用來存儲表模型信息的類定義了類的類型並將 XML 文件中的名稱連接到了 Java 對象中的字段請保持相同的名稱這樣會讓事情簡單更好管理一些但是沒必要保持名稱相同
到現在為止列名和字段信息都已外部化可以讀入包含列信息的 Java 對象中並且可以很容易地把信息發送給 BeanTableModel並用它來設置列
ObservableCollection
TMF 框架的最後一個關鍵部分就是 ObservableCollection 您們當中的某些人可能熟悉 ObservableCollection 的概念它是 Java Collections 框架的一個成員在被修改的時候它會拋出事件從而允許其偵聽器根據這些事件執行操作雖然從來沒有將它引入 Java 語言的正式發行版中但在 Internet 上這個概念已經有了一些第三方實現就本文而言我使用了自己的 ObservableCollection 實現因為框架只需要一些最基本的功能我的實現使用了一個稱為 collectionChanged() 的方法每次發生修改時 ObservableCollection 都會在自己的偵聽器上調用該方法也可以將該用法稱為 Collection 類的 Decorator(有關 Collections 的 Decorator 更多信息請參閱 Collections 框架的站點)只需要增加幾行代碼您就可以在普通的 Collection 類中創建 Collection 類的 Observable 實例 清單 顯示了 ObservableCollection 用法的示例(這只是一個示例沒有包含在 jxzip 中)
清單 ObservableCollection 用法示例
Code highlighting produced by Actipro CodeHighlighter (freeware)
>
// convert a normal list to an ObservableList
ObservableList oList = CollectionUtilities
observableList(list);
// A listener could then register for events from this list by calling
oList
addCollectionListener(this);
// trigger event
oList
add(new Integer(
));
// listener receives event
public void collectionChanged(CollectionEvent e)
{
// event received here
}
ObservableCollection 有許多 TMF 框架之外的應用程序如果您決定采用 TMF 框架您會發現在開發代碼期間 ObservableCollection 框架有許多實際的用途
但是它在 TMF 框架中的用途重點在於它能更好地定義視圖和模型之間的關系當數據發生變化時可以自動更新視圖您可以回想一下這正是傳統 TableModel 的最大限制因為每當數據發生變化時都必須用表模型的引用來更新視圖而在 TMF 框架中使用 ObservableCollection 時當數據發生變化時視圖會自動更新不需要維護一個到模型的引用在 BeanTableModel 的 collectionChanged() 方法的實現中您可以看到這一點
TableUtilities
在該框架中執行的最後一步操作是將所有內容集成到一些實用方法中讓 TMF 框架使用起來簡單明了這些實用方法可以在 comibmjxswingtableTableUtilities 類中找到該類提供了您將需要的所有輔助函數
getColumnInfo() 該實用方法用 Castor XML 文件解析指定的文件並返回指定表模型的所有列信息返回的形式是 BeanTableModel 所需的 LinkedMap 當開發人員選擇從 BeanTableModel 中派生子類時這個方法很重要
getTableModel() 該實用方法是建立在上面的 getColumnInfo() 方法之上它獲得列的信息然後把信息傳遞給 BeanTableModel返回已經設置好所有信息的 BeanTableModel
setViewToModel() 該實用方法是最重要的函數也是 TMF 框架的主要吸引人的地方它也是建立在 getTableModel() 方法之上也有一個到 JTable 的引用(JTable 中有這個表的模型)以及一個到數據(要在表中顯示)的引用它對 JTable 上的 TableModel 進行設置並把數據傳遞給 TableModel結果是只需一行代碼就為 JTable 完成了 TableModel 的設置TMF 框架在該方法上得到了最佳印證TableModel 將永遠地被下面這個簡單的方法所代替
Code highlighting produced by Actipro CodeHighlighter (freeware)
>
TableUtilities
setViewToModel(
table_config
xml
Table
myJTable
myList);
每篇關於 GUI 編程的文章都需要一個示例本文當然也不例外該示例的目的是指出使用 TMF 框架代替傳統 TableModel 設計的主要優勢所在示例中的應用程序將在屏幕上顯示多個表並且可以添加或刪除表表中可以包含不同類型的信息( String 類型 int 類型 Boolean 類型和 BigDecimal 類型)而且最重要的是其中還包含可配置的列信息必須定期更改它們
示例應用程序的代碼從 JX 包中分離了出來您可以 HR 文件夾的 src 目錄中找到源代碼還可以雙擊 build/lib 文件中編譯好的 JAR 文件通過 JRE 運行應用程序
在示例應用程序中有兩個類可以相互交換一個叫作 TableModelFreeExample 另一個叫作 TableModelExample 這兩個類在應用程序中做的是同樣的事使應用程序產生的行為也相同但是它們的設計不同一個使用的是 TMF 框架另外一個則使用傳統的 TableModel您從它們身上注意到的第一件事可能是 TMF 類 TableModelFreeExample 該類由 行代碼構成而在傳統 TableModel 版本 TableModelExample 中它長達 行
Evil HR Director 應用程序
我要使用的示例應用程序是 Evil HR Director 應用程序它允許人力資源總監(可能很可怕戴著眼鏡)在 JTable 中查看潛在雇員的列表然後從表中選出雇傭的人新雇傭的員工的資料會轉移到當前雇員使用的兩個 JTable 中;其中一個表包含個人信息另外一個表包含財務信息在當前雇員表中總監可以隨意選擇解雇誰您可以在圖 中看到該應用程序的 UI
圖 Evil HR Director 應用程序 src=http://imgeducitycn/img_///jpg border= twffan=done>
為了進一步證明 TMF 框架的簡單性請看清單 這個清單只包含三行必需的代碼就可以創建 Evil HR Director 應用程序中包含的三個表的模型這些代碼可以在 TableModelFreeExample 中找到
清單 在 Evil HR Director 應用程序中創建模型所需要的代碼
Code highlighting produced by Actipro CodeHighlighter (freeware)
>
TableUtilities
setViewToModel(
demo/hr/resources/evil_hr_table
xml
Hire
hireTable
candidates);
TableUtilities
setViewToModel(
demo/hr/resources/evil_hr_table
xml
Personal
personalTable
employees);
TableUtilities
setViewToModel(
demo/hr/resources/evil_hr_table
xml
Financial
financialTable
employees);
為了進行比較 TableModelExample 中包含用傳統 TableModel 方法為三個表格創建模型所需要的代碼請查看示例包中的代碼不過我不想在這裡列出所有代碼因為它足足有 行!
演示 TMF 框架的靈活性
TMF 框架的巨大優勢之一是它能更加容易地基於 JTable 的應用程序在其發布之後進行修改為了證實這一點讓我們來看兩個可能的場景這兩個場景在使用 Evil HR Director 應用程序中每天都可能出現在每個場景中您都會看到框架是如何讓應用程序更加容易地適應不斷變化的用戶需求
場景 公司的策略發生變化規定在公司的應用程序中查看私人的婚姻信息是非法的
TMF最終用戶需要從 XML 配置文件中刪除 Married?married
傳統 TableModel開發人員必須深入研究 Java 代碼修改 getColumnName() 讓它無法返回列名Married?;修改 getColumnCount() 讓它返回的結果比以前返回的結果少一列;修改 getValueAt() 不讓它返回 isMarried() 然後開發人員必須重新編譯 Java 代碼並重新部署應用程序
場景 公司策略發生變化公司覺得有必要在潛在雇員表中包含居住地所在的州的信息
TMF: 最終用戶需要將 Statestate 添加到 XML 配置文件中
傳統 TableModel開發人員必須深入研究 Java 代碼修改 getColumnName() 添加一個叫作 State 新列;修改 getColumnCount() 讓它返回的列數加 ;修改 getValueAt() 讓它返回 getState() 然後開發人員必須重新編譯 Java 代碼並重新部署應用程序
您可以看到當應用程序中的表發生變化時(尤其在碰到一個總是朝令夕改的老板時更改更加頻繁)編輯 XML 文件要比重新部署整個應用程序容易得多
使用代碼
在您飛奔過去刪除所有 TableModel 代碼之前我想我還得占用您一分鐘解釋一下 jxzip 文件的內容以及您怎樣才能在您自己的項目中使用它(請記住特定於 TMF 的代碼可以在 comibmjxswingtable 包中找到;您還會在 JX 包中找到我在以前的文章Go stateoftheart with IFrame中介紹的其他代碼)
jxzip 文件包含兩上文件夾
src—— 包含本文中使用的源代碼在 src 文件夾中還有兩個文件夾一個是 HR包含構成 Evil HR Director 應用程序的源代碼;另一個是 JX包含 JX 項目中使用的所有源代碼
build—— 包含 Evil HR Director 應用程序和 JX 項目編譯後的類文件該文件夾中的 lib 文件夾則包含 HR 應用程序和 JX 項目的 JAR 文件
libzip 文件包含以下文件夾
lib—— 包含所有的第三方 JAR 文件運行應用程序或者任何使用 JX 項目的項目需要使用這些文件在這個文件夾中您還會找到第三方項目的許可
docszip 文件包含下列文件夾
docs—— 包含 JX 項目的所有 JavaDoc 信息
要在應用程序中使用 JX 包則需要把 CLASSPATH 指向 build/lib 文件夾中的 jxjar 以及 lib 文件中包含的所有三個第三方 JAR 文件第三方包的許可條款允許您重新發布本文包含的所有包但是如果有興趣對這些包做些修改請閱讀許可條款
結束語
使用 TableModel Free 框架就不用再編寫傳統 TableModel 了TMF 框架改進了 JTable 和 TableModel 模型之間的 MVC 關系更清楚地分離了它們在日後的發布中您甚至可以在不修改任何模型代碼的情況下對組件進行熱交換框架還允許您在模型發生變化時自動更新視圖從而消除傳統 TableModel 設計中所必需的視圖和模型之間的通信
TMF 框架還會極大地減少開發 GUI 所需的時間特別是在處理 JTable 時幾年以前我處理的一個應用程序中有 多個 JTable每個表都來自同一個原始表模型該應用程序可以作為示例使用 TMF 框架我們只用 行代碼就能解決問題;但是不幸的是當時還沒有 TMF所以我們最後編寫了 行額外的代碼才生成必需的表模型這不但增加了開發時間還增加了測試和調試的時間
與使用傳統 TableModel 相比使用 TMF 框架使您到了一個更加容易配置所有 JTable 的時代請想像這樣一個 POS 應用程序該應用程序被銷售給了 個不同的客戶每個客戶都有一套特定的信息所以每個用戶都想有一組顯示在 GUI 上的特定的列如果沒有 TMF 框架您就必須為每個客戶都生成一組特定的 TableModel —— 由此也就生成了一組特定的應用程序而使用可配置的 XML 文件每個客戶都可以使用相同的應用程序客戶所在地的業務分析師可以根據需要修改 XML 文件請想像一下這節約了多少開發和支持成本!
TableModel Free 框架解決了 Swing 開發人員社區的特定需求減少了處理 JTable 時的開發時間和維護開銷提高了它們對終端用戶的易用性Swing 桌面正在回歸使用像 TMF 框架這樣的工具開發人員會發現可以更容易地使用 Swing 和開發 GUI 應用程序您要做的第一步就是用 TMF 框架的一行代碼代替您所有的 TableModel把所有 TableModel 都永遠地拋到虛擬空間的黑洞中去吧
代碼下載jxzip libzip docszip
From:http://tw.wingwit.com/Article/program/Java/hx/201311/25733.html