簡介
模型過濾是這樣一種技術它在 Swing 組件體系結構中提供附加的功能與靈活性
Swing 體系結構的重要創新之一在於采用了模型/視圖/控制器 (MVC) 原理這樣就可將組件的不同角色分離開當一種體系結構具備 MVC 分離特性時即可對組件的數據與狀態作不同的解釋這允許程序員在組件及其模型之間插入過濾器對象模型過濾可以在模型內修改數據的表示還也可以改變模型所封裝數據的外在數目和順序
模型過濾器的另外兩種重要特性是
模型過濾操作不會改變底層的模型數據這使得多個組件可以共享一組數據而且每個組件都可能以不同的方式解釋這組數據
過濾器可以疊用這樣就可以依次用幾個不同的過濾器對象來解釋模型數據
已定義的代理
為了最大限度地利用 Java 平台對面向對象的支持可以簡單地認為組件由若干對象構成這些對象可以由一個通用術語 ― 代理 ― 來描述代理是實現一個公共 Java 接口並與某個特定組件相關聯的對象代理實現的接口定義代理在 MVC 體系結構中充當的角色
對於剛剛接觸 Swing 的程序員而言代理的概念似乎有些難以理解但是它們也是 AWT 組件的一種共同特征例如如果想更改 javaawtLabel 組件上的字體只需創建或獲取 javaawtFont 類的一個實例並且調用 getFont() 使該實例與組件相關聯Font 對象的內部運作細節可能很有趣但是組件只要有 Font 類型對象的一個引用即可適當地顯示自己甚至像標簽前景顏色這種簡單概念也是通過代理實現的;javaawtColor 類提供一種適合作組件前景顏色的對象作為一般規則值為非基本數據類型的各種組件屬性都可看作是代理
Swing 中的 MVC 實現就是這些概念的體現對象不僅用於表示組件的屬性值也用於表示組件行為的諸多方面這種方案相當靈活足以支持 Swing 的可插接外觀 (PLAF) 功能的實現該功能使應用程序既可模擬本地平台的外觀也可用一種與平台無關的方案顯示組件PLAF 既可使應用程序看起來就像 Microsoft Windows Mac OS 和 X/Motif 等平台的本地應用程序一樣也可使應用程序具有一種中立的外觀稱為 Java LAF 或 Metal LAF
PLAF 功能與組件的外觀密切相關本文主要討論這一體系結構的模型部分它與組件的外觀的無關
作為一種模型(或類似一種模型)
每種支持數據與狀態的 Swing 組件都有一種與之相關的模型接口無論接口感興趣的是封裝於該模型的數據還是狀態它都會包含允許組件以編程方式查詢模型內容的若干方法
每個模型接口都提供兩類方法一類方法提供對數據與狀態的訪問而另一類方法允許組件或者其他對象注冊或取消注冊事件監聽程序監聽程序的類型及其提供的事件對象都由這些方法定義
Swing 模型接口可以有不同類型的類實現在許多情況下為模型提供的是一種抽象實現;除了為了觸發模型所表示的各種事件方法而提供的 protected 方法之外這通常是一種不完全的正則實現所有模型都有一個缺省實現並且是一個具體類
既好又簡單 ― ListModel 接口
在開始討論過濾之前對典型的模型接口作一回顧不失為明智之舉
ListModel 接口代表 JList 組件中的數據這是三種集合模型中最簡單的一種(另外兩種分別是 JTree 和 JTable) ListModel 有兩個方法用於檢索列表中的元素個數以及各個元素另外還有兩個方法用於維護感興趣的監聽程序列表以便監聽列表模型的變化
ListModel 的簡化源代碼
package javax
swing;
import javax
swing
event
ListDataListener;
public interface ListModel
{
int getSize();
Object getElementAt(int index);
void addListDataListener(ListDataListener listener);
void removeListDataListener(ListDataListener listener);
}
在 ListModel 接口中 getSize() 與 getElementAt() 方法用於遍歷模型中的元素而其他兩個方法用於建立與感興趣的監聽程序之間的關聯以便監聽模型的變化
ListDataListener 接口支持三個方法當模型監聽到其底層數據發生變化時就會調用這三個方法這三個方法是 intervalAdded() intervalRemoved() 和 contentsChanged() 每個方法都接受單個 ListDataEvent 作為參數根據模型所發生變化的復雜程度之不同模型實現可以使用其中的任一個方法來描述這些變化通常 intervalAdded() 和 intervalRemoved() 用於描述變化的時間間隔;當變化過於復雜無法作為一個閉合間隔進行描述時就會用到 contentsChanged()
為了理解模型過濾如何運作請記住這一點JList 組件只對 ListModel API 的實現感興趣該組件並不關心數據駐留何處以及數據是如何組織的無論該模型是一個缺省類抽象類的擴展還是 ListModel 接口的一種直接實現都不影響 JList 組件的行為
模型過濾的基本概念利用了 Swing 組件對模型類的底層實現缺乏了解這一事實下圖說明了這種典型的關系
模型過濾器是實現了模型接口但並不真正包含數據的類模型過濾器在組件與其模型之間進行協調模型過濾器可以重新解釋模型所提供的信息並且可以更改所提供的數據元素個數數據的順序以及數據本身
在本例中過濾器類是將一個現有模型類作為其數據源來實例化的在模型過濾器的一般實現中對 API 方法的調用將委托給源模型
由於此 API 是統一實現的因此完全可以在組件與其模型之間疊放多個過濾器注意每個過濾層都要求每個 API 調用穿過一個附加的間接層;如果過濾層過於復雜則很可能造成性能瓶頸
基本過濾器
下面顯示的抽象類是作用於 JList 組件之上的模型過濾器的基類其唯一的構造函數要求模型過濾器的每個實例都要引用某個底層的模型數據該數據既可以是另一個模型過濾器也可以不是;在這兩種情況下過濾器的行為是相同的
模型過濾器基類
package com
ketherware
models;
import javax
swing
*;
public abstract class AbstractListModelFilter extends AbstractListModel
{
// 用來保存被過濾模型的引用
protected ListModel delegate;
// 構造函數 ― 接受單個參數
其中包含被過濾模型的引用
public AbstractListModelFilter(ListModel delegate)
{
this
delegate = delegate;
}
public ListModel getDelegate()
{
return this
delegate;
}
public int getSize()
{
// 委托給過濾器目標
return delegate
getSize();
}
public Object getElementAt(int index)
{
// 委托給過濾器目標
return delegate
getElementAt(index);
}
public void addListDataListener(ListDataListener listener)
{
// 委托給過濾器目標
delegate
addListDataListener(listener);
}
public void removeListDataListener(ListDataListener listener)
{
// 委托給過濾器目標
delegate
removeListDataListener(listener);
}
}
該類相當於一種空過濾器它不更改任何底層數據因此它沒有什麼特別的意義ListModel 過濾器類的實際實現將覆蓋該抽象類的方法以便以不同的方式呈現底層數據
您可以通過實現過濾器來改變底層數據事件的特性為了使對模型過濾器的討論更易於理解本文的示例都只針對不可變的數據模型即不觸發任何模型事件的類
缺省模型適合於要求不高的一般應用但是您應該了解這些缺省類都是為通用目的而設計的因此在對性能有嚴格要求的情況下它們通常表現不佳同樣許多常用的模型都是作為可變模型來實現的即模型的數據可隨時間變化當已知數據為靜態數據時這些額外的行為可能是多余的因此您可能想另外構建模型類去掉由事件傳播所導致的額外開銷
不可變模型
在許多情況下根據模型的底層數據是否可變對模型進行分類很有用在數據不會變化的情況下可以實現不可變的數據模型這種模型不實現用於監聽數據變化的監聽程序Swing 模型接口的缺省實現假定數據是可變的
不可變模型的創建過程相當簡單您可以創建一個具體類該類可提供模型接口但為與事件相關的活動所提供的所有方法都不執行任何操作根據模型要作為一般模型使用還是作為專用模型使用您既可將此不可變模型實現為一個抽象類也可將其實現為一個具體類
下面的示例是一個不可變的列表模型我設計它時希望它非常通用並且允許將支持 javautilList 集合接口的任何對象用作數據源返回的數據是一個籠統的 Object 類型;如何顯示對象留待 JList 及其相關繪制程序解釋
不可變模型的示例
package com
ketherware
models;
import java
util
*;
import javax
swing
*;
public abstract class ImmutableListModelFilter extends AbstractListModel
{
// 用來保存被過濾模型的引用
protected List collection;
// 構造函數 ― 接受單個參數
其中包含被過濾模型的引用
public AbstractListModelFilter(List collection)
{
llection = collection;
}
public List getCollection()
{
return llection;
}
public int getSize()
{
// 委托給集合
return collection
size();
}
public Object getElementAt(int index)
{
// 委托給過濾器目標
return collection
get(index);
}
public void addListDataListener(ListDataListener listener)
{
// 覆蓋為
空操作
}
public void removeListDataListener(ListDataListener listener)
{
// 覆蓋為
空操作
}
}
下面將討論四種類型的過濾器替換排序排除和包含
替換過濾的目的在於重新解釋模型數據並且通過改變返回的對象元素來表示它這種類型的過濾器不改變數據元素的順序它既不刪除數據也不創建額外的數據
下面是一個替換過濾器的示例它為底層模型中的每個數據項添加一個數字索引唯一的變化是覆蓋了單個方法
替換過濾器的示例
package com
ketherware
models;
import javax
swing
*;
public abstract class IndexingListModelFilter extends AbstractListModelFilter
{
public Object getElementAt(int index)
{
// 委托給過濾器目標
String element = delegate
getElementAt(index)
toString();
return Integer
toString(index) + ? ?+ element;
}
}
在許多情況下在繪制程序中引入補充的特性可能更合適比如填加一個行索引您可以提供一個過濾器它通過與繪制程序交互來提供額外的圖形表示使用過濾器代替繪制程序的優點在於可用一個組件顯示經過索引的數據而無須與繪制程序相關聯
替換過濾器通常不覆蓋 getSize() 而且不改變所返回元素的順序
排序過濾器
排序過濾器代表了另一層面的復雜性它們不改變所表示元素的個數在這一點上與替換過濾器類似排序過濾器改變模型中經過索引的元素順序其基本技術在於創建模型元素的一種替代索引用於代替實際的順序
排序過濾器的一種常見類型是分類過濾器它基於某個明確的排序順序重新索引數據下面的示例按字母順序排列任一個 ListModel 實現的內容
排序過濾器的示例
package com
ketherware
models;
import java
util
*;
import javax
swing
*;
public abstract class AlphaSortingListModelFilter extends
AbstractListModel
{
// 已排序的索引數組
protected ArrayList sortedIndex;
public AlphaSortingListModelFilter(ListModel delegate)
{
this
delegate = delegate;
resort();
}
// 該算法稱為
插入排序
適合於處理元素個數少於幾百個的數據
// 它是一種
無堆棧
排序
protected synchronized void resort()
{
sortedIndex = new ArrayList();
nextElement:
for (int x=
; x < delegate
getSize(); x++)
{
for (int y=
; y < x; y++)
{
String current =
delegate
getElementAt(x)
toString();
int compareIndex =
((Integer) sortedIndex
get(y))
intValue();
String compare =
sortedIndex
get(compareIndex)
toString();
if (pareTo(compare) <
)
{
sortedList
add(new Integer(x)
y);
continue nextElement;
}
}
sortedList
add(new Integer(x));
}
}
public Object getElementAt(int index)
{
// 委托給過濾器目標
但使用已排序的索引
return delegate
getElementAt(sortedIndex[index]);
}
}
可以將一種排序過濾器用於 JTable 組件以便對表數據執行面向列的排序;這種過濾器的代碼類似於上面的示例通過修改 JTable 的表頭和表的模型組件該過濾器可以得到進一步的增強
請注意上面的示例只對不可變列表模型有效如果數據在動態變化為了修改在事件被觸發時由 ListDataEvent 對象傳遞的索引必須提供一些附加支持這將顯著增加過濾器的復雜性我將它的實現作為一個練習留給讀者
排序過濾器的主要特征在於他們不增加或者減少模型的可見元素個數因此 getSize() 將委托給被過濾的模型他們通常將不改變數據元素而只是按照某種替代順序解釋數據的索引
排除過濾器
最後兩種類型的過濾器非常相似但是擁有完全不同的目的排除過濾器與包含過濾器都允許對模型的數據元素進行限制或者補充額外的元素
排除過濾器使模型中的某些元素看似不存在在只有單一數據源可用並且實現方案只要求顯示數據的一個子集的情況下這些過濾器相當有效
關於典型的排除過濾器的示例請參考 TerritoryListModelFilterjava該示例給出了一個銷售區域列表其中每個區域都與一個銷售人員相關聯當選定一個銷售人員的姓名時過濾器只顯示與該銷售人員相關聯的那些區域
這個示例的優點非常明顯如果不進行過濾則每次選定一個不同的銷售人員都需要重新加載數據模型或者在高速緩存中保存大量的模型實例過濾器甚至允許兩個不同的組件用兩種不同的解釋方案查看同一個基本模型
包含過濾器
包含過濾器盡管不像排除過濾器那樣廣泛適用但它們可用來向模型中添加信息由於這種類型的過濾器可用於進行總計或者小計這些過濾器的最佳用途是報表應用程序
執行總計操作的過濾器創建一個虛擬元素並將其顯示在列表模型的尾部為了實現這一功能過濾器將模型大小的值加 並將對除最後一個元素之外的所有元素的請求發送至代理 SalesTotalListModelFilterjava 中的示例假定列表數據是不可變的;過濾器將列表數據事件忽略這裡再一次用到前一個示例中的 TerritoryListModel
小結
這些示例已經顯示了模型過濾的某些應用過濾是一種應用相當廣泛的概念遠遠不止本文這些相對比較簡單的應用當您開始實現過濾器時請記住下列幾點
過濾可以向不同組件提供不同的視圖並且可以減少應用程序必須支持的完整模型實例的個數
過濾可以應用於 Swing 支持的其他模型包括選擇模型
您可以為處理可變模型或者動態模型構造非常復雜的過濾方案為了實現這一點可以用一個過濾器來處理由該代理模型傳遞的事件
您可以無限地嵌套(或疊用)過濾器但是當每次修改或者查詢模型時每個過濾層都會增加一些額外的處理負擔
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26101.html